diff options
Diffstat (limited to 'services/java')
44 files changed, 13249 insertions, 2985 deletions
diff --git a/services/java/com/android/server/AttributeCache.java b/services/java/com/android/server/AttributeCache.java index 81613c6..81378dc 100644 --- a/services/java/com/android/server/AttributeCache.java +++ b/services/java/com/android/server/AttributeCache.java @@ -23,7 +23,6 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; -import android.os.UserHandle; import android.util.SparseArray; import java.util.HashMap; @@ -35,54 +34,52 @@ import java.util.WeakHashMap; */ public final class AttributeCache { private static AttributeCache sInstance = null; - + private final Context mContext; - private final SparseArray<WeakHashMap<String, Package>> mPackages = - new SparseArray<WeakHashMap<String, Package>>(); + private final WeakHashMap<String, Package> mPackages = + new WeakHashMap<String, Package>(); private final Configuration mConfiguration = new Configuration(); - + public final static class Package { public final Context context; private final SparseArray<HashMap<int[], Entry>> mMap = new SparseArray<HashMap<int[], Entry>>(); - + public Package(Context c) { context = c; } } - + public final static class Entry { public final Context context; public final TypedArray array; - + public Entry(Context c, TypedArray ta) { context = c; array = ta; } } - + public static void init(Context context) { if (sInstance == null) { sInstance = new AttributeCache(context); } } - + public static AttributeCache instance() { return sInstance; } - + public AttributeCache(Context context) { mContext = context; } - + public void removePackage(String packageName) { synchronized (this) { - for (int i=0; i<mPackages.size(); i++) { - mPackages.valueAt(i).remove(packageName); - } + mPackages.remove(packageName); } } - + public void updateConfiguration(Configuration config) { synchronized (this) { int changes = mConfiguration.updateFrom(config); @@ -96,21 +93,10 @@ public final class AttributeCache { } } } - - public void removeUser(int userId) { - synchronized (this) { - mPackages.remove(userId); - } - } - - public Entry get(int userId, String packageName, int resId, int[] styleable) { + + public Entry get(String packageName, int resId, int[] styleable) { synchronized (this) { - WeakHashMap<String, Package> packages = mPackages.get(userId); - if (packages == null) { - packages = new WeakHashMap<String, Package>(); - mPackages.put(userId, packages); - } - Package pkg = packages.get(packageName); + Package pkg = mPackages.get(packageName); HashMap<int[], Entry> map = null; Entry ent = null; if (pkg != null) { @@ -124,8 +110,7 @@ public final class AttributeCache { } else { Context context; try { - context = mContext.createPackageContextAsUser(packageName, 0, - new UserHandle(userId)); + context = mContext.createPackageContext(packageName, 0); if (context == null) { return null; } @@ -133,7 +118,7 @@ public final class AttributeCache { return null; } pkg = new Package(context); - packages.put(packageName, pkg); + mPackages.put(packageName, pkg); } if (map == null) { diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index cccaf1c..4cfe5d5 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -412,8 +412,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { ConnectivityManager.MAX_NETWORK_TYPE+1]; mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1]; - mNetworkPreference = getPersistedNetworkPreference(); - mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1]; mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1]; @@ -495,6 +493,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + // Update mNetworkPreference according to user mannually first then overlay config.xml + mNetworkPreference = getPersistedNetworkPreference(); + if (mNetworkPreference == -1) { + for (int n : mPriorityList) { + if (mNetConfigs[n].isDefault() && ConnectivityManager.isNetworkTypeValid(n)) { + mNetworkPreference = n; + break; + } + } + if (mNetworkPreference == -1) { + throw new IllegalStateException( + "You should set at least one default Network in config.xml!"); + } + } + mNetRequestersPids = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1]; for (int i : mPriorityList) { mNetRequestersPids[i] = new ArrayList(); @@ -726,11 +739,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { final int networkPrefSetting = Settings.Global .getInt(cr, Settings.Global.NETWORK_PREFERENCE, -1); - if (networkPrefSetting != -1) { - return networkPrefSetting; - } - return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE; + return networkPrefSetting; } /** @@ -2527,19 +2537,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { SystemProperties.set(key, ""); } mNumDnsEntries = last; + if (SystemProperties.get("net.dns.search").equals(domains) == false) { + SystemProperties.set("net.dns.search", domains); + changed = true; + } if (changed) { try { - mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses)); + mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains); mNetd.setDefaultInterfaceForDns(iface); } catch (Exception e) { if (DBG) loge("exception setting default dns interface: " + e); } } - if (!domains.equals(SystemProperties.get("net.dns.search"))) { - SystemProperties.set("net.dns.search", domains); - changed = true; - } return changed; } @@ -2555,13 +2565,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { String network = nt.getNetworkInfo().getTypeName(); synchronized (mDnsLock) { if (!mDnsOverridden) { - changed = updateDns(network, p.getInterfaceName(), dnses, ""); + changed = updateDns(network, p.getInterfaceName(), dnses, p.getDomains()); } } } else { try { mNetd.setDnsServersForInterface(p.getInterfaceName(), - NetworkUtils.makeStrings(dnses)); + NetworkUtils.makeStrings(dnses), p.getDomains()); } catch (Exception e) { if (DBG) loge("exception setting dns servers: " + e); } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index a296d34..91ac1de 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -2670,6 +2670,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } InputMethodInfo im = mIms[which]; int subtypeId = mSubtypeIds[which]; + adapter.mCheckedItem = which; + adapter.notifyDataSetChanged(); hideInputMethodMenu(); if (im != null) { if ((subtypeId < 0) @@ -2767,7 +2769,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private final LayoutInflater mInflater; private final int mTextViewResourceId; private final List<ImeSubtypeListItem> mItemsList; - private final int mCheckedItem; + public int mCheckedItem; public ImeSubtypeListAdapter(Context context, int textViewResourceId, List<ImeSubtypeListItem> itemsList, int checkedItem) { super(context, textViewResourceId, itemsList); diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java new file mode 100644 index 0000000..e20a21f --- /dev/null +++ b/services/java/com/android/server/LockSettingsService.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Binder; +import android.os.Environment; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; +import android.provider.Settings.Secure; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.widget.ILockSettings; +import com.android.internal.widget.LockPatternUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Arrays; + +/** + * Keeps the lock pattern/password data and related settings for each user. + * Used by LockPatternUtils. Needs to be a service because Settings app also needs + * to be able to save lockscreen information for secondary users. + * @hide + */ +public class LockSettingsService extends ILockSettings.Stub { + + private final DatabaseHelper mOpenHelper; + private static final String TAG = "LockSettingsService"; + + private static final String TABLE = "locksettings"; + private static final String COLUMN_KEY = "name"; + private static final String COLUMN_USERID = "user"; + private static final String COLUMN_VALUE = "value"; + + private static final String[] COLUMNS_FOR_QUERY = { + COLUMN_VALUE + }; + + private static final String SYSTEM_DIRECTORY = "/system/"; + private static final String LOCK_PATTERN_FILE = "gesture.key"; + private static final String LOCK_PASSWORD_FILE = "password.key"; + + private final Context mContext; + + public LockSettingsService(Context context) { + mContext = context; + // Open the database + mOpenHelper = new DatabaseHelper(mContext); + } + + public void systemReady() { + migrateOldData(); + } + + private void migrateOldData() { + try { + if (getString("migrated", null, 0) != null) { + // Already migrated + return; + } + + final ContentResolver cr = mContext.getContentResolver(); + for (String validSetting : VALID_SETTINGS) { + String value = Settings.Secure.getString(cr, validSetting); + if (value != null) { + setString(validSetting, value, 0); + } + } + // No need to move the password / pattern files. They're already in the right place. + setString("migrated", "true", 0); + Slog.i(TAG, "Migrated lock settings to new location"); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to migrate old data"); + } + } + + private static final void checkWritePermission(int userId) { + final int callingUid = Binder.getCallingUid(); + if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) { + throw new SecurityException("uid=" + callingUid + + " not authorized to write lock settings"); + } + } + + private static final void checkPasswordReadPermission(int userId) { + final int callingUid = Binder.getCallingUid(); + if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) { + throw new SecurityException("uid=" + callingUid + + " not authorized to read lock password"); + } + } + + private static final void checkReadPermission(int userId) { + final int callingUid = Binder.getCallingUid(); + if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID + && UserHandle.getUserId(callingUid) != userId) { + throw new SecurityException("uid=" + callingUid + + " not authorized to read settings of user " + userId); + } + } + + @Override + public void setBoolean(String key, boolean value, int userId) throws RemoteException { + checkWritePermission(userId); + + writeToDb(key, value ? "1" : "0", userId); + } + + @Override + public void setLong(String key, long value, int userId) throws RemoteException { + checkWritePermission(userId); + + writeToDb(key, Long.toString(value), userId); + } + + @Override + public void setString(String key, String value, int userId) throws RemoteException { + checkWritePermission(userId); + + writeToDb(key, value, userId); + } + + @Override + public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { + //checkReadPermission(userId); + + String value = readFromDb(key, null, userId); + return TextUtils.isEmpty(value) ? + defaultValue : (value.equals("1") || value.equals("true")); + } + + @Override + public long getLong(String key, long defaultValue, int userId) throws RemoteException { + //checkReadPermission(userId); + + String value = readFromDb(key, null, userId); + return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value); + } + + @Override + public String getString(String key, String defaultValue, int userId) throws RemoteException { + //checkReadPermission(userId); + + return readFromDb(key, defaultValue, userId); + } + + private String getLockPatternFilename(int userId) { + String dataSystemDirectory = + android.os.Environment.getDataDirectory().getAbsolutePath() + + SYSTEM_DIRECTORY; + if (userId == 0) { + // Leave it in the same place for user 0 + return dataSystemDirectory + LOCK_PATTERN_FILE; + } else { + return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE) + .getAbsolutePath(); + } + } + + private String getLockPasswordFilename(int userId) { + String dataSystemDirectory = + android.os.Environment.getDataDirectory().getAbsolutePath() + + SYSTEM_DIRECTORY; + if (userId == 0) { + // Leave it in the same place for user 0 + return dataSystemDirectory + LOCK_PASSWORD_FILE; + } else { + return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE) + .getAbsolutePath(); + } + } + + @Override + public boolean havePassword(int userId) throws RemoteException { + // Do we need a permissions check here? + + return new File(getLockPasswordFilename(userId)).length() > 0; + } + + @Override + public boolean havePattern(int userId) throws RemoteException { + // Do we need a permissions check here? + + return new File(getLockPatternFilename(userId)).length() > 0; + } + + @Override + public void setLockPattern(byte[] hash, int userId) throws RemoteException { + checkWritePermission(userId); + + writeFile(getLockPatternFilename(userId), hash); + } + + @Override + public boolean checkPattern(byte[] hash, int userId) throws RemoteException { + checkPasswordReadPermission(userId); + try { + // Read all the bytes from the file + RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r"); + final byte[] stored = new byte[(int) raf.length()]; + int got = raf.read(stored, 0, stored.length); + raf.close(); + if (got <= 0) { + return true; + } + // Compare the hash from the file with the entered pattern's hash + return Arrays.equals(stored, hash); + } catch (FileNotFoundException fnfe) { + Slog.e(TAG, "Cannot read file " + fnfe); + return true; + } catch (IOException ioe) { + Slog.e(TAG, "Cannot read file " + ioe); + return true; + } + } + + @Override + public void setLockPassword(byte[] hash, int userId) throws RemoteException { + checkWritePermission(userId); + + writeFile(getLockPasswordFilename(userId), hash); + } + + @Override + public boolean checkPassword(byte[] hash, int userId) throws RemoteException { + checkPasswordReadPermission(userId); + + try { + // Read all the bytes from the file + RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r"); + final byte[] stored = new byte[(int) raf.length()]; + int got = raf.read(stored, 0, stored.length); + raf.close(); + if (got <= 0) { + return true; + } + // Compare the hash from the file with the entered password's hash + return Arrays.equals(stored, hash); + } catch (FileNotFoundException fnfe) { + Slog.e(TAG, "Cannot read file " + fnfe); + return true; + } catch (IOException ioe) { + Slog.e(TAG, "Cannot read file " + ioe); + return true; + } + } + + @Override + public void removeUser(int userId) { + checkWritePermission(userId); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + try { + File file = new File(getLockPasswordFilename(userId)); + if (file.exists()) { + file.delete(); + } + file = new File(getLockPatternFilename(userId)); + if (file.exists()) { + file.delete(); + } + + db.beginTransaction(); + db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private void writeFile(String name, byte[] hash) { + try { + // Write the hash to file + RandomAccessFile raf = new RandomAccessFile(name, "rw"); + // Truncate the file if pattern is null, to clear the lock + if (hash == null || hash.length == 0) { + raf.setLength(0); + } else { + raf.write(hash, 0, hash.length); + } + raf.close(); + } catch (IOException ioe) { + Slog.e(TAG, "Error writing to file " + ioe); + } + } + + private void writeToDb(String key, String value, int userId) { + writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId); + } + + private void writeToDb(SQLiteDatabase db, String key, String value, int userId) { + ContentValues cv = new ContentValues(); + cv.put(COLUMN_KEY, key); + cv.put(COLUMN_USERID, userId); + cv.put(COLUMN_VALUE, value); + + db.beginTransaction(); + try { + db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", + new String[] {key, Integer.toString(userId)}); + db.insert(TABLE, null, cv); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private String readFromDb(String key, String defaultValue, int userId) { + Cursor cursor; + String result = defaultValue; + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, + COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", + new String[] { Integer.toString(userId), key }, + null, null, null)) != null) { + if (cursor.moveToFirst()) { + result = cursor.getString(0); + } + cursor.close(); + } + return result; + } + + class DatabaseHelper extends SQLiteOpenHelper { + private static final String TAG = "LockSettingsDB"; + private static final String DATABASE_NAME = "locksettings.db"; + + private static final int DATABASE_VERSION = 1; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + setWriteAheadLoggingEnabled(true); + } + + private void createTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE + " (" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + + COLUMN_KEY + " TEXT," + + COLUMN_USERID + " INTEGER," + + COLUMN_VALUE + " TEXT" + + ");"); + } + + @Override + public void onCreate(SQLiteDatabase db) { + createTable(db); + initializeDefaults(db); + } + + private void initializeDefaults(SQLiteDatabase db) { + // Get the lockscreen default from a system property, if available + boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default", + false); + if (lockScreenDisable) { + writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0); + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { + // Nothing yet + } + } + + private static final String[] VALID_SETTINGS = new String[] { + LockPatternUtils.LOCKOUT_PERMANENT_KEY, + LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, + LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, + LockPatternUtils.PASSWORD_TYPE_KEY, + LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, + LockPatternUtils.LOCK_PASSWORD_SALT_KEY, + LockPatternUtils.DISABLE_LOCKSCREEN_KEY, + LockPatternUtils.LOCKSCREEN_OPTIONS, + LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, + LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY, + LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, + LockPatternUtils.PASSWORD_HISTORY_KEY, + Secure.LOCK_PATTERN_ENABLED, + Secure.LOCK_BIOMETRIC_WEAK_FLAGS, + Secure.LOCK_PATTERN_VISIBLE, + Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED + }; +} diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 3ddae3e..9ce02e3 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -1398,10 +1398,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void setDnsServersForInterface(String iface, String[] servers) { + public void setDnsServersForInterface(String iface, String[] servers, String domains) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - final Command cmd = new Command("resolver", "setifdns", iface); + final Command cmd = new Command("resolver", "setifdns", iface, + (domains == null ? "" : domains)); + for (String s : servers) { InetAddress a = NetworkUtils.numericToInetAddress(s); if (a.isAnyLocalAddress() == false) { diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java index 790be55..3bfd190 100644 --- a/services/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -57,15 +57,6 @@ public class NetworkTimeUpdateService { private static final int EVENT_POLL_NETWORK_TIME = 2; private static final int EVENT_NETWORK_CONNECTED = 3; - /** Normal polling frequency */ - private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs - /** Try-again polling interval, in case the network request failed */ - private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds - /** Number of times to try again */ - private static final int TRY_AGAIN_TIMES_MAX = 3; - /** If the time difference is greater than this threshold, then update the time. */ - private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000; - private static final String ACTION_POLL = "com.android.server.NetworkTimeUpdateService.action.POLL"; private static int POLL_REQUEST = 0; @@ -86,6 +77,15 @@ public class NetworkTimeUpdateService { private SettingsObserver mSettingsObserver; // The last time that we successfully fetched the NTP time. private long mLastNtpFetchTime = NOT_SET; + + // Normal polling frequency + private final long mPollingIntervalMs; + // Try-again polling interval, in case the network request failed + private final long mPollingIntervalShorterMs; + // Number of times to try again + private final int mTryAgainTimesMax; + // If the time difference is greater than this threshold, then update the time. + private final int mTimeErrorThresholdMs; // Keeps track of how many quick attempts were made to fetch NTP time. // During bootup, the network may not have been up yet, or it's taking time for the // connection to happen. @@ -97,6 +97,15 @@ public class NetworkTimeUpdateService { mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); Intent pollIntent = new Intent(ACTION_POLL, null); mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); + + mPollingIntervalMs = mContext.getResources().getInteger( + com.android.internal.R.integer.config_ntpPollingInterval); + mPollingIntervalShorterMs = mContext.getResources().getInteger( + com.android.internal.R.integer.config_ntpPollingIntervalShorter); + mTryAgainTimesMax = mContext.getResources().getInteger( + com.android.internal.R.integer.config_ntpRetry); + mTimeErrorThresholdMs = mContext.getResources().getInteger( + com.android.internal.R.integer.config_ntpThreshold); } /** Initialize the receivers and initiate the first NTP request */ @@ -143,35 +152,35 @@ public class NetworkTimeUpdateService { if (!isAutomaticTimeRequested()) return; final long refTime = SystemClock.elapsedRealtime(); - // If NITZ time was received less than POLLING_INTERVAL_MS time ago, + // If NITZ time was received less than mPollingIntervalMs time ago, // no need to sync to NTP. - if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) { - resetAlarm(POLLING_INTERVAL_MS); + if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) { + resetAlarm(mPollingIntervalMs); return; } final long currentTime = System.currentTimeMillis(); if (DBG) Log.d(TAG, "System time = " + currentTime); // Get the NTP time - if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS + if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs || event == EVENT_AUTO_TIME_CHANGED) { if (DBG) Log.d(TAG, "Before Ntp fetch"); // force refresh NTP cache when outdated - if (mTime.getCacheAge() >= POLLING_INTERVAL_MS) { + if (mTime.getCacheAge() >= mPollingIntervalMs) { mTime.forceRefresh(); } // only update when NTP time is fresh - if (mTime.getCacheAge() < POLLING_INTERVAL_MS) { + if (mTime.getCacheAge() < mPollingIntervalMs) { final long ntp = mTime.currentTimeMillis(); mTryAgainCounter = 0; // If the clock is more than N seconds off or this is the first time it's been // fetched since boot, set the current time. - if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS + if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs || mLastNtpFetchTime == NOT_SET) { // Set the system time if (DBG && mLastNtpFetchTime == NOT_SET - && Math.abs(ntp - currentTime) <= TIME_ERROR_THRESHOLD_MS) { + && Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) { Log.d(TAG, "For initial setup, rtc = " + currentTime); } if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp); @@ -186,17 +195,17 @@ public class NetworkTimeUpdateService { } else { // Try again shortly mTryAgainCounter++; - if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) { - resetAlarm(POLLING_INTERVAL_SHORTER_MS); + if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) { + resetAlarm(mPollingIntervalShorterMs); } else { // Try much later mTryAgainCounter = 0; - resetAlarm(POLLING_INTERVAL_MS); + resetAlarm(mPollingIntervalMs); } return; } } - resetAlarm(POLLING_INTERVAL_MS); + resetAlarm(mPollingIntervalMs); } /** diff --git a/services/java/com/android/server/SerialService.java b/services/java/com/android/server/SerialService.java index 5d2b2a0..1abe458 100644 --- a/services/java/com/android/server/SerialService.java +++ b/services/java/com/android/server/SerialService.java @@ -51,7 +51,12 @@ public class SerialService extends ISerialManager.Stub { public ParcelFileDescriptor openSerialPort(String path) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null); - return native_open(path); + for (int i = 0; i < mSerialPorts.length; i++) { + if (mSerialPorts[i].equals(path)) { + return native_open(path); + } + } + throw new IllegalArgumentException("Invalid serial port " + path); } private native ParcelFileDescriptor native_open(String path); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 55885e6..a7b502a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -16,12 +16,10 @@ package com.android.server; -import android.accounts.AccountManagerService; import android.app.ActivityManagerNative; import android.bluetooth.BluetoothAdapter; import android.content.ComponentName; import android.content.ContentResolver; -import android.content.ContentService; import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; @@ -32,13 +30,11 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; -import android.os.SchedulingPolicyService; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.server.search.SearchManagerService; import android.service.dreams.DreamService; import android.util.DisplayMetrics; import android.util.EventLog; @@ -48,20 +44,23 @@ import android.view.WindowManager; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; -import com.android.internal.widget.LockSettingsService; import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accounts.AccountManagerService; import com.android.server.am.ActivityManagerService; import com.android.server.am.BatteryStatsService; +import com.android.server.content.ContentService; import com.android.server.display.DisplayManagerService; import com.android.server.dreams.DreamManagerService; import com.android.server.input.InputManagerService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.NetworkStatsService; +import com.android.server.os.SchedulingPolicyService; import com.android.server.pm.Installer; import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerService; import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; +import com.android.server.search.SearchManagerService; import com.android.server.usb.UsbService; import com.android.server.wm.WindowManagerService; diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index dfcc72b..5789a53 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -43,9 +43,14 @@ import android.net.wifi.WpsInfo; import android.net.wifi.WpsResult; import android.net.ConnectivityManager; import android.net.DhcpInfo; +import android.net.DhcpResults; +import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkInfo.State; import android.net.NetworkInfo.DetailedState; +import android.net.NetworkUtils; +import android.net.RouteInfo; import android.net.TrafficStats; import android.os.Binder; import android.os.Handler; @@ -64,14 +69,16 @@ import android.text.TextUtils; import android.util.Log; import android.util.Slog; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Inet4Address; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicBoolean; -import java.io.FileDescriptor; -import java.io.PrintWriter; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.TelephonyIntents; @@ -923,10 +930,53 @@ public class WifiService extends IWifiManager.Stub { * Return the DHCP-assigned addresses from the last successful DHCP request, * if any. * @return the DHCP information + * @deprecated */ public DhcpInfo getDhcpInfo() { enforceAccessPermission(); - return mWifiStateMachine.syncGetDhcpInfo(); + DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults(); + if (dhcpResults.linkProperties == null) return null; + + DhcpInfo info = new DhcpInfo(); + for (LinkAddress la : dhcpResults.linkProperties.getLinkAddresses()) { + InetAddress addr = la.getAddress(); + if (addr instanceof Inet4Address) { + info.ipAddress = NetworkUtils.inetAddressToInt((Inet4Address)addr); + break; + } + } + for (RouteInfo r : dhcpResults.linkProperties.getRoutes()) { + if (r.isDefaultRoute()) { + InetAddress gateway = r.getGateway(); + if (gateway instanceof Inet4Address) { + info.gateway = NetworkUtils.inetAddressToInt((Inet4Address)gateway); + } + } else if (r.isHostRoute()) { + LinkAddress dest = r.getDestination(); + if (dest.getAddress() instanceof Inet4Address) { + info.netmask = NetworkUtils.prefixLengthToNetmaskInt( + dest.getNetworkPrefixLength()); + } + } + } + int dnsFound = 0; + for (InetAddress dns : dhcpResults.linkProperties.getDnses()) { + if (dns instanceof Inet4Address) { + if (dnsFound == 0) { + info.dns1 = NetworkUtils.inetAddressToInt((Inet4Address)dns); + } else { + info.dns2 = NetworkUtils.inetAddressToInt((Inet4Address)dns); + } + if (++dnsFound > 1) break; + } + } + InetAddress serverAddress = dhcpResults.serverAddress; + if (serverAddress instanceof Inet4Address) { + info.serverAddress = NetworkUtils.inetAddressToInt((Inet4Address)serverAddress); + } + info.leaseDuration = dhcpResults.leaseDuration; + + return info; } /** @@ -1232,11 +1282,25 @@ public class WifiService extends IWifiManager.Stub { pw.println("Stay-awake conditions: " + Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0)); - pw.println(); + pw.println("mScreenOff " + mScreenOff); + pw.println("mDeviceIdle " + mDeviceIdle); + pw.println("mPluggedType " + mPluggedType); + pw.println("mEmergencyCallbackMode " + mEmergencyCallbackMode); + pw.println("mMulticastEnabled " + mMulticastEnabled); + pw.println("mMulticastDisabled " + mMulticastDisabled); + pw.println("mEnableTrafficStatsPoll " + mEnableTrafficStatsPoll); + pw.println("mTrafficStatsPollToken " + mTrafficStatsPollToken); + pw.println("mTxPkts " + mTxPkts); + pw.println("mRxPkts " + mRxPkts); + pw.println("mDataActivity " + mDataActivity); + pw.println("mPersistWifiState " + mPersistWifiState.get()); + pw.println("mAirplaneModeOn " + mAirplaneModeOn.get()); + pw.println("mWifiEnabled " + mWifiEnabled); + pw.println("mNotificationEnabled " + mNotificationEnabled); + pw.println("mNotificationRepeatTime " + mNotificationRepeatTime); + pw.println("mNotificationShown " + mNotificationShown); + pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange); - pw.println("Internal state:"); - pw.println(mWifiStateMachine); - pw.println(); pw.println("Latest scan results:"); List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList(); if (scanResults != null && scanResults.size() != 0) { @@ -1261,11 +1325,10 @@ public class WifiService extends IWifiManager.Stub { pw.println("Locks held:"); mLocks.dump(pw); + mWifiWatchdogStateMachine.dump(fd, pw, args); pw.println(); - pw.println("WifiWatchdogStateMachine dump"); - mWifiWatchdogStateMachine.dump(pw); - pw.println("WifiStateMachine dump"); mWifiStateMachine.dump(fd, pw, args); + pw.println(); } private class WifiLock extends DeathRecipient { diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java index eb414fa..9eb6834 100644 --- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -18,7 +18,10 @@ package com.android.server.accessibility; import android.content.Context; import android.os.PowerManager; +import android.util.Pools.SimplePool; import android.util.Slog; +import android.view.Choreographer; +import android.view.Display; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputFilter; @@ -26,6 +29,12 @@ import android.view.MotionEvent; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; +/** + * This class is an input filter for implementing accessibility features such + * as display magnification and explore by touch. + * + * NOTE: This class has to be created and poked only from the main thread. + */ class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation { private static final String TAG = AccessibilityInputFilter.class.getSimpleName(); @@ -48,12 +57,31 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_FEATURE_TOUCH_EXPLORATION = 0x00000002; + private final Runnable mProcessBatchedEventsRunnable = new Runnable() { + @Override + public void run() { + final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); + if (DEBUG) { + Slog.i(TAG, "Begin batch processing for frame: " + frameTimeNanos); + } + processBatchedEvents(frameTimeNanos); + if (DEBUG) { + Slog.i(TAG, "End batch processing."); + } + if (mEventQueue != null) { + scheduleProcessBatchedEvents(); + } + } + }; + private final Context mContext; private final PowerManager mPm; private final AccessibilityManagerService mAms; + private final Choreographer mChoreographer; + private int mCurrentDeviceId; private boolean mInstalled; @@ -64,11 +92,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private ScreenMagnifier mScreenMagnifier; private EventStreamTransformation mEventHandler; + private MotionEventHolder mEventQueue; + AccessibilityInputFilter(Context context, AccessibilityManagerService service) { super(context.getMainLooper()); mContext = context; mAms = service; mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mChoreographer = Choreographer.getInstance(); } @Override @@ -118,10 +149,61 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } mCurrentDeviceId = deviceId; } + batchMotionEvent((MotionEvent) event, policyFlags); + } + + private void scheduleProcessBatchedEvents() { + mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, + mProcessBatchedEventsRunnable, null); + } + + private void batchMotionEvent(MotionEvent event, int policyFlags) { + if (DEBUG) { + Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags); + } + if (mEventQueue == null) { + mEventQueue = MotionEventHolder.obtain(event, policyFlags); + scheduleProcessBatchedEvents(); + return; + } + if (mEventQueue.event.addBatch(event)) { + return; + } + MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags); + holder.next = mEventQueue; + mEventQueue.previous = holder; + mEventQueue = holder; + } + + private void processBatchedEvents(long frameNanos) { + MotionEventHolder current = mEventQueue; + while (current.next != null) { + current = current.next; + } + while (true) { + if (current == null) { + mEventQueue = null; + break; + } + if (current.event.getEventTimeNano() >= frameNanos) { + // Finished with this choreographer frame. Do the rest on the next one. + current.next = null; + break; + } + handleMotionEvent(current.event, current.policyFlags); + MotionEventHolder prior = current; + current = current.previous; + prior.recycle(); + } + } + + private void handleMotionEvent(MotionEvent event, int policyFlags) { + if (DEBUG) { + Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags); + } mPm.userActivity(event.getEventTime(), false); - MotionEvent rawEvent = (MotionEvent) event; - MotionEvent transformedEvent = MotionEvent.obtain(rawEvent); - mEventHandler.onMotionEvent(transformedEvent, rawEvent, policyFlags); + MotionEvent transformedEvent = MotionEvent.obtain(event); + mEventHandler.onMotionEvent(transformedEvent, event, policyFlags); transformedEvent.recycle(); } @@ -169,7 +251,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private void enableFeatures() { if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { - mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext); + mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext, + Display.DEFAULT_DISPLAY, mAms); mEventHandler.setNext(this); } if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { @@ -201,4 +284,34 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo public void onDestroy() { /* ignore */ } + + private static class MotionEventHolder { + private static final int MAX_POOL_SIZE = 32; + private static final SimplePool<MotionEventHolder> sPool = + new SimplePool<MotionEventHolder>(MAX_POOL_SIZE); + + public int policyFlags; + public MotionEvent event; + public MotionEventHolder next; + public MotionEventHolder previous; + + public static MotionEventHolder obtain(MotionEvent event, int policyFlags) { + MotionEventHolder holder = sPool.acquire(); + if (holder == null) { + holder = new MotionEventHolder(); + } + holder.event = MotionEvent.obtain(event); + holder.policyFlags = policyFlags; + return holder; + } + + public void recycle() { + event.recycle(); + event = null; + policyFlags = 0; + next = null; + previous = null; + sPool.release(this); + } + } } diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index a34d44c..3e3e7dc 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -70,7 +70,7 @@ import android.view.IWindowManager; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; -import android.view.WindowInfo; +import android.view.MagnificationSpec; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; @@ -87,7 +87,9 @@ import com.android.internal.statusbar.IStatusBarService; import org.xmlpull.v1.XmlPullParserException; +import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -122,6 +124,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED = "temporaryEnableAccessibilityStateUntilKeyguardRemoved"; + private static final String FUNCTION_DUMP = "dump"; + private static final char COMPONENT_NAME_SEPARATOR = ':'; private static final int OWN_PROCESS_ID = android.os.Process.myPid(); @@ -532,7 +536,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { accessibilityServiceInfo, true); mUiAutomationService.onServiceConnected(componentName, serviceClient.asBinder()); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); scheduleSendStateToClientsLocked(userState); } } @@ -563,7 +567,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mTouchExplorationGrantedServices.add(service); // Update the internal state. performServiceManagementLocked(userState); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); scheduleSendStateToClientsLocked(userState); } } @@ -617,6 +621,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } focus.getBoundsInScreen(outBounds); + + MagnificationSpec spec = service.getCompatibleMagnificationSpec(focus.getWindowId()); + if (spec != null && !spec.isNop()) { + outBounds.offset((int) -spec.offsetX, (int) -spec.offsetY); + outBounds.scale(1 / spec.scale); + } + // Clip to the window rectangle. Rect windowBounds = mTempRect; getActiveWindowBounds(windowBounds); @@ -624,6 +635,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // Clip to the screen rectangle. mDefaultDisplay.getRealSize(mTempPoint); outBounds.intersect(0, 0, mTempPoint.x, mTempPoint.y); + return true; } finally { client.removeConnection(connectionId); @@ -644,19 +656,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { token = getCurrentUserStateLocked().mWindowTokens.get(windowId); } } - WindowInfo info = null; try { - info = mWindowManagerService.getWindowInfo(token); - if (info != null) { - outBounds.set(info.frame); + mWindowManagerService.getWindowFrame(token, outBounds); + if (!outBounds.isEmpty()) { return true; } } catch (RemoteException re) { /* ignore */ - } finally { - if (info != null) { - info.recycle(); - } } return false; } @@ -673,6 +679,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mSecurityPolicy.onTouchInteractionEnd(); } + void onMagnificationStateChanged() { + notifyClearAccessibilityNodeInfoCacheLocked(); + } + private void switchUser(int userId) { synchronized (mLock) { // The user switched so we do not need to restore the current user @@ -723,7 +733,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mTempStateChangeForCurrentUserMemento.clear(); // Update the internal state. performServiceManagementLocked(userState); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); scheduleSendStateToClientsLocked(userState); } } @@ -758,6 +768,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } + private void notifyClearAccessibilityNodeInfoCacheLocked() { + UserState state = getCurrentUserStateLocked(); + for (int i = state.mServices.size() - 1; i >= 0; i--) { + Service service = state.mServices.get(i); + service.notifyClearAccessibilityNodeInfoCache(); + } + } + /** * Removes an AccessibilityInteractionConnection. * @@ -869,7 +887,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { service.linkToOwnDeath(); userState.mServices.add(service); userState.mComponentNameToServiceMap.put(service.mComponentName, service); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); tryEnableTouchExplorationLocked(service); } catch (RemoteException e) { /* do nothing */ @@ -891,7 +909,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mComponentNameToServiceMap.remove(service.mComponentName); service.unlinkToOwnDeath(); service.dispose(); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); tryDisableTouchExplorationLocked(service); return removed; } @@ -1069,7 +1087,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - private void updateInputFilterLocked(UserState userState) { + private void scheduleUpdateInputFilter(UserState userState) { + mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_INPUT_FILTER, userState).sendToTarget(); + } + + private void updateInputFilter(UserState userState) { boolean setInputFilter = false; AccessibilityInputFilter inputFilter = null; synchronized (mLock) { @@ -1179,7 +1201,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { handleAccessibilityEnabledSettingChangedLocked(userState); performServiceManagementLocked(userState); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); scheduleSendStateToClientsLocked(userState); } @@ -1258,6 +1280,46 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + @Override + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); + synchronized (mLock) { + pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)"); + pw.println(); + pw.println("Ui automation service: bound=" + (mUiAutomationService != null)); + pw.println(); + if (mUiAutomationService != null) { + mUiAutomationService.dump(fd, pw, args); + pw.println(); + } + final int userCount = mUserStates.size(); + for (int i = 0; i < userCount; i++) { + UserState userState = mUserStates.valueAt(i); + pw.append("User state[attributes:{id=" + userState.mUserId); + pw.append(", currentUser=" + (userState.mUserId == mCurrentUserId)); + pw.append(", accessibilityEnabled=" + userState.mIsAccessibilityEnabled); + pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled); + pw.append(", displayMagnificationEnabled=" + + userState.mIsDisplayMagnificationEnabled); + pw.append("}"); + pw.println(); + pw.append(" services:{"); + final int serviceCount = userState.mServices.size(); + for (int j = 0; j < serviceCount; j++) { + if (j > 0) { + pw.append(", "); + pw.println(); + pw.append(" "); + } + Service service = userState.mServices.get(j); + service.dump(fd, pw, args); + } + pw.println("}]"); + pw.println(); + } + } + } + private class AccessibilityConnectionWrapper implements DeathRecipient { private final int mWindowId; private final int mUserId; @@ -1294,6 +1356,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public static final int MSG_SEND_RECREATE_INTERNAL_STATE = 4; public static final int MSG_UPDATE_ACTIVE_WINDOW = 5; public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 6; + public static final int MSG_UPDATE_INPUT_FILTER = 7; public MainHandler(Looper looper) { super(looper); @@ -1337,6 +1400,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: { announceNewUserIfNeeded(); } break; + case MSG_UPDATE_INPUT_FILTER: { + UserState userState = (UserState) msg.obj; + updateInputFilter(userState); + } break; } } @@ -1394,9 +1461,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection, DeathRecipient { - // We pick the MSB to avoid collision since accessibility event types are + // We pick the MSBs to avoid collision since accessibility event types are // used as message types allowing us to remove messages per event type. private static final int MSG_ON_GESTURE = 0x80000000; + private static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 0x40000000; final int mUserId; @@ -1450,6 +1518,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final int gestureId = message.arg1; notifyGestureInternal(gestureId); } break; + case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: { + notifyClearAccessibilityNodeInfoCacheInternal(); + } break; default: { final int eventType = type; notifyAccessibilityEventInternal(eventType); @@ -1592,7 +1663,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override - public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, + public boolean findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId, int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { @@ -1603,17 +1674,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { .resolveCallingUserIdEnforcingPermissionsLocked( UserHandle.getCallingUserId()); if (resolvedUserId != mCurrentUserId) { - return -1; + return false; } mSecurityPolicy.enforceCanRetrieveWindowContent(this); final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this); if (!permissionGranted) { - return 0; + return false; } else { resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return 0; + return false; } } } @@ -1621,10 +1692,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); + MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); try { connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId, - interactionId, callback, flags, interrogatingPid, interrogatingTid); - return getCompatibilityScale(resolvedWindowId); + interactionId, callback, flags, interrogatingPid, interrogatingTid, spec); + return true; } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); @@ -1632,11 +1704,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return 0; + return false; } @Override - public float findAccessibilityNodeInfosByText(int accessibilityWindowId, + public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { @@ -1647,18 +1719,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { .resolveCallingUserIdEnforcingPermissionsLocked( UserHandle.getCallingUserId()); if (resolvedUserId != mCurrentUserId) { - return -1; + return false; } mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return 0; + return false; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return 0; + return false; } } } @@ -1666,11 +1738,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); + MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); try { connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, - interactionId, callback, flags, interrogatingPid, - interrogatingTid); - return getCompatibilityScale(resolvedWindowId); + interactionId, callback, flags, interrogatingPid, interrogatingTid, spec); + return true; } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); @@ -1678,12 +1750,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return 0; + return false; } @Override - public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, - long accessibilityNodeId, int interactionId, + public boolean findAccessibilityNodeInfoByAccessibilityId( + int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long interrogatingTid) throws RemoteException { final int resolvedWindowId; @@ -1693,18 +1765,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { .resolveCallingUserIdEnforcingPermissionsLocked( UserHandle.getCallingUserId()); if (resolvedUserId != mCurrentUserId) { - return -1; + return false; } mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return 0; + return false; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return 0; + return false; } } } @@ -1712,10 +1784,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0); final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); + MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); try { connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, - interactionId, callback, allFlags, interrogatingPid, interrogatingTid); - return getCompatibilityScale(resolvedWindowId); + interactionId, callback, allFlags, interrogatingPid, interrogatingTid, + spec); + return true; } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); @@ -1723,11 +1797,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return 0; + return false; } @Override - public float findFocus(int accessibilityWindowId, long accessibilityNodeId, + public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { @@ -1738,18 +1812,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { .resolveCallingUserIdEnforcingPermissionsLocked( UserHandle.getCallingUserId()); if (resolvedUserId != mCurrentUserId) { - return -1; + return false; } mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return 0; + return false; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return 0; + return false; } } } @@ -1757,10 +1831,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); + MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); try { connection.findFocus(accessibilityNodeId, focusType, interactionId, callback, - flags, interrogatingPid, interrogatingTid); - return getCompatibilityScale(resolvedWindowId); + flags, interrogatingPid, interrogatingTid, spec); + return true; } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityFocus()"); @@ -1768,11 +1843,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return 0; + return false; } @Override - public float focusSearch(int accessibilityWindowId, long accessibilityNodeId, + public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { @@ -1783,18 +1858,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { .resolveCallingUserIdEnforcingPermissionsLocked( UserHandle.getCallingUserId()); if (resolvedUserId != mCurrentUserId) { - return -1; + return false; } mSecurityPolicy.enforceCanRetrieveWindowContent(this); resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { - return 0; + return false; } else { connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - return 0; + return false; } } } @@ -1802,10 +1877,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); + MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId); try { connection.focusSearch(accessibilityNodeId, direction, interactionId, callback, - flags, interrogatingPid, interrogatingTid); - return getCompatibilityScale(resolvedWindowId); + flags, interrogatingPid, interrogatingTid, spec); + return true; } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()"); @@ -1813,7 +1889,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return 0; + return false; } @Override @@ -1894,6 +1970,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + @Override + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); + synchronized (mLock) { + pw.append("Service[label=" + mAccessibilityServiceInfo.getResolveInfo() + .loadLabel(mContext.getPackageManager())); + pw.append(", feedbackType" + + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType)); + pw.append(", canRetrieveScreenContent=" + mCanRetrieveScreenContent); + pw.append(", eventTypes=" + + AccessibilityEvent.eventTypeToString(mEventTypes)); + pw.append(", notificationTimeout=" + mNotificationTimeout); + pw.append("]"); + } + } + + @Override public void onServiceDisconnected(ComponentName componentName) { /* do nothing - #binderDied takes care */ } @@ -2021,6 +2114,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget(); } + public void notifyClearAccessibilityNodeInfoCache() { + mHandler.sendEmptyMessage(MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE); + } + private void notifyGestureInternal(int gestureId) { IAccessibilityServiceClient listener = mServiceInterface; if (listener != null) { @@ -2033,6 +2130,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + private void notifyClearAccessibilityNodeInfoCacheInternal() { + IAccessibilityServiceClient listener = mServiceInterface; + if (listener != null) { + try { + listener.clearAccessibilityNodeInfoCache(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error during requesting accessibility info cache" + + " to be cleared.", re); + } + } + } + private void sendDownAndUpKeyEvents(int keyCode) { final long token = Binder.clearCallingIdentity(); @@ -2115,20 +2224,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return accessibilityWindowId; } - private float getCompatibilityScale(int windowId) { + private MagnificationSpec getCompatibleMagnificationSpec(int windowId) { try { IBinder windowToken = mGlobalWindowTokens.get(windowId); + if (windowToken == null) { + windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId); + } if (windowToken != null) { - return mWindowManagerService.getWindowCompatibilityScale(windowToken); - } - windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId); - if (windowToken != null) { - return mWindowManagerService.getWindowCompatibilityScale(windowToken); + return mWindowManagerService.getCompatibleMagnificationSpecForWindow( + windowToken); } } catch (RemoteException re) { /* ignore */ } - return 1.0f; + return null; } } @@ -2313,7 +2422,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } if (!hasPermission(permission)) { throw new SecurityException("You do not have " + permission - + " required to call " + function); + + " required to call " + function + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); } } @@ -2474,7 +2584,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { UserState userState = getCurrentUserStateLocked(); handleAccessibilityEnabledSettingChangedLocked(userState); performServiceManagementLocked(userState); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); scheduleSendStateToClientsLocked(userState); } } @@ -2484,7 +2594,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (mUiAutomationService == null) { UserState userState = getCurrentUserStateLocked(); handleTouchExplorationEnabledSettingChangedLocked(userState); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); scheduleSendStateToClientsLocked(userState); } } @@ -2494,7 +2604,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (mUiAutomationService == null) { UserState userState = getCurrentUserStateLocked(); handleDisplayMagnificationEnabledSettingChangedLocked(userState); - updateInputFilterLocked(userState); + scheduleUpdateInputFilter(userState); scheduleSendStateToClientsLocked(userState); } } diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java index 482bff5..1bf2c42 100644 --- a/services/java/com/android/server/accessibility/ScreenMagnifier.java +++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java @@ -16,8 +16,6 @@ package com.android.server.accessibility; -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; @@ -25,15 +23,10 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff.Mode; import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManager.DisplayListener; +import android.graphics.Region; import android.os.AsyncTask; +import android.os.Binder; import android.os.Handler; import android.os.Message; import android.os.RemoteException; @@ -43,35 +36,23 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Property; import android.util.Slog; -import android.view.Display; -import android.view.DisplayInfo; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.Gravity; -import android.view.IDisplayContentChangeListener; +import android.view.IMagnificationCallbacks; import android.view.IWindowManager; +import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; -import android.view.Surface; import android.view.View; import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.WindowInfo; -import android.view.WindowManager; -import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; -import com.android.internal.R; import com.android.internal.os.SomeArgs; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.Locale; /** @@ -91,7 +72,7 @@ import java.util.Locale; * moving finger will be magnified to fit the screen. For example, if the * screen was not magnified and the user triple taps and holds the screen * would magnify and the viewport will follow the user's finger. When the - * finger goes up the screen will clear zoom out. If the same user interaction + * finger goes up the screen will zoom out. If the same user interaction * is performed when the screen is magnified, the viewport movement will * be the same but when the finger goes up the screen will stay magnified. * In other words, the initial magnified state is sticky. @@ -106,54 +87,55 @@ import java.util.Locale; * to pan the viewport. Note that in this mode the content is panned as * opposed to the viewport dragging mode in which the viewport is moved. * - * 5. When in a permanent magnified state the user can use three or more + * 5. When in a permanent magnified state the user can use two or more * fingers to change the magnification scale which will become the current * default magnification scale. The next time the user magnifies the same * magnification scale would be used. * * 6. The magnification scale will be persisted in settings and in the cloud. */ -public final class ScreenMagnifier implements EventStreamTransformation { +public final class ScreenMagnifier extends IMagnificationCallbacks.Stub + implements EventStreamTransformation { + + private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName(); private static final boolean DEBUG_STATE_TRANSITIONS = false; private static final boolean DEBUG_DETECTING = false; - private static final boolean DEBUG_TRANSFORMATION = false; + private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false; private static final boolean DEBUG_PANNING = false; private static final boolean DEBUG_SCALING = false; - private static final boolean DEBUG_VIEWPORT_WINDOW = false; - private static final boolean DEBUG_WINDOW_TRANSITIONS = false; - private static final boolean DEBUG_ROTATION = false; private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false; - private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName(); - private static final int STATE_DELEGATING = 1; private static final int STATE_DETECTING = 2; private static final int STATE_VIEWPORT_DRAGGING = 3; private static final int STATE_MAGNIFIED_INTERACTION = 4; private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; - private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1; - private static final float DEFAULT_WINDOW_ANIMATION_SCALE = 1.0f; - private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50; - private final IWindowManager mWindowManagerService = IWindowManager.Stub.asInterface( - ServiceManager.getService("window")); - private final WindowManager mWindowManager; - private final DisplayProvider mDisplayProvider; + private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1; + private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2; + private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3; + private static final int MESSAGE_ON_ROTATION_CHANGED = 4; - private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler(); - private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler; - private final StateViewportDraggingHandler mStateViewportDraggingHandler = - new StateViewportDraggingHandler(); + private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1; + + private static final int MY_PID = android.os.Process.myPid(); - private final Interpolator mInterpolator = new DecelerateInterpolator(2.5f); + private final Rect mTempRect = new Rect(); + private final Rect mTempRect1 = new Rect(); + private final Context mContext; + private final IWindowManager mWindowManager; private final MagnificationController mMagnificationController; - private final DisplayContentObserver mDisplayContentObserver; private final ScreenStateObserver mScreenStateObserver; - private final Viewport mViewport; + + private final DetectingStateHandler mDetectingStateHandler; + private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler; + private final StateViewportDraggingHandler mStateViewportDraggingHandler; + + private final AccessibilityManagerService mAms; private final int mTapTimeSlop = ViewConfiguration.getTapTimeout(); private final int mMultiTapTimeSlop = @@ -161,11 +143,9 @@ public final class ScreenMagnifier implements EventStreamTransformation { private final int mTapDistanceSlop; private final int mMultiTapDistanceSlop; - private final int mShortAnimationDuration; - private final int mLongAnimationDuration; - private final float mWindowAnimationScale; + private final long mLongAnimationDuration; - private final Context mContext; + private final Region mMagnifiedBounds = new Region(); private EventStreamTransformation mNext; @@ -178,36 +158,176 @@ public final class ScreenMagnifier implements EventStreamTransformation { private long mDelegatingStateDownTime; - public ScreenMagnifier(Context context) { + private boolean mUpdateMagnificationSpecOnNextBoundsChange; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: { + Region bounds = (Region) message.obj; + handleOnMagnifiedBoundsChanged(bounds); + bounds.recycle(); + } break; + case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: { + SomeArgs args = (SomeArgs) message.obj; + final int left = args.argi1; + final int top = args.argi2; + final int right = args.argi3; + final int bottom = args.argi4; + handleOnRectangleOnScreenRequested(left, top, right, bottom); + args.recycle(); + } break; + case MESSAGE_ON_USER_CONTEXT_CHANGED: { + handleOnUserContextChanged(); + } break; + case MESSAGE_ON_ROTATION_CHANGED: { + final int rotation = message.arg1; + handleOnRotationChanged(rotation); + } break; + } + } + }; + + public ScreenMagnifier(Context context, int displayId, AccessibilityManagerService service) { mContext = context; - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mWindowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService("window")); + mAms = service; - mShortAnimationDuration = context.getResources().getInteger( - com.android.internal.R.integer.config_shortAnimTime); mLongAnimationDuration = context.getResources().getInteger( com.android.internal.R.integer.config_longAnimTime); mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); - mWindowAnimationScale = Settings.Global.getFloat(context.getContentResolver(), - Settings.Global.WINDOW_ANIMATION_SCALE, DEFAULT_WINDOW_ANIMATION_SCALE); - - mMagnificationController = new MagnificationController(mShortAnimationDuration); - mDisplayProvider = new DisplayProvider(context, mWindowManager); - mViewport = new Viewport(mContext, mWindowManager, mWindowManagerService, - mDisplayProvider, mInterpolator, mShortAnimationDuration); - mDisplayContentObserver = new DisplayContentObserver(mContext, mViewport, - mMagnificationController, mWindowManagerService, mDisplayProvider, - mLongAnimationDuration, mWindowAnimationScale); - mScreenStateObserver = new ScreenStateObserver(mContext, mViewport, - mMagnificationController); + mDetectingStateHandler = new DetectingStateHandler(); + mStateViewportDraggingHandler = new StateViewportDraggingHandler(); mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler( context); + mMagnificationController = new MagnificationController(mLongAnimationDuration); + mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController); + + try { + mWindowManager.setMagnificationCallbacks(this); + } catch (RemoteException re) { + /* ignore */ + } + transitionToState(STATE_DETECTING); } @Override + public void onMagnifedBoundsChanged(Region bounds) { + Region newBounds = Region.obtain(bounds); + mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget(); + if (MY_PID != Binder.getCallingPid()) { + bounds.recycle(); + } + } + + private void handleOnMagnifiedBoundsChanged(Region bounds) { + // If there was a rotation we have to update the center of the magnified + // region since the old offset X/Y may be out of its acceptable range for + // the new display width and height. + if (mUpdateMagnificationSpecOnNextBoundsChange) { + mUpdateMagnificationSpecOnNextBoundsChange = false; + MagnificationSpec spec = mMagnificationController.getMagnificationSpec(); + Rect magnifiedFrame = mTempRect; + mMagnifiedBounds.getBounds(magnifiedFrame); + final float scale = spec.scale; + final float centerX = (-spec.offsetX + magnifiedFrame.width() / 2) / scale; + final float centerY = (-spec.offsetY + magnifiedFrame.height() / 2) / scale; + mMagnificationController.setScaleAndMagnifiedRegionCenter(scale, centerX, + centerY, false); + } + mMagnifiedBounds.set(bounds); + mAms.onMagnificationStateChanged(); + } + + @Override + public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = left; + args.argi2 = top; + args.argi3 = right; + args.argi4 = bottom; + mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget(); + } + + private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) { + Rect magnifiedFrame = mTempRect; + mMagnifiedBounds.getBounds(magnifiedFrame); + if (!magnifiedFrame.intersects(left, top, right, bottom)) { + return; + } + Rect magnifFrameInScreenCoords = mTempRect1; + getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords); + final float scrollX; + final float scrollY; + if (right - left > magnifFrameInScreenCoords.width()) { + final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); + if (direction == View.LAYOUT_DIRECTION_LTR) { + scrollX = left - magnifFrameInScreenCoords.left; + } else { + scrollX = right - magnifFrameInScreenCoords.right; + } + } else if (left < magnifFrameInScreenCoords.left) { + scrollX = left - magnifFrameInScreenCoords.left; + } else if (right > magnifFrameInScreenCoords.right) { + scrollX = right - magnifFrameInScreenCoords.right; + } else { + scrollX = 0; + } + if (bottom - top > magnifFrameInScreenCoords.height()) { + scrollY = top - magnifFrameInScreenCoords.top; + } else if (top < magnifFrameInScreenCoords.top) { + scrollY = top - magnifFrameInScreenCoords.top; + } else if (bottom > magnifFrameInScreenCoords.bottom) { + scrollY = bottom - magnifFrameInScreenCoords.bottom; + } else { + scrollY = 0; + } + final float scale = mMagnificationController.getScale(); + mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale); + } + + @Override + public void onRotationChanged(int rotation) { + mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget(); + } + + private void handleOnRotationChanged(int rotation) { + resetMagnificationIfNeeded(); + if (mMagnificationController.isMagnifying()) { + mUpdateMagnificationSpecOnNextBoundsChange = true; + } + } + + @Override + public void onUserContextChanged() { + mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED); + } + + private void handleOnUserContextChanged() { + resetMagnificationIfNeeded(); + } + + private void getMagnifiedFrameInContentCoords(Rect rect) { + MagnificationSpec spec = mMagnificationController.getMagnificationSpec(); + mMagnifiedBounds.getBounds(rect); + rect.offset((int) -spec.offsetX, (int) -spec.offsetY); + rect.scale(1.0f / spec.scale); + } + + private void resetMagnificationIfNeeded() { + if (mMagnificationController.isMagnifying() + && isScreenMagnificationAutoUpdateEnabled(mContext)) { + mMagnificationController.reset(true); + } + } + + @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { mMagnifiedContentInteractonStateHandler.onMotionEvent(event); @@ -257,12 +377,12 @@ public final class ScreenMagnifier implements EventStreamTransformation { @Override public void onDestroy() { - mMagnificationController.setScaleAndMagnifiedRegionCenter(1.0f, - 0, 0, true); - mViewport.setFrameShown(false, true); - mDisplayProvider.destroy(); - mDisplayContentObserver.destroy(); mScreenStateObserver.destroy(); + try { + mWindowManager.setMagnificationCallbacks(null); + } catch (RemoteException re) { + /* ignore */ + } } private void handleMotionEventStateDelegating(MotionEvent event, @@ -284,10 +404,10 @@ public final class ScreenMagnifier implements EventStreamTransformation { final float eventX = event.getX(); final float eventY = event.getY(); if (mMagnificationController.isMagnifying() - && mViewport.getBounds().contains((int) eventX, (int) eventY)) { + && mMagnifiedBounds.contains((int) eventX, (int) eventY)) { final float scale = mMagnificationController.getScale(); - final float scaledOffsetX = mMagnificationController.getScaledOffsetX(); - final float scaledOffsetY = mMagnificationController.getScaledOffsetY(); + final float scaledOffsetX = mMagnificationController.getOffsetX(); + final float scaledOffsetY = mMagnificationController.getOffsetY(); final int pointerCount = event.getPointerCount(); PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount); PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount); @@ -412,16 +532,11 @@ public final class ScreenMagnifier implements EventStreamTransformation { if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { return true; } - final float scale = mMagnificationController.getScale(); - final float scrollX = distanceX / scale; - final float scrollY = distanceY / scale; - final float centerX = mMagnificationController.getMagnifiedRegionCenterX() + scrollX; - final float centerY = mMagnificationController.getMagnifiedRegionCenterY() + scrollY; if (DEBUG_PANNING) { - Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX - + " scrollY: " + scrollY); + Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX + + " scrollY: " + distanceY); } - mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false); + mMagnificationController.offsetMagnifiedRegionCenter(distanceX, distanceY); return true; } @@ -485,7 +600,7 @@ public final class ScreenMagnifier implements EventStreamTransformation { } final float eventX = event.getX(); final float eventY = event.getY(); - if (mViewport.getBounds().contains((int) eventX, (int) eventY)) { + if (mMagnifiedBounds.contains((int) eventX, (int) eventY)) { if (mLastMoveOutsideMagnifiedRegion) { mLastMoveOutsideMagnifiedRegion = false; mMagnificationController.setMagnifiedRegionCenter(eventX, @@ -501,7 +616,6 @@ public final class ScreenMagnifier implements EventStreamTransformation { case MotionEvent.ACTION_UP: { if (!mTranslationEnabledBeforePan) { mMagnificationController.reset(true); - mViewport.setFrameShown(false, true); } clear(); transitionToState(STATE_DETECTING); @@ -559,7 +673,7 @@ public final class ScreenMagnifier implements EventStreamTransformation { switch (action) { case MotionEvent.ACTION_DOWN: { mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); - if (!mViewport.getBounds().contains((int) event.getX(), + if (!mMagnifiedBounds.contains((int) event.getX(), (int) event.getY())) { transitionToDelegatingStateAndClear(); return; @@ -601,7 +715,7 @@ public final class ScreenMagnifier implements EventStreamTransformation { return; } mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); - if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) { + if (!mMagnifiedBounds.contains((int) event.getX(), (int) event.getY())) { transitionToDelegatingStateAndClear(); return; } @@ -727,10 +841,8 @@ public final class ScreenMagnifier implements EventStreamTransformation { if (!mMagnificationController.isMagnifying()) { mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), up.getX(), up.getY(), true); - mViewport.setFrameShown(true, true); } else { mMagnificationController.reset(true); - mViewport.setFrameShown(false, true); } } @@ -742,7 +854,6 @@ public final class ScreenMagnifier implements EventStreamTransformation { mTranslationEnabledBeforePan = mMagnificationController.isMagnifying(); mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), down.getX(), down.getY(), true); - mViewport.setFrameShown(true, true); transitionToState(STATE_VIEWPORT_DRAGGING); } } @@ -837,443 +948,35 @@ public final class ScreenMagnifier implements EventStreamTransformation { } } - private static final class ScreenStateObserver extends BroadcastReceiver { - - private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MESSAGE_ON_SCREEN_STATE_CHANGE: { - String action = (String) message.obj; - handleOnScreenStateChange(action); - } break; - } - } - }; - - private final Context mContext; - private final Viewport mViewport; - private final MagnificationController mMagnificationController; - - public ScreenStateObserver(Context context, Viewport viewport, - MagnificationController magnificationController) { - mContext = context; - mViewport = viewport; - mMagnificationController = magnificationController; - mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - } - - public void destroy() { - mContext.unregisterReceiver(this); - } - - @Override - public void onReceive(Context context, Intent intent) { - mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE, - intent.getAction()).sendToTarget(); - } - - private void handleOnScreenStateChange(String action) { - if (action.equals(Intent.ACTION_SCREEN_OFF) - && mMagnificationController.isMagnifying() - && isScreenMagnificationAutoUpdateEnabled(mContext)) { - mMagnificationController.reset(false); - mViewport.setFrameShown(false, false); - } - } - } - - private static final class DisplayContentObserver { - - private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1; - private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 3; - private static final int MESSAGE_ON_WINDOW_TRANSITION = 4; - private static final int MESSAGE_ON_ROTATION_CHANGED = 5; - private static final int MESSAGE_ON_WINDOW_LAYERS_CHANGED = 6; - - private final Handler mHandler = new MyHandler(); - - private final Rect mTempRect = new Rect(); - - private final IDisplayContentChangeListener mDisplayContentChangeListener; - - private final Context mContext; - private final Viewport mViewport; - private final MagnificationController mMagnificationController; - private final IWindowManager mWindowManagerService; - private final DisplayProvider mDisplayProvider; - private final long mLongAnimationDuration; - private final float mWindowAnimationScale; - - public DisplayContentObserver(Context context, Viewport viewport, - MagnificationController magnificationController, - IWindowManager windowManagerService, DisplayProvider displayProvider, - long longAnimationDuration, float windowAnimationScale) { - mContext = context; - mViewport = viewport; - mMagnificationController = magnificationController; - mWindowManagerService = windowManagerService; - mDisplayProvider = displayProvider; - mLongAnimationDuration = longAnimationDuration; - mWindowAnimationScale = windowAnimationScale; - - mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() { - @Override - public void onWindowTransition(int displayId, int transition, WindowInfo info) { - mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION, - transition, 0, WindowInfo.obtain(info)).sendToTarget(); - } - - @Override - public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle, - boolean immediate) { - SomeArgs args = SomeArgs.obtain(); - args.argi1 = rectangle.left; - args.argi2 = rectangle.top; - args.argi3 = rectangle.right; - args.argi4 = rectangle.bottom; - mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0, - immediate ? 1 : 0, args).sendToTarget(); - } - - @Override - public void onRotationChanged(int rotation) throws RemoteException { - mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0) - .sendToTarget(); - } - - @Override - public void onWindowLayersChanged(int displayId) throws RemoteException { - mHandler.sendEmptyMessage(MESSAGE_ON_WINDOW_LAYERS_CHANGED); - } - }; - - try { - mWindowManagerService.addDisplayContentChangeListener( - mDisplayProvider.getDisplay().getDisplayId(), - mDisplayContentChangeListener); - } catch (RemoteException re) { - /* ignore */ - } - } - - public void destroy() { - try { - mWindowManagerService.removeDisplayContentChangeListener( - mDisplayProvider.getDisplay().getDisplayId(), - mDisplayContentChangeListener); - } catch (RemoteException re) { - /* ignore*/ - } - } - - private void handleOnRotationChanged(int rotation) { - if (DEBUG_ROTATION) { - Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation)); - } - resetMagnificationIfNeeded(); - mViewport.setFrameShown(false, false); - mViewport.rotationChanged(); - mViewport.recomputeBounds(false); - if (mMagnificationController.isMagnifying()) { - final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale); - Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME); - mHandler.sendMessageDelayed(message, delay); - } - } - - private void handleOnWindowTransition(int transition, WindowInfo info) { - if (DEBUG_WINDOW_TRANSITIONS) { - Slog.i(LOG_TAG, "Window transitioning: " - + windowTransitionToString(transition)); - } - try { - final boolean magnifying = mMagnificationController.isMagnifying(); - if (magnifying) { - switch (transition) { - case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: - case WindowManagerPolicy.TRANSIT_TASK_OPEN: - case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: - case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: - case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: - case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: { - resetMagnificationIfNeeded(); - } - } - } - if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR - || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD - || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG - || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD - || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) { - switch (transition) { - case WindowManagerPolicy.TRANSIT_ENTER: - case WindowManagerPolicy.TRANSIT_SHOW: - case WindowManagerPolicy.TRANSIT_EXIT: - case WindowManagerPolicy.TRANSIT_HIDE: { - mViewport.recomputeBounds(mMagnificationController.isMagnifying()); - } break; - } - } - switch (transition) { - case WindowManagerPolicy.TRANSIT_ENTER: - case WindowManagerPolicy.TRANSIT_SHOW: { - if (!magnifying || !isScreenMagnificationAutoUpdateEnabled(mContext)) { - break; - } - final int type = info.type; - switch (type) { - // TODO: Are these all the windows we want to make - // visible when they appear on the screen? - // Do we need to take some of them out? - case WindowManager.LayoutParams.TYPE_APPLICATION: - case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: - case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: - case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: - case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: - case WindowManager.LayoutParams.TYPE_SEARCH_BAR: - case WindowManager.LayoutParams.TYPE_PHONE: - case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: - case WindowManager.LayoutParams.TYPE_TOAST: - case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: - case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: - case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: - case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: - case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: - case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: - case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: - case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: { - Rect magnifiedRegionBounds = mMagnificationController - .getMagnifiedRegionBounds(); - Rect touchableRegion = info.touchableRegion; - if (!magnifiedRegionBounds.intersect(touchableRegion)) { - ensureRectangleInMagnifiedRegionBounds( - magnifiedRegionBounds, touchableRegion); - } - } break; - } break; - } - } - } finally { - if (info != null) { - info.recycle(); - } - } - } - - private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) { - if (!mMagnificationController.isMagnifying()) { - return; - } - Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds(); - if (magnifiedRegionBounds.contains(rectangle)) { - return; - } - ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle); - } - - private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds, - Rect rectangle) { - if (!Rect.intersects(rectangle, mViewport.getBounds())) { - return; - } - final float scrollX; - final float scrollY; - if (rectangle.width() > magnifiedRegionBounds.width()) { - final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); - if (direction == View.LAYOUT_DIRECTION_LTR) { - scrollX = rectangle.left - magnifiedRegionBounds.left; - } else { - scrollX = rectangle.right - magnifiedRegionBounds.right; - } - } else if (rectangle.left < magnifiedRegionBounds.left) { - scrollX = rectangle.left - magnifiedRegionBounds.left; - } else if (rectangle.right > magnifiedRegionBounds.right) { - scrollX = rectangle.right - magnifiedRegionBounds.right; - } else { - scrollX = 0; - } - if (rectangle.height() > magnifiedRegionBounds.height()) { - scrollY = rectangle.top - magnifiedRegionBounds.top; - } else if (rectangle.top < magnifiedRegionBounds.top) { - scrollY = rectangle.top - magnifiedRegionBounds.top; - } else if (rectangle.bottom > magnifiedRegionBounds.bottom) { - scrollY = rectangle.bottom - magnifiedRegionBounds.bottom; - } else { - scrollY = 0; - } - final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX() - + scrollX; - final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY() - + scrollY; - mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY, - true); - } - - private void resetMagnificationIfNeeded() { - if (mMagnificationController.isMagnifying() - && isScreenMagnificationAutoUpdateEnabled(mContext)) { - mMagnificationController.reset(true); - mViewport.setFrameShown(false, true); - } - } - - private String windowTransitionToString(int transition) { - switch (transition) { - case WindowManagerPolicy.TRANSIT_UNSET: { - return "TRANSIT_UNSET"; - } - case WindowManagerPolicy.TRANSIT_NONE: { - return "TRANSIT_NONE"; - } - case WindowManagerPolicy.TRANSIT_ENTER: { - return "TRANSIT_ENTER"; - } - case WindowManagerPolicy.TRANSIT_EXIT: { - return "TRANSIT_EXIT"; - } - case WindowManagerPolicy.TRANSIT_SHOW: { - return "TRANSIT_SHOW"; - } - case WindowManagerPolicy.TRANSIT_EXIT_MASK: { - return "TRANSIT_EXIT_MASK"; - } - case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: { - return "TRANSIT_PREVIEW_DONE"; - } - case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: { - return "TRANSIT_ACTIVITY_OPEN"; - } - case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: { - return "TRANSIT_ACTIVITY_CLOSE"; - } - case WindowManagerPolicy.TRANSIT_TASK_OPEN: { - return "TRANSIT_TASK_OPEN"; - } - case WindowManagerPolicy.TRANSIT_TASK_CLOSE: { - return "TRANSIT_TASK_CLOSE"; - } - case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: { - return "TRANSIT_TASK_TO_FRONT"; - } - case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: { - return "TRANSIT_TASK_TO_BACK"; - } - case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: { - return "TRANSIT_WALLPAPER_CLOSE"; - } - case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: { - return "TRANSIT_WALLPAPER_OPEN"; - } - case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: { - return "TRANSIT_WALLPAPER_INTRA_OPEN"; - } - case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: { - return "TRANSIT_WALLPAPER_INTRA_CLOSE"; - } - default: { - return "<UNKNOWN>"; - } - } - } - - private String rotationToString(int rotation) { - switch (rotation) { - case Surface.ROTATION_0: { - return "ROTATION_0"; - } - case Surface.ROTATION_90: { - return "ROATATION_90"; - } - case Surface.ROTATION_180: { - return "ROATATION_180"; - } - case Surface.ROTATION_270: { - return "ROATATION_270"; - } - default: { - throw new IllegalArgumentException("Invalid rotation: " - + rotation); - } - } - } - - private final class MyHandler extends Handler { - @Override - public void handleMessage(Message message) { - final int action = message.what; - switch (action) { - case MESSAGE_SHOW_VIEWPORT_FRAME: { - mViewport.setFrameShown(true, true); - } break; - case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: { - SomeArgs args = (SomeArgs) message.obj; - try { - mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4); - final boolean immediate = (message.arg1 == 1); - handleOnRectangleOnScreenRequested(mTempRect, immediate); - } finally { - args.recycle(); - } - } break; - case MESSAGE_ON_WINDOW_TRANSITION: { - final int transition = message.arg1; - WindowInfo info = (WindowInfo) message.obj; - handleOnWindowTransition(transition, info); - } break; - case MESSAGE_ON_ROTATION_CHANGED: { - final int rotation = message.arg1; - handleOnRotationChanged(rotation); - } break; - case MESSAGE_ON_WINDOW_LAYERS_CHANGED: { - mViewport.recomputeBounds(mMagnificationController.isMagnifying()); - } break; - default: { - throw new IllegalArgumentException("Unknown message: " + action); - } - } - } - } - } - private final class MagnificationController { - private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION = - "accessibilityTransformation"; + private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = + "magnificationSpec"; - private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec(); + private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain(); - private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec(); + private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain(); private final Rect mTempRect = new Rect(); private final ValueAnimator mTransformationAnimator; - public MagnificationController(int animationDuration) { + public MagnificationController(long animationDuration) { Property<MagnificationController, MagnificationSpec> property = Property.of(MagnificationController.class, MagnificationSpec.class, - PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION); + PROPERTY_NAME_MAGNIFICATION_SPEC); TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() { - private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec(); + private final MagnificationSpec mTempTransformationSpec = + MagnificationSpec.obtain(); @Override public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec, MagnificationSpec toSpec) { MagnificationSpec result = mTempTransformationSpec; - result.mScale = fromSpec.mScale - + (toSpec.mScale - fromSpec.mScale) * fraction; - result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX - + (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX) - * fraction; - result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY - + (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY) + result.scale = fromSpec.scale + + (toSpec.scale - fromSpec.scale) * fraction; + result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction; - result.mScaledOffsetX = fromSpec.mScaledOffsetX - + (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX) - * fraction; - result.mScaledOffsetY = fromSpec.mScaledOffsetY - + (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY) + result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction; return result; } @@ -1281,61 +984,50 @@ public final class ScreenMagnifier implements EventStreamTransformation { mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec); mTransformationAnimator.setDuration((long) (animationDuration)); - mTransformationAnimator.setInterpolator(mInterpolator); + mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f)); } public boolean isMagnifying() { - return mCurrentMagnificationSpec.mScale > 1.0f; + return mCurrentMagnificationSpec.scale > 1.0f; } public void reset(boolean animate) { if (mTransformationAnimator.isRunning()) { mTransformationAnimator.cancel(); } - mCurrentMagnificationSpec.reset(); + mCurrentMagnificationSpec.clear(); if (animate) { - animateAccessibilityTranformation(mSentMagnificationSpec, + animateMangificationSpec(mSentMagnificationSpec, mCurrentMagnificationSpec); } else { - setAccessibilityTransformation(mCurrentMagnificationSpec); + setMagnificationSpec(mCurrentMagnificationSpec); } - } - - public Rect getMagnifiedRegionBounds() { - mTempRect.set(mViewport.getBounds()); - mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX, - (int) -mCurrentMagnificationSpec.mScaledOffsetY); - mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale); - return mTempRect; + Rect bounds = mTempRect; + bounds.setEmpty(); + mAms.onMagnificationStateChanged(); } public float getScale() { - return mCurrentMagnificationSpec.mScale; - } - - public float getMagnifiedRegionCenterX() { - return mCurrentMagnificationSpec.mMagnifiedRegionCenterX; + return mCurrentMagnificationSpec.scale; } - public float getMagnifiedRegionCenterY() { - return mCurrentMagnificationSpec.mMagnifiedRegionCenterY; + public float getOffsetX() { + return mCurrentMagnificationSpec.offsetX; } - public float getScaledOffsetX() { - return mCurrentMagnificationSpec.mScaledOffsetX; - } - - public float getScaledOffsetY() { - return mCurrentMagnificationSpec.mScaledOffsetY; + public float getOffsetY() { + return mCurrentMagnificationSpec.offsetY; } public void setScale(float scale, float pivotX, float pivotY, boolean animate) { + Rect magnifiedFrame = mTempRect; + mMagnifiedBounds.getBounds(magnifiedFrame); MagnificationSpec spec = mCurrentMagnificationSpec; - final float oldScale = spec.mScale; - final float oldCenterX = spec.mMagnifiedRegionCenterX; - final float oldCenterY = spec.mMagnifiedRegionCenterY; - final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale; - final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale; + final float oldScale = spec.scale; + final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale; + final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale; + final float normPivotX = (-spec.offsetX + pivotX) / oldScale; + final float normPivotY = (-spec.offsetY + pivotY) / oldScale; final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); final float centerX = normPivotX + offsetX; @@ -1344,16 +1036,26 @@ public final class ScreenMagnifier implements EventStreamTransformation { } public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) { - setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY, + setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY, animate); } + public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) { + final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; + mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX, + getMinOffsetX()), 0); + final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; + mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY, + getMinOffsetY()), 0); + setMagnificationSpec(mCurrentMagnificationSpec); + } + public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY, boolean animate) { - if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0 - && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX, + if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0 + && Float.compare(mCurrentMagnificationSpec.offsetX, centerX) == 0 - && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY, + && Float.compare(mCurrentMagnificationSpec.offsetY, centerY) == 0) { return; } @@ -1361,501 +1063,114 @@ public final class ScreenMagnifier implements EventStreamTransformation { mTransformationAnimator.cancel(); } if (DEBUG_MAGNIFICATION_CONTROLLER) { - Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX - + " centerY: " + centerY); + Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + + " offsetY: " + centerY); } - mCurrentMagnificationSpec.initialize(scale, centerX, centerY); + updateMagnificationSpec(scale, centerX, centerY); if (animate) { - animateAccessibilityTranformation(mSentMagnificationSpec, + animateMangificationSpec(mSentMagnificationSpec, mCurrentMagnificationSpec); } else { - setAccessibilityTransformation(mCurrentMagnificationSpec); + setMagnificationSpec(mCurrentMagnificationSpec); } + mAms.onMagnificationStateChanged(); + } + + public void updateMagnificationSpec(float scale, float magnifiedCenterX, + float magnifiedCenterY) { + Rect magnifiedFrame = mTempRect; + mMagnifiedBounds.getBounds(magnifiedFrame); + mCurrentMagnificationSpec.scale = scale; + final int viewportWidth = magnifiedFrame.width(); + final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale; + mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX, + getMinOffsetX()), 0); + final int viewportHeight = magnifiedFrame.height(); + final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale; + mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY, + getMinOffsetY()), 0); } - private void animateAccessibilityTranformation(MagnificationSpec fromSpec, + private float getMinOffsetX() { + Rect magnifiedFrame = mTempRect; + mMagnifiedBounds.getBounds(magnifiedFrame); + final float viewportWidth = magnifiedFrame.width(); + return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale; + } + + private float getMinOffsetY() { + Rect magnifiedFrame = mTempRect; + mMagnifiedBounds.getBounds(magnifiedFrame); + final float viewportHeight = magnifiedFrame.height(); + return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale; + } + + private void animateMangificationSpec(MagnificationSpec fromSpec, MagnificationSpec toSpec) { mTransformationAnimator.setObjectValues(fromSpec, toSpec); mTransformationAnimator.start(); } - @SuppressWarnings("unused") - // Called from an animator. - public MagnificationSpec getAccessibilityTransformation() { + public MagnificationSpec getMagnificationSpec() { return mSentMagnificationSpec; } - public void setAccessibilityTransformation(MagnificationSpec transformation) { - if (DEBUG_TRANSFORMATION) { - Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale - + " offsetX: " + transformation.mScaledOffsetX - + " offsetY: " + transformation.mScaledOffsetY); + public void setMagnificationSpec(MagnificationSpec spec) { + if (DEBUG_SET_MAGNIFICATION_SPEC) { + Slog.i(LOG_TAG, "Sending: " + spec); } try { - mSentMagnificationSpec.updateFrom(transformation); - mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(), - transformation.mScale, transformation.mScaledOffsetX, - transformation.mScaledOffsetY); + mSentMagnificationSpec.scale = spec.scale; + mSentMagnificationSpec.offsetX = spec.offsetX; + mSentMagnificationSpec.offsetY = spec.offsetY; + mWindowManager.setMagnificationSpec( + MagnificationSpec.obtain(spec)); } catch (RemoteException re) { /* ignore */ } } - - private class MagnificationSpec { - - private static final float DEFAULT_SCALE = 1.0f; - - public float mScale = DEFAULT_SCALE; - - public float mMagnifiedRegionCenterX; - - public float mMagnifiedRegionCenterY; - - public float mScaledOffsetX; - - public float mScaledOffsetY; - - public void initialize(float scale, float magnifiedRegionCenterX, - float magnifiedRegionCenterY) { - mScale = scale; - - final int viewportWidth = mViewport.getBounds().width(); - final int viewportHeight = mViewport.getBounds().height(); - final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale; - final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale; - final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX; - final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY; - - mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX, - minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX); - mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY, - minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY); - - mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2); - mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2); - } - - public void updateFrom(MagnificationSpec other) { - mScale = other.mScale; - mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX; - mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY; - mScaledOffsetX = other.mScaledOffsetX; - mScaledOffsetY = other.mScaledOffsetY; - } - - public void reset() { - mScale = DEFAULT_SCALE; - mMagnifiedRegionCenterX = 0; - mMagnifiedRegionCenterY = 0; - mScaledOffsetX = 0; - mScaledOffsetY = 0; - } - } } - private static final class Viewport { - - private static final String PROPERTY_NAME_ALPHA = "alpha"; - - private static final String PROPERTY_NAME_BOUNDS = "bounds"; - - private static final int MIN_ALPHA = 0; - - private static final int MAX_ALPHA = 255; - - private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>(); - - private final Rect mTempRect1 = new Rect(); - private final Rect mTempRect2 = new Rect(); - private final Rect mTempRect3 = new Rect(); - - private final IWindowManager mWindowManagerService; - private final DisplayProvider mDisplayProvider; - - private final ViewportWindow mViewportFrame; - - private final ValueAnimator mResizeFrameAnimator; - - private final ValueAnimator mShowHideFrameAnimator; - - public Viewport(Context context, WindowManager windowManager, - IWindowManager windowManagerService, DisplayProvider displayInfoProvider, - Interpolator animationInterpolator, long animationDuration) { - mWindowManagerService = windowManagerService; - mDisplayProvider = displayInfoProvider; - mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider); - - mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA, - MIN_ALPHA, MAX_ALPHA); - mShowHideFrameAnimator.setInterpolator(animationInterpolator); - mShowHideFrameAnimator.setDuration(animationDuration); - mShowHideFrameAnimator.addListener(new AnimatorListener() { - @Override - public void onAnimationEnd(Animator animation) { - if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) { - mViewportFrame.hide(); - } - } - @Override - public void onAnimationStart(Animator animation) { - /* do nothing - stub */ - } - @Override - public void onAnimationCancel(Animator animation) { - /* do nothing - stub */ - } - @Override - public void onAnimationRepeat(Animator animation) { - /* do nothing - stub */ - } - }); - - Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class, - Rect.class, PROPERTY_NAME_BOUNDS); - TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() { - private final Rect mReusableResultRect = new Rect(); - @Override - public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) { - Rect result = mReusableResultRect; - result.left = (int) (fromFrame.left - + (toFrame.left - fromFrame.left) * fraction); - result.top = (int) (fromFrame.top - + (toFrame.top - fromFrame.top) * fraction); - result.right = (int) (fromFrame.right - + (toFrame.right - fromFrame.right) * fraction); - result.bottom = (int) (fromFrame.bottom - + (toFrame.bottom - fromFrame.bottom) * fraction); - return result; - } - }; - mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property, - evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds); - mResizeFrameAnimator.setDuration((long) (animationDuration)); - mResizeFrameAnimator.setInterpolator(animationInterpolator); + private final class ScreenStateObserver extends BroadcastReceiver { + private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1; - recomputeBounds(false); - } + private final Context mContext; + private final MagnificationController mMagnificationController; - private final Comparator<WindowInfo> mWindowInfoInverseComparator = - new Comparator<WindowInfo>() { + private final Handler mHandler = new Handler() { @Override - public int compare(WindowInfo lhs, WindowInfo rhs) { - if (lhs.layer != rhs.layer) { - return rhs.layer - lhs.layer; - } - if (lhs.touchableRegion.top != rhs.touchableRegion.top) { - return rhs.touchableRegion.top - lhs.touchableRegion.top; - } - if (lhs.touchableRegion.left != rhs.touchableRegion.left) { - return rhs.touchableRegion.left - lhs.touchableRegion.left; - } - if (lhs.touchableRegion.right != rhs.touchableRegion.right) { - return rhs.touchableRegion.right - lhs.touchableRegion.right; - } - if (lhs.touchableRegion.bottom != rhs.touchableRegion.bottom) { - return rhs.touchableRegion.bottom - lhs.touchableRegion.bottom; + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_ON_SCREEN_STATE_CHANGE: { + String action = (String) message.obj; + handleOnScreenStateChange(action); + } break; } - return 0; } }; - public void recomputeBounds(boolean animate) { - Rect magnifiedFrame = mTempRect1; - magnifiedFrame.set(0, 0, 0, 0); - - DisplayInfo displayInfo = mDisplayProvider.getDisplayInfo(); - - Rect availableFrame = mTempRect2; - availableFrame.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); - - ArrayList<WindowInfo> infos = mTempWindowInfoList; - infos.clear(); - int windowCount = 0; - try { - mWindowManagerService.getVisibleWindowsForDisplay( - mDisplayProvider.getDisplay().getDisplayId(), infos); - Collections.sort(infos, mWindowInfoInverseComparator); - windowCount = infos.size(); - for (int i = 0; i < windowCount; i++) { - WindowInfo info = infos.get(i); - if (info.type == WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) { - continue; - } - Rect windowFrame = mTempRect3; - windowFrame.set(info.touchableRegion); - if (isWindowMagnified(info.type)) { - magnifiedFrame.union(windowFrame); - magnifiedFrame.intersect(availableFrame); - } else { - subtract(windowFrame, magnifiedFrame); - subtract(availableFrame, windowFrame); - } - if (availableFrame.equals(magnifiedFrame)) { - break; - } - } - } catch (RemoteException re) { - /* ignore */ - } finally { - for (int i = windowCount - 1; i >= 0; i--) { - infos.remove(i).recycle(); - } - } - - final int displayWidth = mDisplayProvider.getDisplayInfo().logicalWidth; - final int displayHeight = mDisplayProvider.getDisplayInfo().logicalHeight; - magnifiedFrame.intersect(0, 0, displayWidth, displayHeight); - - resize(magnifiedFrame, animate); - } - - private boolean isWindowMagnified(int type) { - return (type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR - && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD - && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); - } - - public void rotationChanged() { - mViewportFrame.rotationChanged(); - } - - public Rect getBounds() { - return mViewportFrame.getBounds(); - } - - public void setFrameShown(boolean shown, boolean animate) { - if (mViewportFrame.isShown() == shown) { - return; - } - if (animate) { - if (mShowHideFrameAnimator.isRunning()) { - mShowHideFrameAnimator.reverse(); - } else { - if (shown) { - mViewportFrame.show(); - mShowHideFrameAnimator.start(); - } else { - mShowHideFrameAnimator.reverse(); - } - } - } else { - mShowHideFrameAnimator.cancel(); - if (shown) { - mViewportFrame.show(); - } else { - mViewportFrame.hide(); - } - } - } - - private void resize(Rect bounds, boolean animate) { - if (mViewportFrame.getBounds().equals(bounds)) { - return; - } - if (animate) { - if (mResizeFrameAnimator.isRunning()) { - mResizeFrameAnimator.cancel(); - } - mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds); - mResizeFrameAnimator.start(); - } else { - mViewportFrame.setBounds(bounds); - } - } - - private boolean subtract(Rect lhs, Rect rhs) { - if (lhs.right < rhs.left || lhs.left > rhs.right - || lhs.bottom < rhs.top || lhs.top > rhs.bottom) { - return false; - } - if (lhs.left < rhs.left) { - lhs.right = rhs.left; - } - if (lhs.top < rhs.top) { - lhs.bottom = rhs.top; - } - if (lhs.right > rhs.right) { - lhs.left = rhs.right; - } - if (lhs.bottom > rhs.bottom) { - lhs.top = rhs.bottom; - } - return true; - } - - private static final class ViewportWindow { - private static final String WINDOW_TITLE = "Magnification Overlay"; - - private final WindowManager mWindowManager; - private final DisplayProvider mDisplayProvider; - - private final ContentView mWindowContent; - private final WindowManager.LayoutParams mWindowParams; - - private final Rect mBounds = new Rect(); - private boolean mShown; - private int mAlpha; - - public ViewportWindow(Context context, WindowManager windowManager, - DisplayProvider displayProvider) { - mWindowManager = windowManager; - mDisplayProvider = displayProvider; - - ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - mWindowContent = new ContentView(context); - mWindowContent.setLayoutParams(contentParams); - mWindowContent.setBackgroundColor(R.color.transparent); - - mWindowParams = new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY); - mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - mWindowParams.setTitle(WINDOW_TITLE); - mWindowParams.gravity = Gravity.CENTER; - mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth; - mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight; - mWindowParams.format = PixelFormat.TRANSLUCENT; - } - - public boolean isShown() { - return mShown; - } - - public void show() { - if (mShown) { - return; - } - mShown = true; - mWindowManager.addView(mWindowContent, mWindowParams); - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "ViewportWindow shown."); - } - } - - public void hide() { - if (!mShown) { - return; - } - mShown = false; - mWindowManager.removeView(mWindowContent); - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "ViewportWindow hidden."); - } - } - - @SuppressWarnings("unused") - // Called reflectively from an animator. - public int getAlpha() { - return mAlpha; - } - - @SuppressWarnings("unused") - // Called reflectively from an animator. - public void setAlpha(int alpha) { - if (mAlpha == alpha) { - return; - } - mAlpha = alpha; - if (mShown) { - mWindowContent.invalidate(); - } - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha); - } - } - - public Rect getBounds() { - return mBounds; - } - - public void rotationChanged() { - mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth; - mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight; - if (mShown) { - mWindowManager.updateViewLayout(mWindowContent, mWindowParams); - } - } - - public void setBounds(Rect bounds) { - if (mBounds.equals(bounds)) { - return; - } - mBounds.set(bounds); - if (mShown) { - mWindowContent.invalidate(); - } - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds); - } - } - - private final class ContentView extends View { - private final Drawable mHighlightFrame; - - public ContentView(Context context) { - super(context); - mHighlightFrame = context.getResources().getDrawable( - R.drawable.magnified_region_frame); - } - - @Override - public void onDraw(Canvas canvas) { - canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); - mHighlightFrame.setBounds(mBounds); - mHighlightFrame.setAlpha(mAlpha); - mHighlightFrame.draw(canvas); - } - } - } - } - - private static class DisplayProvider implements DisplayListener { - private final WindowManager mWindowManager; - private final DisplayManager mDisplayManager; - private final Display mDefaultDisplay; - private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); - - public DisplayProvider(Context context, WindowManager windowManager) { - mWindowManager = windowManager; - mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); - mDefaultDisplay = mWindowManager.getDefaultDisplay(); - mDisplayManager.registerDisplayListener(this, null); - updateDisplayInfo(); - } - - public DisplayInfo getDisplayInfo() { - return mDefaultDisplayInfo; - } - - public Display getDisplay() { - return mDefaultDisplay; - } - - private void updateDisplayInfo() { - if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { - Slog.e(LOG_TAG, "Default display is not valid."); - } + public ScreenStateObserver(Context context, + MagnificationController magnificationController) { + mContext = context; + mMagnificationController = magnificationController; + mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); } public void destroy() { - mDisplayManager.unregisterDisplayListener(this); - } - - @Override - public void onDisplayAdded(int displayId) { - /* do noting */ + mContext.unregisterReceiver(this); } @Override - public void onDisplayRemoved(int displayId) { - // Having no default display + public void onReceive(Context context, Intent intent) { + mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE, + intent.getAction()).sendToTarget(); } - @Override - public void onDisplayChanged(int displayId) { - updateDisplayInfo(); + private void handleOnScreenStateChange(String action) { + if (mMagnificationController.isMagnifying() + && isScreenMagnificationAutoUpdateEnabled(mContext)) { + mMagnificationController.reset(false); + } } } } diff --git a/services/java/com/android/server/accounts/AccountAuthenticatorCache.java b/services/java/com/android/server/accounts/AccountAuthenticatorCache.java new file mode 100644 index 0000000..7552368 --- /dev/null +++ b/services/java/com/android/server/accounts/AccountAuthenticatorCache.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accounts; + +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; +import android.accounts.IAccountAuthenticator; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.RegisteredServicesCache; +import android.content.pm.XmlSerializerAndParser; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.text.TextUtils; +import android.util.AttributeSet; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; + +/** + * A cache of services that export the {@link IAccountAuthenticator} interface. This cache + * is built by interrogating the {@link PackageManager} and is updated as packages are added, + * removed and changed. The authenticators are referred to by their account type and + * are made available via the {@link RegisteredServicesCache#getServiceInfo} method. + * @hide + */ +/* package private */ class AccountAuthenticatorCache + extends RegisteredServicesCache<AuthenticatorDescription> + implements IAccountAuthenticatorCache { + private static final String TAG = "Account"; + private static final MySerializer sSerializer = new MySerializer(); + + public AccountAuthenticatorCache(Context context) { + super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT, + AccountManager.AUTHENTICATOR_META_DATA_NAME, + AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer); + } + + public AuthenticatorDescription parseServiceAttributes(Resources res, + String packageName, AttributeSet attrs) { + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AccountAuthenticator); + try { + final String accountType = + sa.getString(com.android.internal.R.styleable.AccountAuthenticator_accountType); + final int labelId = sa.getResourceId( + com.android.internal.R.styleable.AccountAuthenticator_label, 0); + final int iconId = sa.getResourceId( + com.android.internal.R.styleable.AccountAuthenticator_icon, 0); + final int smallIconId = sa.getResourceId( + com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0); + final int prefId = sa.getResourceId( + com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0); + final boolean customTokens = sa.getBoolean( + com.android.internal.R.styleable.AccountAuthenticator_customTokens, false); + if (TextUtils.isEmpty(accountType)) { + return null; + } + return new AuthenticatorDescription(accountType, packageName, labelId, iconId, + smallIconId, prefId, customTokens); + } finally { + sa.recycle(); + } + } + + private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> { + public void writeAsXml(AuthenticatorDescription item, XmlSerializer out) + throws IOException { + out.attribute(null, "type", item.type); + } + + public AuthenticatorDescription createFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type")); + } + } +} diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java new file mode 100644 index 0000000..150df9e --- /dev/null +++ b/services/java/com/android/server/accounts/AccountManagerService.java @@ -0,0 +1,2558 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accounts; + +import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountAndUser; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; +import android.accounts.GrantCredentialsPermissionActivity; +import android.accounts.IAccountAuthenticator; +import android.accounts.IAccountAuthenticatorResponse; +import android.accounts.IAccountManager; +import android.accounts.IAccountManagerResponse; +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.RegisteredServicesCache; +import android.content.pm.RegisteredServicesCacheListener; +import android.content.pm.UserInfo; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Binder; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.util.IndentingPrintWriter; +import com.google.android.collect.Lists; +import com.google.android.collect.Sets; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A system service that provides account, password, and authtoken management for all + * accounts on the device. Some of these calls are implemented with the help of the corresponding + * {@link IAccountAuthenticator} services. This service is not accessed by users directly, + * instead one uses an instance of {@link AccountManager}, which can be accessed as follows: + * AccountManager accountManager = AccountManager.get(context); + * @hide + */ +public class AccountManagerService + extends IAccountManager.Stub + implements RegisteredServicesCacheListener<AuthenticatorDescription> { + private static final String TAG = "AccountManagerService"; + + private static final int TIMEOUT_DELAY_MS = 1000 * 60; + private static final String DATABASE_NAME = "accounts.db"; + private static final int DATABASE_VERSION = 4; + + private final Context mContext; + + private final PackageManager mPackageManager; + private UserManager mUserManager; + + private HandlerThread mMessageThread; + private final MessageHandler mMessageHandler; + + // Messages that can be sent on mHandler + private static final int MESSAGE_TIMED_OUT = 3; + + private final IAccountAuthenticatorCache mAuthenticatorCache; + + private static final String TABLE_ACCOUNTS = "accounts"; + private static final String ACCOUNTS_ID = "_id"; + private static final String ACCOUNTS_NAME = "name"; + private static final String ACCOUNTS_TYPE = "type"; + private static final String ACCOUNTS_TYPE_COUNT = "count(type)"; + private static final String ACCOUNTS_PASSWORD = "password"; + + private static final String TABLE_AUTHTOKENS = "authtokens"; + private static final String AUTHTOKENS_ID = "_id"; + private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id"; + private static final String AUTHTOKENS_TYPE = "type"; + private static final String AUTHTOKENS_AUTHTOKEN = "authtoken"; + + private static final String TABLE_GRANTS = "grants"; + private static final String GRANTS_ACCOUNTS_ID = "accounts_id"; + private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type"; + private static final String GRANTS_GRANTEE_UID = "uid"; + + private static final String TABLE_EXTRAS = "extras"; + private static final String EXTRAS_ID = "_id"; + private static final String EXTRAS_ACCOUNTS_ID = "accounts_id"; + private static final String EXTRAS_KEY = "key"; + private static final String EXTRAS_VALUE = "value"; + + private static final String TABLE_META = "meta"; + private static final String META_KEY = "key"; + private static final String META_VALUE = "value"; + + private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION = + new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT}; + private static final Intent ACCOUNTS_CHANGED_INTENT; + + private static final String COUNT_OF_MATCHING_GRANTS = "" + + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS + + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID + + " AND " + GRANTS_GRANTEE_UID + "=?" + + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?" + + " AND " + ACCOUNTS_NAME + "=?" + + " AND " + ACCOUNTS_TYPE + "=?"; + + private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT = + AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; + private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE, + AUTHTOKENS_AUTHTOKEN}; + + private static final String SELECTION_USERDATA_BY_ACCOUNT = + EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; + private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE}; + + private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>(); + private final AtomicInteger mNotificationIds = new AtomicInteger(1); + + static class UserAccounts { + private final int userId; + private final DatabaseHelper openHelper; + private final HashMap<Pair<Pair<Account, String>, Integer>, Integer> + credentialsPermissionNotificationIds = + new HashMap<Pair<Pair<Account, String>, Integer>, Integer>(); + private final HashMap<Account, Integer> signinRequiredNotificationIds = + new HashMap<Account, Integer>(); + private final Object cacheLock = new Object(); + /** protected by the {@link #cacheLock} */ + private final HashMap<String, Account[]> accountCache = + new LinkedHashMap<String, Account[]>(); + /** protected by the {@link #cacheLock} */ + private HashMap<Account, HashMap<String, String>> userDataCache = + new HashMap<Account, HashMap<String, String>>(); + /** protected by the {@link #cacheLock} */ + private HashMap<Account, HashMap<String, String>> authTokenCache = + new HashMap<Account, HashMap<String, String>>(); + + UserAccounts(Context context, int userId) { + this.userId = userId; + synchronized (cacheLock) { + openHelper = new DatabaseHelper(context, userId); + } + } + } + + private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>(); + + private static AtomicReference<AccountManagerService> sThis = + new AtomicReference<AccountManagerService>(); + private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; + + static { + ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); + ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + } + + + /** + * This should only be called by system code. One should only call this after the service + * has started. + * @return a reference to the AccountManagerService instance + * @hide + */ + public static AccountManagerService getSingleton() { + return sThis.get(); + } + + public AccountManagerService(Context context) { + this(context, context.getPackageManager(), new AccountAuthenticatorCache(context)); + } + + public AccountManagerService(Context context, PackageManager packageManager, + IAccountAuthenticatorCache authenticatorCache) { + mContext = context; + mPackageManager = packageManager; + + mMessageThread = new HandlerThread("AccountManagerService"); + mMessageThread.start(); + mMessageHandler = new MessageHandler(mMessageThread.getLooper()); + + mAuthenticatorCache = authenticatorCache; + mAuthenticatorCache.setListener(this, null /* Handler */); + + sThis.set(this); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context1, Intent intent) { + purgeOldGrantsAll(); + } + }, intentFilter); + + IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onUserRemoved(intent); + } + }, userFilter); + } + + public void systemReady() { + } + + private UserManager getUserManager() { + if (mUserManager == null) { + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + return mUserManager; + } + + private UserAccounts initUser(int userId) { + synchronized (mUsers) { + UserAccounts accounts = mUsers.get(userId); + if (accounts == null) { + accounts = new UserAccounts(mContext, userId); + mUsers.append(userId, accounts); + purgeOldGrants(accounts); + validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */); + } + return accounts; + } + } + + private void purgeOldGrantsAll() { + synchronized (mUsers) { + for (int i = 0; i < mUsers.size(); i++) { + purgeOldGrants(mUsers.valueAt(i)); + } + } + } + + private void purgeOldGrants(UserAccounts accounts) { + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final Cursor cursor = db.query(TABLE_GRANTS, + new String[]{GRANTS_GRANTEE_UID}, + null, null, GRANTS_GRANTEE_UID, null, null); + try { + while (cursor.moveToNext()) { + final int uid = cursor.getInt(0); + final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null; + if (packageExists) { + continue; + } + Log.d(TAG, "deleting grants for UID " + uid + + " because its package is no longer installed"); + db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?", + new String[]{Integer.toString(uid)}); + } + } finally { + cursor.close(); + } + } + } + + /** + * Validate internal set of accounts against installed authenticators for + * given user. Clears cached authenticators before validating. + */ + public void validateAccounts(int userId) { + final UserAccounts accounts = getUserAccounts(userId); + + // Invalidate user-specific cache to make sure we catch any + // removed authenticators. + validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */); + } + + /** + * Validate internal set of accounts against installed authenticators for + * given user. Clear cached authenticators before validating when requested. + */ + private void validateAccountsInternal( + UserAccounts accounts, boolean invalidateAuthenticatorCache) { + if (invalidateAuthenticatorCache) { + mAuthenticatorCache.invalidateCache(accounts.userId); + } + + final HashSet<AuthenticatorDescription> knownAuth = Sets.newHashSet(); + for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> service : + mAuthenticatorCache.getAllServices(accounts.userId)) { + knownAuth.add(service.type); + } + + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + boolean accountDeleted = false; + Cursor cursor = db.query(TABLE_ACCOUNTS, + new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME}, + null, null, null, null, null); + try { + accounts.accountCache.clear(); + final HashMap<String, ArrayList<String>> accountNamesByType = + new LinkedHashMap<String, ArrayList<String>>(); + while (cursor.moveToNext()) { + final long accountId = cursor.getLong(0); + final String accountType = cursor.getString(1); + final String accountName = cursor.getString(2); + + if (!knownAuth.contains(AuthenticatorDescription.newKey(accountType))) { + Slog.w(TAG, "deleting account " + accountName + " because type " + + accountType + " no longer has a registered authenticator"); + db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); + accountDeleted = true; + final Account account = new Account(accountName, accountType); + accounts.userDataCache.remove(account); + accounts.authTokenCache.remove(account); + } else { + ArrayList<String> accountNames = accountNamesByType.get(accountType); + if (accountNames == null) { + accountNames = new ArrayList<String>(); + accountNamesByType.put(accountType, accountNames); + } + accountNames.add(accountName); + } + } + for (Map.Entry<String, ArrayList<String>> cur + : accountNamesByType.entrySet()) { + final String accountType = cur.getKey(); + final ArrayList<String> accountNames = cur.getValue(); + final Account[] accountsForType = new Account[accountNames.size()]; + int i = 0; + for (String accountName : accountNames) { + accountsForType[i] = new Account(accountName, accountType); + ++i; + } + accounts.accountCache.put(accountType, accountsForType); + } + } finally { + cursor.close(); + if (accountDeleted) { + sendAccountsChangedBroadcast(accounts.userId); + } + } + } + } + + private UserAccounts getUserAccountsForCaller() { + return getUserAccounts(UserHandle.getCallingUserId()); + } + + protected UserAccounts getUserAccounts(int userId) { + synchronized (mUsers) { + UserAccounts accounts = mUsers.get(userId); + if (accounts == null) { + accounts = initUser(userId); + mUsers.append(userId, accounts); + } + return accounts; + } + } + + private void onUserRemoved(Intent intent) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId < 1) return; + + UserAccounts accounts; + synchronized (mUsers) { + accounts = mUsers.get(userId); + mUsers.remove(userId); + } + if (accounts == null) { + File dbFile = new File(getDatabaseName(userId)); + dbFile.delete(); + return; + } + + synchronized (accounts.cacheLock) { + accounts.openHelper.close(); + File dbFile = new File(getDatabaseName(userId)); + dbFile.delete(); + } + } + + @Override + public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) { + Slog.d(TAG, "onServiceChanged() for userId " + userId); + validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */); + } + + public String getPassword(Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getPassword: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + return readPasswordInternal(accounts, account); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private String readPasswordInternal(UserAccounts accounts, Account account) { + if (account == null) { + return null; + } + + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD}, + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.name, account.type}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getString(0); + } + return null; + } finally { + cursor.close(); + } + } + } + + public String getUserData(Account account, String key) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getUserData: " + account + + ", key " + key + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + if (key == null) throw new IllegalArgumentException("key is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + return readUserDataInternal(accounts, account, key); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public AuthenticatorDescription[] getAuthenticatorTypes() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAuthenticatorTypes: " + + "caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + final int userId = UserHandle.getCallingUserId(); + final long identityToken = clearCallingIdentity(); + try { + Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>> + authenticatorCollection = mAuthenticatorCache.getAllServices(userId); + AuthenticatorDescription[] types = + new AuthenticatorDescription[authenticatorCollection.size()]; + int i = 0; + for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator + : authenticatorCollection) { + types[i] = authenticator.type; + i++; + } + return types; + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean addAccount(Account account, String password, Bundle extras) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addAccount: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + + UserAccounts accounts = getUserAccountsForCaller(); + // fails if the account already exists + long identityToken = clearCallingIdentity(); + try { + return addAccountInternal(accounts, account, password, extras); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private boolean addAccountInternal(UserAccounts accounts, Account account, String password, + Bundle extras) { + if (account == null) { + return false; + } + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long numMatches = DatabaseUtils.longForQuery(db, + "select count(*) from " + TABLE_ACCOUNTS + + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.name, account.type}); + if (numMatches > 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping since the account already exists"); + return false; + } + ContentValues values = new ContentValues(); + values.put(ACCOUNTS_NAME, account.name); + values.put(ACCOUNTS_TYPE, account.type); + values.put(ACCOUNTS_PASSWORD, password); + long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); + if (accountId < 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping the DB insert failed"); + return false; + } + if (extras != null) { + for (String key : extras.keySet()) { + final String value = extras.getString(key); + if (insertExtraLocked(db, accountId, key, value) < 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping since insertExtra failed for key " + key); + return false; + } + } + } + db.setTransactionSuccessful(); + insertAccountIntoCacheLocked(accounts, account); + } finally { + db.endTransaction(); + } + sendAccountsChangedBroadcast(accounts.userId); + return true; + } + } + + private long insertExtraLocked(SQLiteDatabase db, long accountId, String key, String value) { + ContentValues values = new ContentValues(); + values.put(EXTRAS_KEY, key); + values.put(EXTRAS_ACCOUNTS_ID, accountId); + values.put(EXTRAS_VALUE, value); + return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values); + } + + public void hasFeatures(IAccountManagerResponse response, + Account account, String[] features) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "hasFeatures: " + account + + ", response " + response + + ", features " + stringArrayToString(features) + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + if (features == null) throw new IllegalArgumentException("features is null"); + checkReadAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + new TestFeaturesSession(accounts, response, account, features).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private class TestFeaturesSession extends Session { + private final String[] mFeatures; + private final Account mAccount; + + public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response, + Account account, String[] features) { + super(accounts, response, account.type, false /* expectActivityLaunch */, + true /* stripAuthTokenFromResult */); + mFeatures = features; + mAccount = account; + } + + public void run() throws RemoteException { + try { + mAuthenticator.hasFeatures(this, mAccount, mFeatures); + } catch (RemoteException e) { + onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception"); + } + } + + public void onResult(Bundle result) { + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + try { + if (result == null) { + response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle"); + return; + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } + final Bundle newResult = new Bundle(); + newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, + result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)); + response.onResult(newResult); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + } + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", hasFeatures" + + ", " + mAccount + + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); + } + } + + public void removeAccount(IAccountManagerResponse response, Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "removeAccount: " + account + + ", response " + response + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + checkManageAccountsPermission(); + UserHandle user = Binder.getCallingUserHandle(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + + cancelNotification(getSigninRequiredNotificationId(accounts, account), user); + synchronized(accounts.credentialsPermissionNotificationIds) { + for (Pair<Pair<Account, String>, Integer> pair: + accounts.credentialsPermissionNotificationIds.keySet()) { + if (account.equals(pair.first.first)) { + int id = accounts.credentialsPermissionNotificationIds.get(pair); + cancelNotification(id, user); + } + } + } + + try { + new RemoveAccountSession(accounts, response, account).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private class RemoveAccountSession extends Session { + final Account mAccount; + public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, + Account account) { + super(accounts, response, account.type, false /* expectActivityLaunch */, + true /* stripAuthTokenFromResult */); + mAccount = account; + } + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", removeAccount" + + ", account " + mAccount; + } + + public void run() throws RemoteException { + mAuthenticator.getAccountRemovalAllowed(this, mAccount); + } + + public void onResult(Bundle result) { + if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT) + && !result.containsKey(AccountManager.KEY_INTENT)) { + final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + if (removalAllowed) { + removeAccountInternal(mAccounts, mAccount); + } + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } + Bundle result2 = new Bundle(); + result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed); + try { + response.onResult(result2); + } catch (RemoteException e) { + // ignore + } + } + } + super.onResult(result); + } + } + + /* For testing */ + protected void removeAccountInternal(Account account) { + removeAccountInternal(getUserAccountsForCaller(), account); + } + + private void removeAccountInternal(UserAccounts accounts, Account account) { + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.name, account.type}); + removeAccountFromCacheLocked(accounts, account); + sendAccountsChangedBroadcast(accounts.userId); + } + } + + public void invalidateAuthToken(String accountType, String authToken) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "invalidateAuthToken: accountType " + accountType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + if (authToken == null) throw new IllegalArgumentException("authToken is null"); + checkManageAccountsOrUseCredentialsPermissions(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + invalidateAuthTokenLocked(accounts, db, accountType, authToken); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db, + String accountType, String authToken) { + if (authToken == null || accountType == null) { + return; + } + Cursor cursor = db.rawQuery( + "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID + + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME + + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE + + " FROM " + TABLE_ACCOUNTS + + " JOIN " + TABLE_AUTHTOKENS + + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID + + " = " + AUTHTOKENS_ACCOUNTS_ID + + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND " + + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?", + new String[]{authToken, accountType}); + try { + while (cursor.moveToNext()) { + long authTokenId = cursor.getLong(0); + String accountName = cursor.getString(1); + String authTokenType = cursor.getString(2); + db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); + writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType), + authTokenType, null); + } + } finally { + cursor.close(); + } + } + + private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type, + String authToken) { + if (account == null || type == null) { + return false; + } + cancelNotification(getSigninRequiredNotificationId(accounts, account), + new UserHandle(accounts.userId)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountIdLocked(db, account); + if (accountId < 0) { + return false; + } + db.delete(TABLE_AUTHTOKENS, + AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?", + new String[]{type}); + ContentValues values = new ContentValues(); + values.put(AUTHTOKENS_ACCOUNTS_ID, accountId); + values.put(AUTHTOKENS_TYPE, type); + values.put(AUTHTOKENS_AUTHTOKEN, authToken); + if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) { + db.setTransactionSuccessful(); + writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken); + return true; + } + return false; + } finally { + db.endTransaction(); + } + } + } + + public String peekAuthToken(Account account, String authTokenType) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "peekAuthToken: " + account + + ", authTokenType " + authTokenType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + return readAuthTokenInternal(accounts, account, authTokenType); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void setAuthToken(Account account, String authTokenType, String authToken) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setAuthToken: " + account + + ", authTokenType " + authTokenType + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + saveAuthTokenToDatabase(accounts, account, authTokenType, authToken); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void setPassword(Account account, String password) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setAuthToken: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + setPasswordInternal(accounts, account, password); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void setPasswordInternal(UserAccounts accounts, Account account, String password) { + if (account == null) { + return; + } + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + final ContentValues values = new ContentValues(); + values.put(ACCOUNTS_PASSWORD, password); + final long accountId = getAccountIdLocked(db, account); + if (accountId >= 0) { + final String[] argsAccountId = {String.valueOf(accountId)}; + db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); + db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId); + accounts.authTokenCache.remove(account); + db.setTransactionSuccessful(); + } + } finally { + db.endTransaction(); + } + sendAccountsChangedBroadcast(accounts.userId); + } + } + + private void sendAccountsChangedBroadcast(int userId) { + Log.i(TAG, "the accounts changed, sending broadcast of " + + ACCOUNTS_CHANGED_INTENT.getAction()); + mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId)); + } + + public void clearPassword(Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "clearPassword: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + setPasswordInternal(accounts, account, null); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void setUserData(Account account, String key, String value) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setUserData: " + account + + ", key " + key + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (key == null) throw new IllegalArgumentException("key is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + setUserdataInternal(accounts, account, key, value); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void setUserdataInternal(UserAccounts accounts, Account account, String key, + String value) { + if (account == null || key == null) { + return; + } + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountIdLocked(db, account); + if (accountId < 0) { + return; + } + long extrasId = getExtrasIdLocked(db, accountId, key); + if (extrasId < 0 ) { + extrasId = insertExtraLocked(db, accountId, key, value); + if (extrasId < 0) { + return; + } + } else { + ContentValues values = new ContentValues(); + values.put(EXTRAS_VALUE, value); + if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) { + return; + } + + } + writeUserDataIntoCacheLocked(accounts, db, account, key, value); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + } + + private void onResult(IAccountManagerResponse response, Bundle result) { + if (result == null) { + Log.e(TAG, "the result is unexpectedly null", new Exception()); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } + try { + response.onResult(result); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote + // exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + + public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType, + final String authTokenType) + throws RemoteException { + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + + final int callingUid = getCallingUid(); + clearCallingIdentity(); + if (callingUid != android.os.Process.SYSTEM_UID) { + throw new SecurityException("can only call from system"); + } + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, accountType, false, + false /* stripAuthTokenFromResult */) { + protected String toDebugString(long now) { + return super.toDebugString(now) + ", getAuthTokenLabel" + + ", " + accountType + + ", authTokenType " + authTokenType; + } + + public void run() throws RemoteException { + mAuthenticator.getAuthTokenLabel(this, authTokenType); + } + + public void onResult(Bundle result) { + if (result != null) { + String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL); + Bundle bundle = new Bundle(); + bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label); + super.onResult(bundle); + return; + } else { + super.onResult(result); + } + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void getAuthToken(IAccountManagerResponse response, final Account account, + final String authTokenType, final boolean notifyOnAuthFailure, + final boolean expectActivityLaunch, Bundle loginOptionsIn) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAuthToken: " + account + + ", response " + response + + ", authTokenType " + authTokenType + + ", notifyOnAuthFailure " + notifyOnAuthFailure + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + checkBinderPermission(Manifest.permission.USE_CREDENTIALS); + final UserAccounts accounts = getUserAccountsForCaller(); + final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo; + authenticatorInfo = mAuthenticatorCache.getServiceInfo( + AuthenticatorDescription.newKey(account.type), accounts.userId); + final boolean customTokens = + authenticatorInfo != null && authenticatorInfo.type.customTokens; + + // skip the check if customTokens + final int callerUid = Binder.getCallingUid(); + final boolean permissionGranted = customTokens || + permissionIsGranted(account, authTokenType, callerUid); + + final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() : + loginOptionsIn; + // let authenticator know the identity of the caller + loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid); + loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid()); + if (notifyOnAuthFailure) { + loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true); + } + + long identityToken = clearCallingIdentity(); + try { + // if the caller has permission, do the peek. otherwise go the more expensive + // route of starting a Session + if (!customTokens && permissionGranted) { + String authToken = readAuthTokenInternal(accounts, account, authTokenType); + if (authToken != null) { + Bundle result = new Bundle(); + result.putString(AccountManager.KEY_AUTHTOKEN, authToken); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + onResult(response, result); + return; + } + } + + new Session(accounts, response, account.type, expectActivityLaunch, + false /* stripAuthTokenFromResult */) { + protected String toDebugString(long now) { + if (loginOptions != null) loginOptions.keySet(); + return super.toDebugString(now) + ", getAuthToken" + + ", " + account + + ", authTokenType " + authTokenType + + ", loginOptions " + loginOptions + + ", notifyOnAuthFailure " + notifyOnAuthFailure; + } + + public void run() throws RemoteException { + // If the caller doesn't have permission then create and return the + // "grant permission" intent instead of the "getAuthToken" intent. + if (!permissionGranted) { + mAuthenticator.getAuthTokenLabel(this, authTokenType); + } else { + mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions); + } + } + + public void onResult(Bundle result) { + if (result != null) { + if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) { + Intent intent = newGrantCredentialsPermissionIntent(account, callerUid, + new AccountAuthenticatorResponse(this), + authTokenType, + result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL)); + Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + onResult(bundle); + return; + } + String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); + if (authToken != null) { + String name = result.getString(AccountManager.KEY_ACCOUNT_NAME); + String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE); + if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) { + onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + "the type and name should not be empty"); + return; + } + if (!customTokens) { + saveAuthTokenToDatabase(mAccounts, new Account(name, type), + authTokenType, authToken); + } + } + + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); + if (intent != null && notifyOnAuthFailure && !customTokens) { + doNotification(mAccounts, + account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE), + intent, accounts.userId); + } + } + super.onResult(result); + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void createNoCredentialsPermissionNotification(Account account, Intent intent, + int userId) { + int uid = intent.getIntExtra( + GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1); + String authTokenType = intent.getStringExtra( + GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE); + String authTokenLabel = intent.getStringExtra( + GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL); + + Notification n = new Notification(android.R.drawable.stat_sys_warning, null, + 0 /* when */); + final String titleAndSubtitle = + mContext.getString(R.string.permission_request_notification_with_subtitle, + account.name); + final int index = titleAndSubtitle.indexOf('\n'); + String title = titleAndSubtitle; + String subtitle = ""; + if (index > 0) { + title = titleAndSubtitle.substring(0, index); + subtitle = titleAndSubtitle.substring(index + 1); + } + UserHandle user = new UserHandle(userId); + n.setLatestEventInfo(mContext, title, subtitle, + PendingIntent.getActivityAsUser(mContext, 0, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, user)); + installNotification(getCredentialPermissionNotificationId( + account, authTokenType, uid), n, user); + } + + private Intent newGrantCredentialsPermissionIntent(Account account, int uid, + AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) { + + Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class); + // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag. + // Since it was set in Eclair+ we can't change it without breaking apps using + // the intent from a non-Activity context. + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory( + String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid))); + + intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account); + intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response); + intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid); + + return intent; + } + + private Integer getCredentialPermissionNotificationId(Account account, String authTokenType, + int uid) { + Integer id; + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid)); + synchronized (accounts.credentialsPermissionNotificationIds) { + final Pair<Pair<Account, String>, Integer> key = + new Pair<Pair<Account, String>, Integer>( + new Pair<Account, String>(account, authTokenType), uid); + id = accounts.credentialsPermissionNotificationIds.get(key); + if (id == null) { + id = mNotificationIds.incrementAndGet(); + accounts.credentialsPermissionNotificationIds.put(key, id); + } + } + return id; + } + + private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) { + Integer id; + synchronized (accounts.signinRequiredNotificationIds) { + id = accounts.signinRequiredNotificationIds.get(account); + if (id == null) { + id = mNotificationIds.incrementAndGet(); + accounts.signinRequiredNotificationIds.put(account, id); + } + } + return id; + } + + public void addAcount(final IAccountManagerResponse response, final String accountType, + final String authTokenType, final String[] requiredFeatures, + final boolean expectActivityLaunch, final Bundle optionsIn) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addAccount: accountType " + accountType + + ", response " + response + + ", authTokenType " + authTokenType + + ", requiredFeatures " + stringArrayToString(requiredFeatures) + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + checkManageAccountsPermission(); + + UserAccounts accounts = getUserAccountsForCaller(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn; + options.putInt(AccountManager.KEY_CALLER_UID, uid); + options.putInt(AccountManager.KEY_CALLER_PID, pid); + + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, accountType, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + public void run() throws RemoteException { + mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, + options); + } + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", addAccount" + + ", accountType " + accountType + + ", requiredFeatures " + + (requiredFeatures != null + ? TextUtils.join(",", requiredFeatures) + : null); + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + @Override + public void confirmCredentialsAsUser(IAccountManagerResponse response, + final Account account, final Bundle options, final boolean expectActivityLaunch, + int userId) { + // Only allow the system process to read accounts of other users + if (userId != UserHandle.getCallingUserId() + && Binder.getCallingUid() != android.os.Process.myUid()) { + throw new SecurityException("User " + UserHandle.getCallingUserId() + + " trying to confirm account credentials for " + userId); + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "confirmCredentials: " + account + + ", response " + response + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccounts(userId); + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, account.type, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + public void run() throws RemoteException { + mAuthenticator.confirmCredentials(this, account, options); + } + protected String toDebugString(long now) { + return super.toDebugString(now) + ", confirmCredentials" + + ", " + account; + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void updateCredentials(IAccountManagerResponse response, final Account account, + final String authTokenType, final boolean expectActivityLaunch, + final Bundle loginOptions) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "updateCredentials: " + account + + ", response " + response + + ", authTokenType " + authTokenType + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, account.type, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + public void run() throws RemoteException { + mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); + } + protected String toDebugString(long now) { + if (loginOptions != null) loginOptions.keySet(); + return super.toDebugString(now) + ", updateCredentials" + + ", " + account + + ", authTokenType " + authTokenType + + ", loginOptions " + loginOptions; + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void editProperties(IAccountManagerResponse response, final String accountType, + final boolean expectActivityLaunch) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "editProperties: accountType " + accountType + + ", response " + response + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, accountType, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + public void run() throws RemoteException { + mAuthenticator.editProperties(this, mAccountType); + } + protected String toDebugString(long now) { + return super.toDebugString(now) + ", editProperties" + + ", accountType " + accountType; + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private class GetAccountsByTypeAndFeatureSession extends Session { + private final String[] mFeatures; + private volatile Account[] mAccountsOfType = null; + private volatile ArrayList<Account> mAccountsWithFeatures = null; + private volatile int mCurrentAccount = 0; + + public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, + IAccountManagerResponse response, String type, String[] features) { + super(accounts, response, type, false /* expectActivityLaunch */, + true /* stripAuthTokenFromResult */); + mFeatures = features; + } + + public void run() throws RemoteException { + synchronized (mAccounts.cacheLock) { + mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType); + } + // check whether each account matches the requested features + mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length); + mCurrentAccount = 0; + + checkAccount(); + } + + public void checkAccount() { + if (mCurrentAccount >= mAccountsOfType.length) { + sendResult(); + return; + } + + final IAccountAuthenticator accountAuthenticator = mAuthenticator; + if (accountAuthenticator == null) { + // It is possible that the authenticator has died, which is indicated by + // mAuthenticator being set to null. If this happens then just abort. + // There is no need to send back a result or error in this case since + // that already happened when mAuthenticator was cleared. + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "checkAccount: aborting session since we are no longer" + + " connected to the authenticator, " + toDebugString()); + } + return; + } + try { + accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures); + } catch (RemoteException e) { + onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception"); + } + } + + public void onResult(Bundle result) { + mNumResults++; + if (result == null) { + onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle"); + return; + } + if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { + mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]); + } + mCurrentAccount++; + checkAccount(); + } + + public void sendResult() { + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + try { + Account[] accounts = new Account[mAccountsWithFeatures.size()]; + for (int i = 0; i < accounts.length; i++) { + accounts[i] = mAccountsWithFeatures.get(i); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + + response); + } + Bundle result = new Bundle(); + result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); + response.onResult(result); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + } + + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", getAccountsByTypeAndFeatures" + + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); + } + } + + /** + * Returns the accounts for a specific user + * @hide + */ + public Account[] getAccounts(int userId) { + checkReadAccountsPermission(); + UserAccounts accounts = getUserAccounts(userId); + long identityToken = clearCallingIdentity(); + try { + synchronized (accounts.cacheLock) { + return getAccountsFromCacheLocked(accounts, null); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** + * Returns accounts for all running users. + * + * @hide + */ + public AccountAndUser[] getRunningAccounts() { + final int[] runningUserIds; + try { + runningUserIds = ActivityManagerNative.getDefault().getRunningUserIds(); + } catch (RemoteException e) { + // Running in system_server; should never happen + throw new RuntimeException(e); + } + return getAccounts(runningUserIds); + } + + /** {@hide} */ + public AccountAndUser[] getAllAccounts() { + final List<UserInfo> users = getUserManager().getUsers(); + final int[] userIds = new int[users.size()]; + for (int i = 0; i < userIds.length; i++) { + userIds[i] = users.get(i).id; + } + return getAccounts(userIds); + } + + private AccountAndUser[] getAccounts(int[] userIds) { + final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList(); + synchronized (mUsers) { + for (int userId : userIds) { + UserAccounts userAccounts = getUserAccounts(userId); + if (userAccounts == null) continue; + synchronized (userAccounts.cacheLock) { + Account[] accounts = getAccountsFromCacheLocked(userAccounts, null); + for (int a = 0; a < accounts.length; a++) { + runningAccounts.add(new AccountAndUser(accounts[a], userId)); + } + } + } + } + + AccountAndUser[] accountsArray = new AccountAndUser[runningAccounts.size()]; + return runningAccounts.toArray(accountsArray); + } + + @Override + public Account[] getAccountsAsUser(String type, int userId) { + // Only allow the system process to read accounts of other users + if (userId != UserHandle.getCallingUserId() + && Binder.getCallingUid() != android.os.Process.myUid()) { + throw new SecurityException("User " + UserHandle.getCallingUserId() + + " trying to get account for " + userId); + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAccounts: accountType " + type + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + checkReadAccountsPermission(); + UserAccounts accounts = getUserAccounts(userId); + long identityToken = clearCallingIdentity(); + try { + synchronized (accounts.cacheLock) { + return getAccountsFromCacheLocked(accounts, type); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + @Override + public Account[] getAccounts(String type) { + return getAccountsAsUser(type, UserHandle.getCallingUserId()); + } + + public void getAccountsByFeatures(IAccountManagerResponse response, + String type, String[] features) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAccounts: accountType " + type + + ", response " + response + + ", features " + stringArrayToString(features) + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (type == null) throw new IllegalArgumentException("accountType is null"); + checkReadAccountsPermission(); + UserAccounts userAccounts = getUserAccountsForCaller(); + long identityToken = clearCallingIdentity(); + try { + if (features == null || features.length == 0) { + Account[] accounts; + synchronized (userAccounts.cacheLock) { + accounts = getAccountsFromCacheLocked(userAccounts, type); + } + Bundle result = new Bundle(); + result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); + onResult(response, result); + return; + } + new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private long getAccountIdLocked(SQLiteDatabase db, Account account) { + Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID}, + "name=? AND type=?", new String[]{account.name, account.type}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getLong(0); + } + return -1; + } finally { + cursor.close(); + } + } + + private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) { + Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID}, + EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?", + new String[]{key}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getLong(0); + } + return -1; + } finally { + cursor.close(); + } + } + + private abstract class Session extends IAccountAuthenticatorResponse.Stub + implements IBinder.DeathRecipient, ServiceConnection { + IAccountManagerResponse mResponse; + final String mAccountType; + final boolean mExpectActivityLaunch; + final long mCreationTime; + + public int mNumResults = 0; + private int mNumRequestContinued = 0; + private int mNumErrors = 0; + + + IAccountAuthenticator mAuthenticator = null; + + private final boolean mStripAuthTokenFromResult; + protected final UserAccounts mAccounts; + + public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType, + boolean expectActivityLaunch, boolean stripAuthTokenFromResult) { + super(); + if (response == null) throw new IllegalArgumentException("response is null"); + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + mAccounts = accounts; + mStripAuthTokenFromResult = stripAuthTokenFromResult; + mResponse = response; + mAccountType = accountType; + mExpectActivityLaunch = expectActivityLaunch; + mCreationTime = SystemClock.elapsedRealtime(); + synchronized (mSessions) { + mSessions.put(toString(), this); + } + try { + response.asBinder().linkToDeath(this, 0 /* flags */); + } catch (RemoteException e) { + mResponse = null; + binderDied(); + } + } + + IAccountManagerResponse getResponseAndClose() { + if (mResponse == null) { + // this session has already been closed + return null; + } + IAccountManagerResponse response = mResponse; + close(); // this clears mResponse so we need to save the response before this call + return response; + } + + private void close() { + synchronized (mSessions) { + if (mSessions.remove(toString()) == null) { + // the session was already closed, so bail out now + return; + } + } + if (mResponse != null) { + // stop listening for response deaths + mResponse.asBinder().unlinkToDeath(this, 0 /* flags */); + + // clear this so that we don't accidentally send any further results + mResponse = null; + } + cancelTimeout(); + unbind(); + } + + public void binderDied() { + mResponse = null; + close(); + } + + protected String toDebugString() { + return toDebugString(SystemClock.elapsedRealtime()); + } + + protected String toDebugString(long now) { + return "Session: expectLaunch " + mExpectActivityLaunch + + ", connected " + (mAuthenticator != null) + + ", stats (" + mNumResults + "/" + mNumRequestContinued + + "/" + mNumErrors + ")" + + ", lifetime " + ((now - mCreationTime) / 1000.0); + } + + void bind() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "initiating bind to authenticator type " + mAccountType); + } + if (!bindToAuthenticator(mAccountType)) { + Log.d(TAG, "bind attempt failed for " + toDebugString()); + onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure"); + } + } + + private void unbind() { + if (mAuthenticator != null) { + mAuthenticator = null; + mContext.unbindService(this); + } + } + + public void scheduleTimeout() { + mMessageHandler.sendMessageDelayed( + mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS); + } + + public void cancelTimeout() { + mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this); + } + + public void onServiceConnected(ComponentName name, IBinder service) { + mAuthenticator = IAccountAuthenticator.Stub.asInterface(service); + try { + run(); + } catch (RemoteException e) { + onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, + "remote exception"); + } + } + + public void onServiceDisconnected(ComponentName name) { + mAuthenticator = null; + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + try { + response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, + "disconnected"); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onServiceDisconnected: " + + "caught RemoteException while responding", e); + } + } + } + } + + public abstract void run() throws RemoteException; + + public void onTimedOut() { + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + try { + response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, + "timeout"); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding", + e); + } + } + } + } + + public void onResult(Bundle result) { + mNumResults++; + if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) { + String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME); + String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); + if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { + Account account = new Account(accountName, accountType); + cancelNotification(getSigninRequiredNotificationId(mAccounts, account), + new UserHandle(mAccounts.userId)); + } + } + IAccountManagerResponse response; + if (mExpectActivityLaunch && result != null + && result.containsKey(AccountManager.KEY_INTENT)) { + response = mResponse; + } else { + response = getResponseAndClose(); + } + if (response != null) { + try { + if (result == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + + " calling onError() on response " + response); + } + response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + "null bundle returned"); + } else { + if (mStripAuthTokenFromResult) { + result.remove(AccountManager.KEY_AUTHTOKEN); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + + " calling onResult() on response " + response); + } + response.onResult(result); + } + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + } + + public void onRequestContinued() { + mNumRequestContinued++; + } + + public void onError(int errorCode, String errorMessage) { + mNumErrors++; + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + + " calling onError() on response " + response); + } + try { + response.onError(errorCode, errorMessage); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onError: caught RemoteException while responding", e); + } + } + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onError: already closed"); + } + } + } + + /** + * find the component name for the authenticator and initiate a bind + * if no authenticator or the bind fails then return false, otherwise return true + */ + private boolean bindToAuthenticator(String authenticatorType) { + final AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo; + authenticatorInfo = mAuthenticatorCache.getServiceInfo( + AuthenticatorDescription.newKey(authenticatorType), mAccounts.userId); + if (authenticatorInfo == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "there is no authenticator for " + authenticatorType + + ", bailing out"); + } + return false; + } + + Intent intent = new Intent(); + intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT); + intent.setComponent(authenticatorInfo.componentName); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName); + } + if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE, mAccounts.userId)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed"); + } + return false; + } + + + return true; + } + } + + private class MessageHandler extends Handler { + MessageHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_TIMED_OUT: + Session session = (Session)msg.obj; + session.onTimedOut(); + break; + + default: + throw new IllegalStateException("unhandled message: " + msg.what); + } + } + } + + private static String getDatabaseName(int userId) { + File systemDir = Environment.getSystemSecureDirectory(); + File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME); + if (userId == 0) { + // Migrate old file, if it exists, to the new location. + // Make sure the new file doesn't already exist. A dummy file could have been + // accidentally created in the old location, causing the new one to become corrupted + // as well. + File oldFile = new File(systemDir, DATABASE_NAME); + if (oldFile.exists() && !databaseFile.exists()) { + // Check for use directory; create if it doesn't exist, else renameTo will fail + File userDir = Environment.getUserSystemDirectory(userId); + if (!userDir.exists()) { + if (!userDir.mkdirs()) { + throw new IllegalStateException("User dir cannot be created: " + userDir); + } + } + if (!oldFile.renameTo(databaseFile)) { + throw new IllegalStateException("User dir cannot be migrated: " + databaseFile); + } + } + } + return databaseFile.getPath(); + } + + static class DatabaseHelper extends SQLiteOpenHelper { + + public DatabaseHelper(Context context, int userId) { + super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION); + } + + /** + * This call needs to be made while the mCacheLock is held. The way to + * ensure this is to get the lock any time a method is called ont the DatabaseHelper + * @param db The database. + */ + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " + + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + ACCOUNTS_NAME + " TEXT NOT NULL, " + + ACCOUNTS_TYPE + " TEXT NOT NULL, " + + ACCOUNTS_PASSWORD + " TEXT, " + + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); + + db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " + + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, " + + AUTHTOKENS_TYPE + " TEXT NOT NULL, " + + AUTHTOKENS_AUTHTOKEN + " TEXT, " + + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))"); + + createGrantsTable(db); + + db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( " + + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + EXTRAS_ACCOUNTS_ID + " INTEGER, " + + EXTRAS_KEY + " TEXT NOT NULL, " + + EXTRAS_VALUE + " TEXT, " + + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))"); + + db.execSQL("CREATE TABLE " + TABLE_META + " ( " + + META_KEY + " TEXT PRIMARY KEY NOT NULL, " + + META_VALUE + " TEXT)"); + + createAccountsDeletionTrigger(db); + } + + private void createAccountsDeletionTrigger(SQLiteDatabase db) { + db.execSQL("" + + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS + + " BEGIN" + + " DELETE FROM " + TABLE_AUTHTOKENS + + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " DELETE FROM " + TABLE_EXTRAS + + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " DELETE FROM " + TABLE_GRANTS + + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " END"); + } + + private void createGrantsTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " + + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, " + + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, " + + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " + + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + + "," + GRANTS_GRANTEE_UID + "))"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); + + if (oldVersion == 1) { + // no longer need to do anything since the work is done + // when upgrading from version 2 + oldVersion++; + } + + if (oldVersion == 2) { + createGrantsTable(db); + db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete"); + createAccountsDeletionTrigger(db); + oldVersion++; + } + + if (oldVersion == 3) { + db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE + + " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'"); + oldVersion++; + } + } + + @Override + public void onOpen(SQLiteDatabase db) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME); + } + } + + public IBinder onBind(Intent intent) { + return asBinder(); + } + + /** + * Searches array of arguments for the specified string + * @param args array of argument strings + * @param value value to search for + * @return true if the value is contained in the array + */ + private static boolean scanArgs(String[] args, String value) { + if (args != null) { + for (String arg : args) { + if (value.equals(arg)) { + return true; + } + } + } + return false; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + fout.println("Permission Denial: can't dump AccountsManager from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c"); + final IndentingPrintWriter ipw = new IndentingPrintWriter(fout, " "); + + final List<UserInfo> users = getUserManager().getUsers(); + for (UserInfo user : users) { + ipw.println("User " + user + ":"); + ipw.increaseIndent(); + dumpUser(getUserAccounts(user.id), fd, ipw, args, isCheckinRequest); + ipw.println(); + ipw.decreaseIndent(); + } + } + + private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout, + String[] args, boolean isCheckinRequest) { + synchronized (userAccounts.cacheLock) { + final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase(); + + if (isCheckinRequest) { + // This is a checkin request. *Only* upload the account types and the count of each. + Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION, + null, null, ACCOUNTS_TYPE, null, null); + try { + while (cursor.moveToNext()) { + // print type,count + fout.println(cursor.getString(0) + "," + cursor.getString(1)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } else { + Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */); + fout.println("Accounts: " + accounts.length); + for (Account account : accounts) { + fout.println(" " + account); + } + + fout.println(); + synchronized (mSessions) { + final long now = SystemClock.elapsedRealtime(); + fout.println("Active Sessions: " + mSessions.size()); + for (Session session : mSessions.values()) { + fout.println(" " + session.toDebugString(now)); + } + } + + fout.println(); + mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId); + } + } + } + + private void doNotification(UserAccounts accounts, Account account, CharSequence message, + Intent intent, int userId) { + long identityToken = clearCallingIdentity(); + try { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "doNotification: " + message + " intent:" + intent); + } + + if (intent.getComponent() != null && + GrantCredentialsPermissionActivity.class.getName().equals( + intent.getComponent().getClassName())) { + createNoCredentialsPermissionNotification(account, intent, userId); + } else { + final Integer notificationId = getSigninRequiredNotificationId(accounts, account); + intent.addCategory(String.valueOf(notificationId)); + Notification n = new Notification(android.R.drawable.stat_sys_warning, null, + 0 /* when */); + UserHandle user = new UserHandle(userId); + final String notificationTitleFormat = + mContext.getText(R.string.notification_title).toString(); + n.setLatestEventInfo(mContext, + String.format(notificationTitleFormat, account.name), + message, PendingIntent.getActivityAsUser( + mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, + null, user)); + installNotification(notificationId, n, user); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + protected void installNotification(final int notificationId, final Notification n, + UserHandle user) { + ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) + .notifyAsUser(null, notificationId, n, user); + } + + protected void cancelNotification(int id, UserHandle user) { + long identityToken = clearCallingIdentity(); + try { + ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) + .cancelAsUser(null, id, user); + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** Succeeds if any of the specified permissions are granted. */ + private void checkBinderPermission(String... permissions) { + final int uid = Binder.getCallingUid(); + + for (String perm : permissions) { + if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, " caller uid " + uid + " has " + perm); + } + return; + } + } + + String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions); + Log.w(TAG, " " + msg); + throw new SecurityException(msg); + } + + private boolean inSystemImage(int callingUid) { + final int callingUserId = UserHandle.getUserId(callingUid); + + final PackageManager userPackageManager; + try { + userPackageManager = mContext.createPackageContextAsUser( + "android", 0, new UserHandle(callingUserId)).getPackageManager(); + } catch (NameNotFoundException e) { + return false; + } + + String[] packages = userPackageManager.getPackagesForUid(callingUid); + for (String name : packages) { + try { + PackageInfo packageInfo = userPackageManager.getPackageInfo(name, 0 /* flags */); + if (packageInfo != null + && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + return false; + } + + private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) { + final boolean inSystemImage = inSystemImage(callerUid); + final boolean fromAuthenticator = account != null + && hasAuthenticatorUid(account.type, callerUid); + final boolean hasExplicitGrants = account != null + && hasExplicitlyGrantedPermission(account, authTokenType, callerUid); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid " + + callerUid + ", " + account + + ": is authenticator? " + fromAuthenticator + + ", has explicit permission? " + hasExplicitGrants); + } + return fromAuthenticator || hasExplicitGrants || inSystemImage; + } + + private boolean hasAuthenticatorUid(String accountType, int callingUid) { + final int callingUserId = UserHandle.getUserId(callingUid); + for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo : + mAuthenticatorCache.getAllServices(callingUserId)) { + if (serviceInfo.type.type.equals(accountType)) { + return (serviceInfo.uid == callingUid) || + (mPackageManager.checkSignatures(serviceInfo.uid, callingUid) + == PackageManager.SIGNATURE_MATCH); + } + } + return false; + } + + private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType, + int callerUid) { + if (callerUid == android.os.Process.SYSTEM_UID) { + return true; + } + UserAccounts accounts = getUserAccountsForCaller(); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + String[] args = { String.valueOf(callerUid), authTokenType, + account.name, account.type}; + final boolean permissionGranted = + DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0; + if (!permissionGranted && ActivityManager.isRunningInTestHarness()) { + // TODO: Skip this check when running automated tests. Replace this + // with a more general solution. + Log.d(TAG, "no credentials permission for usage of " + account + ", " + + authTokenType + " by uid " + callerUid + + " but ignoring since device is in test harness."); + return true; + } + return permissionGranted; + } + } + + private void checkCallingUidAgainstAuthenticator(Account account) { + final int uid = Binder.getCallingUid(); + if (account == null || !hasAuthenticatorUid(account.type, uid)) { + String msg = "caller uid " + uid + " is different than the authenticator's uid"; + Log.w(TAG, msg); + throw new SecurityException(msg); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid"); + } + } + + private void checkAuthenticateAccountsPermission(Account account) { + checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS); + checkCallingUidAgainstAuthenticator(account); + } + + private void checkReadAccountsPermission() { + checkBinderPermission(Manifest.permission.GET_ACCOUNTS); + } + + private void checkManageAccountsPermission() { + checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS); + } + + private void checkManageAccountsOrUseCredentialsPermissions() { + checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS, + Manifest.permission.USE_CREDENTIALS); + } + + public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) + throws RemoteException { + final int callingUid = getCallingUid(); + + if (callingUid != android.os.Process.SYSTEM_UID) { + throw new SecurityException(); + } + + if (value) { + grantAppPermission(account, authTokenType, uid); + } else { + revokeAppPermission(account, authTokenType, uid); + } + } + + /** + * Allow callers with the given uid permission to get credentials for account/authTokenType. + * <p> + * Although this is public it can only be accessed via the AccountManagerService object + * which is in the system. This means we don't need to protect it with permissions. + * @hide + */ + private void grantAppPermission(Account account, String authTokenType, int uid) { + if (account == null || authTokenType == null) { + Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception()); + return; + } + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountIdLocked(db, account); + if (accountId >= 0) { + ContentValues values = new ContentValues(); + values.put(GRANTS_ACCOUNTS_ID, accountId); + values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType); + values.put(GRANTS_GRANTEE_UID, uid); + db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values); + db.setTransactionSuccessful(); + } + } finally { + db.endTransaction(); + } + cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), + new UserHandle(accounts.userId)); + } + } + + /** + * Don't allow callers with the given uid permission to get credentials for + * account/authTokenType. + * <p> + * Although this is public it can only be accessed via the AccountManagerService object + * which is in the system. This means we don't need to protect it with permissions. + * @hide + */ + private void revokeAppPermission(Account account, String authTokenType, int uid) { + if (account == null || authTokenType == null) { + Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception()); + return; + } + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountIdLocked(db, account); + if (accountId >= 0) { + db.delete(TABLE_GRANTS, + GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND " + + GRANTS_GRANTEE_UID + "=?", + new String[]{String.valueOf(accountId), authTokenType, + String.valueOf(uid)}); + db.setTransactionSuccessful(); + } + } finally { + db.endTransaction(); + } + cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), + new UserHandle(accounts.userId)); + } + } + + static final private String stringArrayToString(String[] value) { + return value != null ? ("[" + TextUtils.join(",", value) + "]") : null; + } + + private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) { + final Account[] oldAccountsForType = accounts.accountCache.get(account.type); + if (oldAccountsForType != null) { + ArrayList<Account> newAccountsList = new ArrayList<Account>(); + for (Account curAccount : oldAccountsForType) { + if (!curAccount.equals(account)) { + newAccountsList.add(curAccount); + } + } + if (newAccountsList.isEmpty()) { + accounts.accountCache.remove(account.type); + } else { + Account[] newAccountsForType = new Account[newAccountsList.size()]; + newAccountsForType = newAccountsList.toArray(newAccountsForType); + accounts.accountCache.put(account.type, newAccountsForType); + } + } + accounts.userDataCache.remove(account); + accounts.authTokenCache.remove(account); + } + + /** + * This assumes that the caller has already checked that the account is not already present. + */ + private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) { + Account[] accountsForType = accounts.accountCache.get(account.type); + int oldLength = (accountsForType != null) ? accountsForType.length : 0; + Account[] newAccountsForType = new Account[oldLength + 1]; + if (accountsForType != null) { + System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength); + } + newAccountsForType[oldLength] = account; + accounts.accountCache.put(account.type, newAccountsForType); + } + + protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) { + if (accountType != null) { + final Account[] accounts = userAccounts.accountCache.get(accountType); + if (accounts == null) { + return EMPTY_ACCOUNT_ARRAY; + } else { + return Arrays.copyOf(accounts, accounts.length); + } + } else { + int totalLength = 0; + for (Account[] accounts : userAccounts.accountCache.values()) { + totalLength += accounts.length; + } + if (totalLength == 0) { + return EMPTY_ACCOUNT_ARRAY; + } + Account[] accounts = new Account[totalLength]; + totalLength = 0; + for (Account[] accountsOfType : userAccounts.accountCache.values()) { + System.arraycopy(accountsOfType, 0, accounts, totalLength, + accountsOfType.length); + totalLength += accountsOfType.length; + } + return accounts; + } + } + + protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db, + Account account, String key, String value) { + HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account); + if (userDataForAccount == null) { + userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); + accounts.userDataCache.put(account, userDataForAccount); + } + if (value == null) { + userDataForAccount.remove(key); + } else { + userDataForAccount.put(key, value); + } + } + + protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db, + Account account, String key, String value) { + HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account); + if (authTokensForAccount == null) { + authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); + accounts.authTokenCache.put(account, authTokensForAccount); + } + if (value == null) { + authTokensForAccount.remove(key); + } else { + authTokensForAccount.put(key, value); + } + } + + protected String readAuthTokenInternal(UserAccounts accounts, Account account, + String authTokenType) { + synchronized (accounts.cacheLock) { + HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account); + if (authTokensForAccount == null) { + // need to populate the cache for this account + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); + accounts.authTokenCache.put(account, authTokensForAccount); + } + return authTokensForAccount.get(authTokenType); + } + } + + protected String readUserDataInternal(UserAccounts accounts, Account account, String key) { + synchronized (accounts.cacheLock) { + HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account); + if (userDataForAccount == null) { + // need to populate the cache for this account + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); + accounts.userDataCache.put(account, userDataForAccount); + } + return userDataForAccount.get(key); + } + } + + protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked( + final SQLiteDatabase db, Account account) { + HashMap<String, String> userDataForAccount = new HashMap<String, String>(); + Cursor cursor = db.query(TABLE_EXTRAS, + COLUMNS_EXTRAS_KEY_AND_VALUE, + SELECTION_USERDATA_BY_ACCOUNT, + new String[]{account.name, account.type}, + null, null, null); + try { + while (cursor.moveToNext()) { + final String tmpkey = cursor.getString(0); + final String value = cursor.getString(1); + userDataForAccount.put(tmpkey, value); + } + } finally { + cursor.close(); + } + return userDataForAccount; + } + + protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked( + final SQLiteDatabase db, Account account) { + HashMap<String, String> authTokensForAccount = new HashMap<String, String>(); + Cursor cursor = db.query(TABLE_AUTHTOKENS, + COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN, + SELECTION_AUTHTOKENS_BY_ACCOUNT, + new String[]{account.name, account.type}, + null, null, null); + try { + while (cursor.moveToNext()) { + final String type = cursor.getString(0); + final String authToken = cursor.getString(1); + authTokensForAccount.put(type, authToken); + } + } finally { + cursor.close(); + } + return authTokensForAccount; + } +} diff --git a/services/java/com/android/server/accounts/IAccountAuthenticatorCache.java b/services/java/com/android/server/accounts/IAccountAuthenticatorCache.java new file mode 100644 index 0000000..bb09687 --- /dev/null +++ b/services/java/com/android/server/accounts/IAccountAuthenticatorCache.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accounts; + +import android.accounts.AuthenticatorDescription; +import android.content.pm.RegisteredServicesCache; +import android.content.pm.RegisteredServicesCacheListener; +import android.os.Handler; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collection; + +/** + * An interface to the Authenticator specialization of RegisteredServicesCache. The use of + * this interface by the AccountManagerService makes it easier to unit test it. + * @hide + */ +public interface IAccountAuthenticatorCache { + /** + * Accessor for the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that + * matched the specified {@link android.accounts.AuthenticatorDescription} or null + * if none match. + * @param type the authenticator type to return + * @return the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that + * matches the account type or null if none is present + */ + RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> getServiceInfo( + AuthenticatorDescription type, int userId); + + /** + * @return A copy of a Collection of all the current Authenticators. + */ + Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> getAllServices( + int userId); + + /** + * Dumps the state of the cache. See + * {@link android.os.Binder#dump(java.io.FileDescriptor, java.io.PrintWriter, String[])} + */ + void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId); + + /** + * Sets a listener that will be notified whenever the authenticator set changes + * @param listener the listener to notify, or null + * @param handler the {@link Handler} on which the notification will be posted. If null + * the notification will be posted on the main thread. + */ + void setListener(RegisteredServicesCacheListener<AuthenticatorDescription> listener, + Handler handler); + + void invalidateCache(int userId); +} diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 2ba9a10..1cd370a 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -1446,7 +1446,7 @@ public final class ActivityManagerService extends ActivityManagerNative context.setTheme(android.R.style.Theme_Holo); m.mContext = context; m.mFactoryTest = factoryTest; - m.mMainStack = new ActivityStack(m, context, true); + m.mMainStack = new ActivityStack(m, context, true, thr.mLooper); m.mBatteryStatsService.publish(context); m.mUsageStatsService.publish(context); @@ -1467,6 +1467,7 @@ public final class ActivityManagerService extends ActivityManagerNative static class AThread extends Thread { ActivityManagerService mService; + Looper mLooper; boolean mReady = false; public AThread() { @@ -1484,6 +1485,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { mService = m; + mLooper = Looper.myLooper(); notifyAll(); } @@ -14594,10 +14596,6 @@ public final class ActivityManagerService extends ActivityManagerNative // Clean up all state and processes associated with the user. // Kill all the processes for the user. forceStopUserLocked(userId); - AttributeCache ac = AttributeCache.instance(); - if (ac != null) { - ac.removeUser(userId); - } } } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index de0f9ca..749dc66 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -407,7 +407,7 @@ final class ActivityRecord { packageName = aInfo.applicationInfo.packageName; launchMode = aInfo.launchMode; - AttributeCache.Entry ent = AttributeCache.instance().get(userId, packageName, + AttributeCache.Entry ent = AttributeCache.instance().get(packageName, realTheme, com.android.internal.R.styleable.Window); fullscreen = ent != null && !ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsFloating, false) diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 27dd732..b007bc8 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -49,6 +49,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PowerManager; @@ -306,11 +307,17 @@ final class ActivityStack { } } - final Handler mHandler = new Handler() { + final Handler mHandler; + + final class ActivityStackHandler extends Handler { //public Handler() { // if (localLOGV) Slog.v(TAG, "Handler started!"); //} + public ActivityStackHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { case SLEEP_TIMEOUT_MSG: { @@ -410,7 +417,8 @@ final class ActivityStack { } }; - ActivityStack(ActivityManagerService service, Context context, boolean mainStack) { + ActivityStack(ActivityManagerService service, Context context, boolean mainStack, Looper looper) { + mHandler = new ActivityStackHandler(looper); mService = service; mContext = context; mMainStack = mainStack; @@ -1810,8 +1818,8 @@ final class ActivityStack { } mHistory.add(addPos, r); r.putInHistory(); - mService.mWindowManager.addAppToken(addPos, r.userId, r.appToken, - r.task.taskId, r.info.screenOrientation, r.fullscreen, + mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, + r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); if (VALIDATE_TOKENS) { validateAppTokensLocked(); @@ -1875,8 +1883,8 @@ final class ActivityStack { mNoAnimActivities.remove(r); } r.updateOptionsLocked(options); - mService.mWindowManager.addAppToken(addPos, r.userId, r.appToken, - r.task.taskId, r.info.screenOrientation, r.fullscreen, + mService.mWindowManager.addAppToken( + addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); boolean doShow = true; if (newTask) { @@ -1914,8 +1922,8 @@ final class ActivityStack { } else { // If this is the first activity, don't do any fancy animations, // because there is nothing for it to animate on top of. - mService.mWindowManager.addAppToken(addPos, r.userId, r.appToken, - r.task.taskId, r.info.screenOrientation, r.fullscreen, + mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, + r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); ActivityOptions.abort(options); } diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java new file mode 100644 index 0000000..3b92338 --- /dev/null +++ b/services/java/com/android/server/content/ContentService.java @@ -0,0 +1,848 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.content; + +import android.Manifest; +import android.accounts.Account; +import android.app.ActivityManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.IContentService; +import android.content.ISyncStatusObserver; +import android.content.PeriodicSync; +import android.content.SyncAdapterType; +import android.content.SyncInfo; +import android.content.SyncStatusInfo; +import android.database.IContentObserver; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.Log; +import android.util.SparseIntArray; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * {@hide} + */ +public final class ContentService extends IContentService.Stub { + private static final String TAG = "ContentService"; + private Context mContext; + private boolean mFactoryTest; + private final ObserverNode mRootNode = new ObserverNode(""); + private SyncManager mSyncManager = null; + private final Object mSyncManagerLock = new Object(); + + private SyncManager getSyncManager() { + synchronized(mSyncManagerLock) { + try { + // Try to create the SyncManager, return null if it fails (e.g. the disk is full). + if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest); + } catch (SQLiteException e) { + Log.e(TAG, "Can't create SyncManager", e); + } + return mSyncManager; + } + } + + @Override + protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP, + "caller doesn't have the DUMP permission"); + + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + if (mSyncManager == null) { + pw.println("No SyncManager created! (Disk full?)"); + } else { + mSyncManager.dump(fd, pw); + } + pw.println(); + pw.println("Observer tree:"); + synchronized (mRootNode) { + int[] counts = new int[2]; + final SparseIntArray pidCounts = new SparseIntArray(); + mRootNode.dumpLocked(fd, pw, args, "", " ", counts, pidCounts); + pw.println(); + ArrayList<Integer> sorted = new ArrayList<Integer>(); + for (int i=0; i<pidCounts.size(); i++) { + sorted.add(pidCounts.keyAt(i)); + } + Collections.sort(sorted, new Comparator<Integer>() { + @Override + public int compare(Integer lhs, Integer rhs) { + int lc = pidCounts.get(lhs); + int rc = pidCounts.get(rhs); + if (lc < rc) { + return 1; + } else if (lc > rc) { + return -1; + } + return 0; + } + + }); + for (int i=0; i<sorted.size(); i++) { + int pid = sorted.get(i); + pw.print(" pid "); pw.print(pid); pw.print(": "); + pw.print(pidCounts.get(pid)); pw.println(" observers"); + } + pw.println(); + pw.print(" Total number of nodes: "); pw.println(counts[0]); + pw.print(" Total number of observers: "); pw.println(counts[1]); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + // The content service only throws security exceptions, so let's + // log all others. + if (!(e instanceof SecurityException)) { + Log.e(TAG, "Content Service Crash", e); + } + throw e; + } + } + + /*package*/ ContentService(Context context, boolean factoryTest) { + mContext = context; + mFactoryTest = factoryTest; + } + + public void systemReady() { + getSyncManager(); + } + + /** + * Register a content observer tied to a specific user's view of the provider. + * @param userHandle the user whose view of the provider is to be observed. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly handled; all other pseudousers are forbidden. + */ + @Override + public void registerContentObserver(Uri uri, boolean notifyForDescendants, + IContentObserver observer, int userHandle) { + if (observer == null || uri == null) { + throw new IllegalArgumentException("You must pass a valid uri and observer"); + } + + final int callingUser = UserHandle.getCallingUserId(); + if (callingUser != userHandle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "no permission to observe other users' provider view"); + } + + if (userHandle < 0) { + if (userHandle == UserHandle.USER_CURRENT) { + userHandle = ActivityManager.getCurrentUser(); + } else if (userHandle != UserHandle.USER_ALL) { + throw new InvalidParameterException("Bad user handle for registerContentObserver: " + + userHandle); + } + } + + synchronized (mRootNode) { + mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode, + Binder.getCallingUid(), Binder.getCallingPid(), userHandle); + if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri + + " with notifyForDescendants " + notifyForDescendants); + } + } + + public void registerContentObserver(Uri uri, boolean notifyForDescendants, + IContentObserver observer) { + registerContentObserver(uri, notifyForDescendants, observer, + UserHandle.getCallingUserId()); + } + + public void unregisterContentObserver(IContentObserver observer) { + if (observer == null) { + throw new IllegalArgumentException("You must pass a valid observer"); + } + synchronized (mRootNode) { + mRootNode.removeObserverLocked(observer); + if (false) Log.v(TAG, "Unregistered observer " + observer); + } + } + + /** + * Notify observers of a particular user's view of the provider. + * @param userHandle the user whose view of the provider is to be notified. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly interpreted; no other pseudousers are allowed. + */ + @Override + public void notifyChange(Uri uri, IContentObserver observer, + boolean observerWantsSelfNotifications, boolean syncToNetwork, + int userHandle) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle + + " from observer " + observer + ", syncToNetwork " + syncToNetwork); + } + + // Notify for any user other than the caller's own requires permission. + final int callingUserHandle = UserHandle.getCallingUserId(); + if (userHandle != callingUserHandle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "no permission to notify other users"); + } + + // We passed the permission check; resolve pseudouser targets as appropriate + if (userHandle < 0) { + if (userHandle == UserHandle.USER_CURRENT) { + userHandle = ActivityManager.getCurrentUser(); + } else if (userHandle != UserHandle.USER_ALL) { + throw new InvalidParameterException("Bad user handle for notifyChange: " + + userHandle); + } + } + + final int uid = Binder.getCallingUid(); + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>(); + synchronized (mRootNode) { + mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications, + userHandle, calls); + } + final int numCalls = calls.size(); + for (int i=0; i<numCalls; i++) { + ObserverCall oc = calls.get(i); + try { + oc.mObserver.onChange(oc.mSelfChange, uri); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri); + } + } catch (RemoteException ex) { + synchronized (mRootNode) { + Log.w(TAG, "Found dead observer, removing"); + IBinder binder = oc.mObserver.asBinder(); + final ArrayList<ObserverNode.ObserverEntry> list + = oc.mNode.mObservers; + int numList = list.size(); + for (int j=0; j<numList; j++) { + ObserverNode.ObserverEntry oe = list.get(j); + if (oe.observer.asBinder() == binder) { + list.remove(j); + j--; + numList--; + } + } + } + } + } + if (syncToNetwork) { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid, + uri.getAuthority()); + } + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void notifyChange(Uri uri, IContentObserver observer, + boolean observerWantsSelfNotifications, boolean syncToNetwork) { + notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork, + UserHandle.getCallingUserId()); + } + + /** + * Hide this class since it is not part of api, + * but current unittest framework requires it to be public + * @hide + * + */ + public static final class ObserverCall { + final ObserverNode mNode; + final IContentObserver mObserver; + final boolean mSelfChange; + + ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) { + mNode = node; + mObserver = observer; + mSelfChange = selfChange; + } + } + + public void requestSync(Account account, String authority, Bundle extras) { + ContentResolver.validateSyncExtrasBundle(extras); + int userId = UserHandle.getCallingUserId(); + int uId = Binder.getCallingUid(); + + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.scheduleSync(account, userId, uId, authority, extras, 0 /* no delay */, + false /* onlyThoseWithUnkownSyncableState */); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** + * Clear all scheduled sync operations that match the uri and cancel the active sync + * if they match the authority and account, if they are present. + * @param account filter the pending and active syncs to cancel using this account + * @param authority filter the pending and active syncs to cancel using this authority + */ + public void cancelSync(Account account, String authority) { + int userId = UserHandle.getCallingUserId(); + + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.clearScheduledSyncOperations(account, userId, authority); + syncManager.cancelActiveSync(account, userId, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** + * Get information about the SyncAdapters that are known to the system. + * @return an array of SyncAdapters that have registered with the system + */ + public SyncAdapterType[] getSyncAdapterTypes() { + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + final int userId = UserHandle.getCallingUserId(); + final long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + return syncManager.getSyncAdapterTypes(userId); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean getSyncAutomatically(Account account, String providerName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getSyncAutomatically( + account, userId, providerName); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void setSyncAutomatically(Account account, String providerName, boolean sync) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().setSyncAutomatically( + account, userId, providerName, sync); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void addPeriodicSync(Account account, String authority, Bundle extras, + long pollFrequency) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + getSyncManager().getSyncStorageEngine().addPeriodicSync( + account, userId, authority, extras, pollFrequency); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void removePeriodicSync(Account account, String authority, Bundle extras) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority, + extras); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + return getSyncManager().getSyncStorageEngine().getPeriodicSyncs( + account, userId, providerName); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public int getIsSyncable(Account account, String providerName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getIsSyncable( + account, userId, providerName); + } + } finally { + restoreCallingIdentity(identityToken); + } + return -1; + } + + public void setIsSyncable(Account account, String providerName, int syncable) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().setIsSyncable( + account, userId, providerName, syncable); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean getMasterSyncAutomatically() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void setMasterSyncAutomatically(boolean flag) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean isSyncActive(Account account, String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().isSyncActive( + account, userId, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public List<SyncInfo> getCurrentSyncs() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public SyncStatusInfo getSyncStatus(Account account, String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority( + account, userId, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return null; + } + + public boolean isSyncPending(Account account, String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void addStatusChangeListener(int mask, ISyncStatusObserver callback) { + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null && callback != null) { + syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void removeStatusChangeListener(ISyncStatusObserver callback) { + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null && callback != null) { + syncManager.getSyncStorageEngine().removeStatusChangeListener(callback); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public static ContentService main(Context context, boolean factoryTest) { + ContentService service = new ContentService(context, factoryTest); + ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service); + return service; + } + + /** + * Hide this class since it is not part of api, + * but current unittest framework requires it to be public + * @hide + */ + public static final class ObserverNode { + private class ObserverEntry implements IBinder.DeathRecipient { + public final IContentObserver observer; + public final int uid; + public final int pid; + public final boolean notifyForDescendants; + private final int userHandle; + private final Object observersLock; + + public ObserverEntry(IContentObserver o, boolean n, Object observersLock, + int _uid, int _pid, int _userHandle) { + this.observersLock = observersLock; + observer = o; + uid = _uid; + pid = _pid; + userHandle = _userHandle; + notifyForDescendants = n; + try { + observer.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + + public void binderDied() { + synchronized (observersLock) { + removeObserverLocked(observer); + } + } + + public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, + String name, String prefix, SparseIntArray pidCounts) { + pidCounts.put(pid, pidCounts.get(pid)+1); + pw.print(prefix); pw.print(name); pw.print(": pid="); + pw.print(pid); pw.print(" uid="); + pw.print(uid); pw.print(" user="); + pw.print(userHandle); pw.print(" target="); + pw.println(Integer.toHexString(System.identityHashCode( + observer != null ? observer.asBinder() : null))); + } + } + + public static final int INSERT_TYPE = 0; + public static final int UPDATE_TYPE = 1; + public static final int DELETE_TYPE = 2; + + private String mName; + private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>(); + private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>(); + + public ObserverNode(String name) { + mName = name; + } + + public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, + String name, String prefix, int[] counts, SparseIntArray pidCounts) { + String innerName = null; + if (mObservers.size() > 0) { + if ("".equals(name)) { + innerName = mName; + } else { + innerName = name + "/" + mName; + } + for (int i=0; i<mObservers.size(); i++) { + counts[1]++; + mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix, + pidCounts); + } + } + if (mChildren.size() > 0) { + if (innerName == null) { + if ("".equals(name)) { + innerName = mName; + } else { + innerName = name + "/" + mName; + } + } + for (int i=0; i<mChildren.size(); i++) { + counts[0]++; + mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix, + counts, pidCounts); + } + } + } + + private String getUriSegment(Uri uri, int index) { + if (uri != null) { + if (index == 0) { + return uri.getAuthority(); + } else { + return uri.getPathSegments().get(index - 1); + } + } else { + return null; + } + } + + private int countUriSegments(Uri uri) { + if (uri == null) { + return 0; + } + return uri.getPathSegments().size() + 1; + } + + // Invariant: userHandle is either a hard user number or is USER_ALL + public void addObserverLocked(Uri uri, IContentObserver observer, + boolean notifyForDescendants, Object observersLock, + int uid, int pid, int userHandle) { + addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock, + uid, pid, userHandle); + } + + private void addObserverLocked(Uri uri, int index, IContentObserver observer, + boolean notifyForDescendants, Object observersLock, + int uid, int pid, int userHandle) { + // If this is the leaf node add the observer + if (index == countUriSegments(uri)) { + mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock, + uid, pid, userHandle)); + return; + } + + // Look to see if the proper child already exists + String segment = getUriSegment(uri, index); + if (segment == null) { + throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer"); + } + int N = mChildren.size(); + for (int i = 0; i < N; i++) { + ObserverNode node = mChildren.get(i); + if (node.mName.equals(segment)) { + node.addObserverLocked(uri, index + 1, observer, notifyForDescendants, + observersLock, uid, pid, userHandle); + return; + } + } + + // No child found, create one + ObserverNode node = new ObserverNode(segment); + mChildren.add(node); + node.addObserverLocked(uri, index + 1, observer, notifyForDescendants, + observersLock, uid, pid, userHandle); + } + + public boolean removeObserverLocked(IContentObserver observer) { + int size = mChildren.size(); + for (int i = 0; i < size; i++) { + boolean empty = mChildren.get(i).removeObserverLocked(observer); + if (empty) { + mChildren.remove(i); + i--; + size--; + } + } + + IBinder observerBinder = observer.asBinder(); + size = mObservers.size(); + for (int i = 0; i < size; i++) { + ObserverEntry entry = mObservers.get(i); + if (entry.observer.asBinder() == observerBinder) { + mObservers.remove(i); + // We no longer need to listen for death notifications. Remove it. + observerBinder.unlinkToDeath(entry, 0); + break; + } + } + + if (mChildren.size() == 0 && mObservers.size() == 0) { + return true; + } + return false; + } + + private void collectMyObserversLocked(boolean leaf, IContentObserver observer, + boolean observerWantsSelfNotifications, int targetUserHandle, + ArrayList<ObserverCall> calls) { + int N = mObservers.size(); + IBinder observerBinder = observer == null ? null : observer.asBinder(); + for (int i = 0; i < N; i++) { + ObserverEntry entry = mObservers.get(i); + + // Don't notify the observer if it sent the notification and isn't interested + // in self notifications + boolean selfChange = (entry.observer.asBinder() == observerBinder); + if (selfChange && !observerWantsSelfNotifications) { + continue; + } + + // Does this observer match the target user? + if (targetUserHandle == UserHandle.USER_ALL + || entry.userHandle == UserHandle.USER_ALL + || targetUserHandle == entry.userHandle) { + // Make sure the observer is interested in the notification + if (leaf || (!leaf && entry.notifyForDescendants)) { + calls.add(new ObserverCall(this, entry.observer, selfChange)); + } + } + } + } + + /** + * targetUserHandle is either a hard user handle or is USER_ALL + */ + public void collectObserversLocked(Uri uri, int index, IContentObserver observer, + boolean observerWantsSelfNotifications, int targetUserHandle, + ArrayList<ObserverCall> calls) { + String segment = null; + int segmentCount = countUriSegments(uri); + if (index >= segmentCount) { + // This is the leaf node, notify all observers + collectMyObserversLocked(true, observer, observerWantsSelfNotifications, + targetUserHandle, calls); + } else if (index < segmentCount){ + segment = getUriSegment(uri, index); + // Notify any observers at this level who are interested in descendants + collectMyObserversLocked(false, observer, observerWantsSelfNotifications, + targetUserHandle, calls); + } + + int N = mChildren.size(); + for (int i = 0; i < N; i++) { + ObserverNode node = mChildren.get(i); + if (segment == null || node.mName.equals(segment)) { + // We found the child, + node.collectObserversLocked(uri, index + 1, + observer, observerWantsSelfNotifications, targetUserHandle, calls); + if (segment != null) { + break; + } + } + } + } + } +} diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java new file mode 100644 index 0000000..cd66cf2 --- /dev/null +++ b/services/java/com/android/server/content/SyncManager.java @@ -0,0 +1,2786 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.content; + +import android.accounts.Account; +import android.accounts.AccountAndUser; +import android.accounts.AccountManager; +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.ISyncAdapter; +import android.content.ISyncContext; +import android.content.ISyncStatusObserver; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.SyncActivityTooManyDeletes; +import android.content.SyncAdapterType; +import android.content.SyncAdaptersCache; +import android.content.SyncInfo; +import android.content.SyncResult; +import android.content.SyncStatusInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.RegisteredServicesCache; +import android.content.pm.RegisteredServicesCacheListener; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.WorkSource; +import android.provider.Settings; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.util.EventLog; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.accounts.AccountManagerService; +import com.android.server.content.SyncStorageEngine.OnSyncRequestListener; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; +import com.google.android.collect.Sets; + +import java.io.FileDescriptor; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +/** + * @hide + */ +public class SyncManager { + private static final String TAG = "SyncManager"; + + /** Delay a sync due to local changes this long. In milliseconds */ + private static final long LOCAL_SYNC_DELAY; + + /** + * If a sync takes longer than this and the sync queue is not empty then we will + * cancel it and add it back to the end of the sync queue. In milliseconds. + */ + private static final long MAX_TIME_PER_SYNC; + + static { + final boolean isLargeRAM = ActivityManager.isLargeRAM(); + int defaultMaxInitSyncs = isLargeRAM ? 5 : 2; + int defaultMaxRegularSyncs = isLargeRAM ? 2 : 1; + MAX_SIMULTANEOUS_INITIALIZATION_SYNCS = + SystemProperties.getInt("sync.max_init_syncs", defaultMaxInitSyncs); + MAX_SIMULTANEOUS_REGULAR_SYNCS = + SystemProperties.getInt("sync.max_regular_syncs", defaultMaxRegularSyncs); + LOCAL_SYNC_DELAY = + SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */); + MAX_TIME_PER_SYNC = + SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */); + SYNC_NOTIFICATION_DELAY = + SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */); + } + + private static final long SYNC_NOTIFICATION_DELAY; + + /** + * When retrying a sync for the first time use this delay. After that + * the retry time will double until it reached MAX_SYNC_RETRY_TIME. + * In milliseconds. + */ + private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds + + /** + * Default the max sync retry time to this value. + */ + private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour + + /** + * How long to wait before retrying a sync that failed due to one already being in progress. + */ + private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10; + + private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000; + + private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*"; + private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; + private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock"; + + private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS; + private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS; + + private Context mContext; + + private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0]; + + // TODO: add better locking around mRunningAccounts + private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY; + + volatile private PowerManager.WakeLock mHandleAlarmWakeLock; + volatile private PowerManager.WakeLock mSyncManagerWakeLock; + volatile private boolean mDataConnectionIsConnected = false; + volatile private boolean mStorageIsLow = false; + + private final NotificationManager mNotificationMgr; + private AlarmManager mAlarmService = null; + + private SyncStorageEngine mSyncStorageEngine; + + @GuardedBy("mSyncQueue") + private final SyncQueue mSyncQueue; + + protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList(); + + // set if the sync active indicator should be reported + private boolean mNeedSyncActiveNotification = false; + + private final PendingIntent mSyncAlarmIntent; + // Synchronized on "this". Instead of using this directly one should instead call + // its accessor, getConnManager(). + private ConnectivityManager mConnManagerDoNotUseDirectly; + + protected SyncAdaptersCache mSyncAdapters; + + private BroadcastReceiver mStorageIntentReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Internal storage is low."); + } + mStorageIsLow = true; + cancelActiveSync(null /* any account */, UserHandle.USER_ALL, + null /* any authority */); + } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Internal storage is ok."); + } + mStorageIsLow = false; + sendCheckAlarmsMessage(); + } + } + }; + + private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + mSyncHandler.onBootCompleted(); + } + }; + + private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (getConnectivityManager().getBackgroundDataSetting()) { + scheduleSync(null /* account */, UserHandle.USER_ALL, + SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED, + null /* authority */, + new Bundle(), 0 /* delay */, + false /* onlyThoseWithUnknownSyncableState */); + } + } + }; + + private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + updateRunningAccounts(); + + // Kick off sync for everyone, since this was a radical account change + scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_ACCOUNTS_UPDATED, null, + null, 0 /* no delay */, false); + } + }; + + private final PowerManager mPowerManager; + + // Use this as a random offset to seed all periodic syncs + private int mSyncRandomOffsetMillis; + + private final UserManager mUserManager; + + private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds + private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours + + private List<UserInfo> getAllUsers() { + return mUserManager.getUsers(); + } + + private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) { + boolean found = false; + for (int i = 0; i < accounts.length; i++) { + if (accounts[i].userId == userId + && accounts[i].account.equals(account)) { + found = true; + break; + } + } + return found; + } + + public void updateRunningAccounts() { + mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts(); + + if (mBootCompleted) { + doDatabaseCleanup(); + } + + for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { + if (!containsAccountAndUser(mRunningAccounts, + currentSyncContext.mSyncOperation.account, + currentSyncContext.mSyncOperation.userId)) { + Log.d(TAG, "canceling sync since the account is no longer running"); + sendSyncFinishedOrCanceledMessage(currentSyncContext, + null /* no result since this is a cancel */); + } + } + + // we must do this since we don't bother scheduling alarms when + // the accounts are not set yet + sendCheckAlarmsMessage(); + } + + private void doDatabaseCleanup() { + for (UserInfo user : mUserManager.getUsers(true)) { + // Skip any partially created/removed users + if (user.partial) continue; + Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id); + mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id); + } + } + + private BroadcastReceiver mConnectivityIntentReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + final boolean wasConnected = mDataConnectionIsConnected; + + // don't use the intent to figure out if network is connected, just check + // ConnectivityManager directly. + mDataConnectionIsConnected = readDataConnectionState(); + if (mDataConnectionIsConnected) { + if (!wasConnected) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Reconnection detected: clearing all backoffs"); + } + mSyncStorageEngine.clearAllBackoffs(mSyncQueue); + } + sendCheckAlarmsMessage(); + } + } + }; + + private boolean readDataConnectionState() { + NetworkInfo networkInfo = getConnectivityManager().getActiveNetworkInfo(); + return (networkInfo != null) && networkInfo.isConnected(); + } + + private BroadcastReceiver mShutdownIntentReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + Log.w(TAG, "Writing sync state before shutdown..."); + getSyncStorageEngine().writeAllState(); + } + }; + + private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) return; + + if (Intent.ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(userId); + } else if (Intent.ACTION_USER_STARTING.equals(action)) { + onUserStarting(userId); + } else if (Intent.ACTION_USER_STOPPING.equals(action)) { + onUserStopping(userId); + } + } + }; + + private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; + private final SyncHandler mSyncHandler; + + private volatile boolean mBootCompleted = false; + + private ConnectivityManager getConnectivityManager() { + synchronized (this) { + if (mConnManagerDoNotUseDirectly == null) { + mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + } + return mConnManagerDoNotUseDirectly; + } + } + + /** + * Should only be created after {@link ContentService#systemReady()} so that + * {@link PackageManager} is ready to query. + */ + public SyncManager(Context context, boolean factoryTest) { + // Initialize the SyncStorageEngine first, before registering observers + // and creating threads and so on; it may fail if the disk is full. + mContext = context; + + SyncStorageEngine.init(context); + mSyncStorageEngine = SyncStorageEngine.getSingleton(); + mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { + public void onSyncRequest(Account account, int userId, int reason, String authority, + Bundle extras) { + scheduleSync(account, userId, reason, authority, extras, 0, false); + } + }); + + mSyncAdapters = new SyncAdaptersCache(mContext); + mSyncQueue = new SyncQueue(mContext.getPackageManager(), mSyncStorageEngine, mSyncAdapters); + + HandlerThread syncThread = new HandlerThread("SyncHandlerThread", + Process.THREAD_PRIORITY_BACKGROUND); + syncThread.start(); + mSyncHandler = new SyncHandler(syncThread.getLooper()); + + mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { + @Override + public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) { + if (!removed) { + scheduleSync(null, UserHandle.USER_ALL, + SyncOperation.REASON_SERVICE_CHANGED, + type.authority, null, 0 /* no delay */, + false /* onlyThoseWithUnkownSyncableState */); + } + } + }, mSyncHandler); + + mSyncAlarmIntent = PendingIntent.getBroadcast( + mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0); + + IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(mConnectivityIntentReceiver, intentFilter); + + if (!factoryTest) { + intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); + context.registerReceiver(mBootCompletedReceiver, intentFilter); + } + + intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); + context.registerReceiver(mBackgroundDataSettingChanged, intentFilter); + + intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW); + intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); + context.registerReceiver(mStorageIntentReceiver, intentFilter); + + intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); + intentFilter.setPriority(100); + context.registerReceiver(mShutdownIntentReceiver, intentFilter); + + intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + intentFilter.addAction(Intent.ACTION_USER_STARTING); + intentFilter.addAction(Intent.ACTION_USER_STOPPING); + mContext.registerReceiverAsUser( + mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); + + if (!factoryTest) { + mNotificationMgr = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + context.registerReceiver(new SyncAlarmIntentReceiver(), + new IntentFilter(ACTION_SYNC_ALARM)); + } else { + mNotificationMgr = null; + } + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + + // This WakeLock is used to ensure that we stay awake between the time that we receive + // a sync alarm notification and when we finish processing it. We need to do this + // because we don't do the work in the alarm handler, rather we do it in a message + // handler. + mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + HANDLE_SYNC_ALARM_WAKE_LOCK); + mHandleAlarmWakeLock.setReferenceCounted(false); + + // This WakeLock is used to ensure that we stay awake while running the sync loop + // message handler. Normally we will hold a sync adapter wake lock while it is being + // synced but during the execution of the sync loop it might finish a sync for + // one sync adapter before starting the sync for the other sync adapter and we + // don't want the device to go to sleep during that window. + mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + SYNC_LOOP_WAKE_LOCK); + mSyncManagerWakeLock.setReferenceCounted(false); + + mSyncStorageEngine.addStatusChangeListener( + ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() { + public void onStatusChanged(int which) { + // force the sync loop to run if the settings change + sendCheckAlarmsMessage(); + } + }); + + if (!factoryTest) { + // Register for account list updates for all users + mContext.registerReceiverAsUser(mAccountsUpdatedReceiver, + UserHandle.ALL, + new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION), + null, null); + } + + // Pick a random second in a day to seed all periodic syncs + mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000; + } + + /** + * Return a random value v that satisfies minValue <= v < maxValue. The difference between + * maxValue and minValue must be less than Integer.MAX_VALUE. + */ + private long jitterize(long minValue, long maxValue) { + Random random = new Random(SystemClock.elapsedRealtime()); + long spread = maxValue - minValue; + if (spread > Integer.MAX_VALUE) { + throw new IllegalArgumentException("the difference between the maxValue and the " + + "minValue must be less than " + Integer.MAX_VALUE); + } + return minValue + random.nextInt((int)spread); + } + + public SyncStorageEngine getSyncStorageEngine() { + return mSyncStorageEngine; + } + + private void ensureAlarmService() { + if (mAlarmService == null) { + mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + } + } + + /** + * Initiate a sync. This can start a sync for all providers + * (pass null to url, set onlyTicklable to false), only those + * providers that are marked as ticklable (pass null to url, + * set onlyTicklable to true), or a specific provider (set url + * to the content url of the provider). + * + * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is + * true then initiate a sync that just checks for local changes to send + * to the server, otherwise initiate a sync that first gets any + * changes from the server before sending local changes back to + * the server. + * + * <p>If a specific provider is being synced (the url is non-null) + * then the extras can contain SyncAdapter-specific information + * to control what gets synced (e.g. which specific feed to sync). + * + * <p>You'll start getting callbacks after this. + * + * @param requestedAccount the account to sync, may be null to signify all accounts + * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL, + * then all users' accounts are considered. + * @param reason for sync request. If this is a positive integer, it is the Linux uid + * assigned to the process that requested the sync. If it's negative, the sync was requested by + * the SyncManager itself and could be one of the following: + * {@link SyncOperation#REASON_BACKGROUND_DATA_SETTINGS_CHANGED} + * {@link SyncOperation#REASON_ACCOUNTS_UPDATED} + * {@link SyncOperation#REASON_SERVICE_CHANGED} + * {@link SyncOperation#REASON_PERIODIC} + * {@link SyncOperation#REASON_IS_SYNCABLE} + * {@link SyncOperation#REASON_SYNC_AUTO} + * {@link SyncOperation#REASON_MASTER_SYNC_AUTO} + * {@link SyncOperation#REASON_USER_START} + * @param requestedAuthority the authority to sync, may be null to indicate all authorities + * @param extras a Map of SyncAdapter-specific information to control + * syncs of a specific provider. Can be null. Is ignored + * if the url is null. + * @param delay how many milliseconds in the future to wait before performing this + * @param onlyThoseWithUnkownSyncableState + */ + public void scheduleSync(Account requestedAccount, int userId, int reason, + String requestedAuthority, Bundle extras, long delay, + boolean onlyThoseWithUnkownSyncableState) { + boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); + + final boolean backgroundDataUsageAllowed = !mBootCompleted || + getConnectivityManager().getBackgroundDataSetting(); + + if (extras == null) extras = new Bundle(); + + Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); + if (expedited) { + delay = -1; // this means schedule at the front of the queue + } + + AccountAndUser[] accounts; + if (requestedAccount != null && userId != UserHandle.USER_ALL) { + accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) }; + } else { + // if the accounts aren't configured yet then we can't support an account-less + // sync request + accounts = mRunningAccounts; + if (accounts.length == 0) { + if (isLoggable) { + Log.v(TAG, "scheduleSync: no accounts configured, dropping"); + } + return; + } + } + + final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); + final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); + if (manualSync) { + extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); + } + final boolean ignoreSettings = + extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); + + int source; + if (uploadOnly) { + source = SyncStorageEngine.SOURCE_LOCAL; + } else if (manualSync) { + source = SyncStorageEngine.SOURCE_USER; + } else if (requestedAuthority == null) { + source = SyncStorageEngine.SOURCE_POLL; + } else { + // this isn't strictly server, since arbitrary callers can (and do) request + // a non-forced two-way sync on a specific url + source = SyncStorageEngine.SOURCE_SERVER; + } + + for (AccountAndUser account : accounts) { + // Compile a list of authorities that have sync adapters. + // For each authority sync each account that matches a sync adapter. + final HashSet<String> syncableAuthorities = new HashSet<String>(); + for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter : + mSyncAdapters.getAllServices(account.userId)) { + syncableAuthorities.add(syncAdapter.type.authority); + } + + // if the url was specified then replace the list of authorities + // with just this authority or clear it if this authority isn't + // syncable + if (requestedAuthority != null) { + final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority); + syncableAuthorities.clear(); + if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority); + } + + for (String authority : syncableAuthorities) { + int isSyncable = mSyncStorageEngine.getIsSyncable(account.account, account.userId, + authority); + if (isSyncable == 0) { + continue; + } + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; + syncAdapterInfo = mSyncAdapters.getServiceInfo( + SyncAdapterType.newKey(authority, account.account.type), account.userId); + if (syncAdapterInfo == null) { + continue; + } + final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs(); + final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable(); + if (isSyncable < 0 && isAlwaysSyncable) { + mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1); + isSyncable = 1; + } + if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { + continue; + } + if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) { + continue; + } + + // always allow if the isSyncable state is unknown + boolean syncAllowed = + (isSyncable < 0) + || ignoreSettings + || (backgroundDataUsageAllowed + && mSyncStorageEngine.getMasterSyncAutomatically(account.userId) + && mSyncStorageEngine.getSyncAutomatically(account.account, + account.userId, authority)); + if (!syncAllowed) { + if (isLoggable) { + Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority + + " is not allowed, dropping request"); + } + continue; + } + + Pair<Long, Long> backoff = mSyncStorageEngine + .getBackoff(account.account, account.userId, authority); + long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account, + account.userId, authority); + final long backoffTime = backoff != null ? backoff.first : 0; + if (isSyncable < 0) { + Bundle newExtras = new Bundle(); + newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); + if (isLoggable) { + Log.v(TAG, "scheduleSync:" + + " delay " + delay + + ", source " + source + + ", account " + account + + ", authority " + authority + + ", extras " + newExtras); + } + scheduleSyncOperation( + new SyncOperation(account.account, account.userId, reason, source, + authority, newExtras, 0, backoffTime, delayUntil, + allowParallelSyncs)); + } + if (!onlyThoseWithUnkownSyncableState) { + if (isLoggable) { + Log.v(TAG, "scheduleSync:" + + " delay " + delay + + ", source " + source + + ", account " + account + + ", authority " + authority + + ", extras " + extras); + } + scheduleSyncOperation( + new SyncOperation(account.account, account.userId, reason, source, + authority, extras, delay, backoffTime, delayUntil, + allowParallelSyncs)); + } + } + } + } + + public void scheduleLocalSync(Account account, int userId, int reason, String authority) { + final Bundle extras = new Bundle(); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); + scheduleSync(account, userId, reason, authority, extras, LOCAL_SYNC_DELAY, + false /* onlyThoseWithUnkownSyncableState */); + } + + public SyncAdapterType[] getSyncAdapterTypes(int userId) { + final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos; + serviceInfos = mSyncAdapters.getAllServices(userId); + SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()]; + int i = 0; + for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) { + types[i] = serviceInfo.type; + ++i; + } + return types; + } + + private void sendSyncAlarmMessage() { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM"); + mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM); + } + + private void sendCheckAlarmsMessage() { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS"); + mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS); + mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS); + } + + private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext, + SyncResult syncResult) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED"); + Message msg = mSyncHandler.obtainMessage(); + msg.what = SyncHandler.MESSAGE_SYNC_FINISHED; + msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult); + mSyncHandler.sendMessage(msg); + } + + private void sendCancelSyncsMessage(final Account account, final int userId, + final String authority) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL"); + Message msg = mSyncHandler.obtainMessage(); + msg.what = SyncHandler.MESSAGE_CANCEL; + msg.obj = Pair.create(account, authority); + msg.arg1 = userId; + mSyncHandler.sendMessage(msg); + } + + class SyncHandlerMessagePayload { + public final ActiveSyncContext activeSyncContext; + public final SyncResult syncResult; + + SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) { + this.activeSyncContext = syncContext; + this.syncResult = syncResult; + } + } + + class SyncAlarmIntentReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + mHandleAlarmWakeLock.acquire(); + sendSyncAlarmMessage(); + } + } + + private void clearBackoffSetting(SyncOperation op) { + mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority, + SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); + synchronized (mSyncQueue) { + mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0); + } + } + + private void increaseBackoffSetting(SyncOperation op) { + // TODO: Use this function to align it to an already scheduled sync + // operation in the specified window + final long now = SystemClock.elapsedRealtime(); + + final Pair<Long, Long> previousSettings = + mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority); + long newDelayInMs = -1; + if (previousSettings != null) { + // don't increase backoff before current backoff is expired. This will happen for op's + // with ignoreBackoff set. + if (now < previousSettings.first) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Still in backoff, do not increase it. " + + "Remaining: " + ((previousSettings.first - now) / 1000) + " seconds."); + } + return; + } + // Subsequent delays are the double of the previous delay + newDelayInMs = previousSettings.second * 2; + } + if (newDelayInMs <= 0) { + // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS + newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS, + (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1)); + } + + // Cap the delay + long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(), + Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS, + DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS); + if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) { + newDelayInMs = maxSyncRetryTimeInSeconds * 1000; + } + + final long backoff = now + newDelayInMs; + + mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority, + backoff, newDelayInMs); + + op.backoff = backoff; + op.updateEffectiveRunTime(); + + synchronized (mSyncQueue) { + mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff); + } + } + + private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) { + final long delayUntil = delayUntilSeconds * 1000; + final long absoluteNow = System.currentTimeMillis(); + long newDelayUntilTime; + if (delayUntil > absoluteNow) { + newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow); + } else { + newDelayUntilTime = 0; + } + mSyncStorageEngine + .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime); + synchronized (mSyncQueue) { + mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime); + } + } + + /** + * Cancel the active sync if it matches the authority and account. + * @param account limit the cancelations to syncs with this account, if non-null + * @param authority limit the cancelations to syncs with this authority, if non-null + */ + public void cancelActiveSync(Account account, int userId, String authority) { + sendCancelSyncsMessage(account, userId, authority); + } + + /** + * Create and schedule a SyncOperation. + * + * @param syncOperation the SyncOperation to schedule + */ + public void scheduleSyncOperation(SyncOperation syncOperation) { + boolean queueChanged; + synchronized (mSyncQueue) { + queueChanged = mSyncQueue.add(syncOperation); + } + + if (queueChanged) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation); + } + sendCheckAlarmsMessage(); + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation " + + syncOperation); + } + } + } + + /** + * Remove scheduled sync operations. + * @param account limit the removals to operations with this account, if non-null + * @param authority limit the removals to operations with this authority, if non-null + */ + public void clearScheduledSyncOperations(Account account, int userId, String authority) { + synchronized (mSyncQueue) { + mSyncQueue.remove(account, userId, authority); + } + mSyncStorageEngine.setBackoff(account, userId, authority, + SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); + } + + void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) { + boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG); + if (isLoggable) { + Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation); + } + + operation = new SyncOperation(operation); + + // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given + // request. Retries of the request will always honor the backoff, so clear the + // flag in case we retry this request. + if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) { + operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); + } + + // If this sync aborted because the internal sync loop retried too many times then + // don't reschedule. Otherwise we risk getting into a retry loop. + // If the operation succeeded to some extent then retry immediately. + // If this was a two-way sync then retry soft errors with an exponential backoff. + // If this was an upward sync then schedule a two-way sync immediately. + // Otherwise do not reschedule. + if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) { + Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified " + + operation); + } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false) + && !syncResult.syncAlreadyInProgress) { + operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD); + Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync " + + "encountered an error: " + operation); + scheduleSyncOperation(operation); + } else if (syncResult.tooManyRetries) { + Log.d(TAG, "not retrying sync operation because it retried too many times: " + + operation); + } else if (syncResult.madeSomeProgress()) { + if (isLoggable) { + Log.d(TAG, "retrying sync operation because even though it had an error " + + "it achieved some success"); + } + scheduleSyncOperation(operation); + } else if (syncResult.syncAlreadyInProgress) { + if (isLoggable) { + Log.d(TAG, "retrying sync operation that failed because there was already a " + + "sync in progress: " + operation); + } + scheduleSyncOperation(new SyncOperation(operation.account, operation.userId, + operation.reason, + operation.syncSource, + operation.authority, operation.extras, + DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, + operation.backoff, operation.delayUntil, operation.allowParallelSyncs)); + } else if (syncResult.hasSoftError()) { + if (isLoggable) { + Log.d(TAG, "retrying sync operation because it encountered a soft error: " + + operation); + } + scheduleSyncOperation(operation); + } else { + Log.d(TAG, "not retrying sync operation because the error is a hard error: " + + operation); + } + } + + private void onUserStarting(int userId) { + // Make sure that accounts we're about to use are valid + AccountManagerService.getSingleton().validateAccounts(userId); + + mSyncAdapters.invalidateCache(userId); + + updateRunningAccounts(); + + synchronized (mSyncQueue) { + mSyncQueue.addPendingOperations(userId); + } + + // Schedule sync for any accounts under started user + final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId); + for (Account account : accounts) { + scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, + 0 /* no delay */, true /* onlyThoseWithUnknownSyncableState */); + } + + sendCheckAlarmsMessage(); + } + + private void onUserStopping(int userId) { + updateRunningAccounts(); + + cancelActiveSync( + null /* any account */, + userId, + null /* any authority */); + } + + private void onUserRemoved(int userId) { + updateRunningAccounts(); + + // Clean up the storage engine database + mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId); + synchronized (mSyncQueue) { + mSyncQueue.removeUser(userId); + } + } + + /** + * @hide + */ + class ActiveSyncContext extends ISyncContext.Stub + implements ServiceConnection, IBinder.DeathRecipient { + final SyncOperation mSyncOperation; + final long mHistoryRowId; + ISyncAdapter mSyncAdapter; + final long mStartTime; + long mTimeoutStartTime; + boolean mBound; + final PowerManager.WakeLock mSyncWakeLock; + final int mSyncAdapterUid; + SyncInfo mSyncInfo; + boolean mIsLinkedToDeath = false; + + /** + * Create an ActiveSyncContext for an impending sync and grab the wakelock for that + * sync adapter. Since this grabs the wakelock you need to be sure to call + * close() when you are done with this ActiveSyncContext, whether the sync succeeded + * or not. + * @param syncOperation the SyncOperation we are about to sync + * @param historyRowId the row in which to record the history info for this sync + * @param syncAdapterUid the UID of the application that contains the sync adapter + * for this sync. This is used to attribute the wakelock hold to that application. + */ + public ActiveSyncContext(SyncOperation syncOperation, long historyRowId, + int syncAdapterUid) { + super(); + mSyncAdapterUid = syncAdapterUid; + mSyncOperation = syncOperation; + mHistoryRowId = historyRowId; + mSyncAdapter = null; + mStartTime = SystemClock.elapsedRealtime(); + mTimeoutStartTime = mStartTime; + mSyncWakeLock = mSyncHandler.getSyncWakeLock( + mSyncOperation.account, mSyncOperation.authority); + mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid)); + mSyncWakeLock.acquire(); + } + + public void sendHeartbeat() { + // heartbeats are no longer used + } + + public void onFinished(SyncResult result) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this); + // include "this" in the message so that the handler can ignore it if this + // ActiveSyncContext is no longer the mActiveSyncContext at message handling + // time + sendSyncFinishedOrCanceledMessage(this, result); + } + + public void toString(StringBuilder sb) { + sb.append("startTime ").append(mStartTime) + .append(", mTimeoutStartTime ").append(mTimeoutStartTime) + .append(", mHistoryRowId ").append(mHistoryRowId) + .append(", syncOperation ").append(mSyncOperation); + } + + public void onServiceConnected(ComponentName name, IBinder service) { + Message msg = mSyncHandler.obtainMessage(); + msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED; + msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service)); + mSyncHandler.sendMessage(msg); + } + + public void onServiceDisconnected(ComponentName name) { + Message msg = mSyncHandler.obtainMessage(); + msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED; + msg.obj = new ServiceConnectionData(this, null); + mSyncHandler.sendMessage(msg); + } + + boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this); + } + Intent intent = new Intent(); + intent.setAction("android.content.SyncAdapter"); + intent.setComponent(info.componentName); + intent.putExtra(Intent.EXTRA_CLIENT_LABEL, + com.android.internal.R.string.sync_binding_label); + intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser( + mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0, + null, new UserHandle(userId))); + mBound = true; + final boolean bindResult = mContext.bindService(intent, this, + Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT, + mSyncOperation.userId); + if (!bindResult) { + mBound = false; + } + return bindResult; + } + + /** + * Performs the required cleanup, which is the releasing of the wakelock and + * unbinding from the sync adapter (if actually bound). + */ + protected void close() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "unBindFromSyncAdapter: connection " + this); + } + if (mBound) { + mBound = false; + mContext.unbindService(this); + } + mSyncWakeLock.release(); + mSyncWakeLock.setWorkSource(null); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + toString(sb); + return sb.toString(); + } + + @Override + public void binderDied() { + sendSyncFinishedOrCanceledMessage(this, null); + } + } + + protected void dump(FileDescriptor fd, PrintWriter pw) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + dumpSyncState(ipw); + dumpSyncHistory(ipw); + dumpSyncAdapters(ipw); + } + + static String formatTime(long time) { + Time tobj = new Time(); + tobj.set(time); + return tobj.format("%Y-%m-%d %H:%M:%S"); + } + + protected void dumpSyncState(PrintWriter pw) { + pw.print("data connected: "); pw.println(mDataConnectionIsConnected); + pw.print("auto sync: "); + List<UserInfo> users = getAllUsers(); + if (users != null) { + for (UserInfo user : users) { + pw.print("u" + user.id + "=" + + mSyncStorageEngine.getMasterSyncAutomatically(user.id) + " "); + } + pw.println(); + } + pw.print("memory low: "); pw.println(mStorageIsLow); + + final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts(); + + pw.print("accounts: "); + if (accounts != INITIAL_ACCOUNTS_ARRAY) { + pw.println(accounts.length); + } else { + pw.println("not known yet"); + } + final long now = SystemClock.elapsedRealtime(); + pw.print("now: "); pw.print(now); + pw.println(" (" + formatTime(System.currentTimeMillis()) + ")"); + pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000)); + pw.println(" (HH:MM:SS)"); + pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000)); + pw.println(" (HH:MM:SS)"); + pw.print("time spent syncing: "); + pw.print(DateUtils.formatElapsedTime( + mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000)); + pw.print(" (HH:MM:SS), sync "); + pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not "); + pw.println("in progress"); + if (mSyncHandler.mAlarmScheduleTime != null) { + pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime); + pw.print(" ("); + pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000)); + pw.println(" (HH:MM:SS) from now)"); + } else { + pw.println("no alarm is scheduled (there had better not be any pending syncs)"); + } + + pw.print("notification info: "); + final StringBuilder sb = new StringBuilder(); + mSyncHandler.mSyncNotificationInfo.toString(sb); + pw.println(sb.toString()); + + pw.println(); + pw.println("Active Syncs: " + mActiveSyncContexts.size()); + final PackageManager pm = mContext.getPackageManager(); + for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) { + final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000; + pw.print(" "); + pw.print(DateUtils.formatElapsedTime(durationInSeconds)); + pw.print(" - "); + pw.print(activeSyncContext.mSyncOperation.dump(pm, false)); + pw.println(); + } + + synchronized (mSyncQueue) { + sb.setLength(0); + mSyncQueue.dump(sb); + } + pw.println(); + pw.print(sb.toString()); + + // join the installed sync adapter with the accounts list and emit for everything + pw.println(); + pw.println("Sync Status"); + for (AccountAndUser account : accounts) { + pw.printf("Account %s u%d %s\n", + account.account.name, account.userId, account.account.type); + + pw.println("======================================================================="); + final PrintTable table = new PrintTable(13); + table.set(0, 0, + "Authority", // 0 + "Syncable", // 1 + "Enabled", // 2 + "Delay", // 3 + "Loc", // 4 + "Poll", // 5 + "Per", // 6 + "Serv", // 7 + "User", // 8 + "Tot", // 9 + "Time", // 10 + "Last Sync", // 11 + "Periodic" // 12 + ); + + final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted = + Lists.newArrayList(); + sorted.addAll(mSyncAdapters.getAllServices(account.userId)); + Collections.sort(sorted, + new Comparator<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>() { + @Override + public int compare(RegisteredServicesCache.ServiceInfo<SyncAdapterType> lhs, + RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) { + return lhs.type.authority.compareTo(rhs.type.authority); + } + }); + for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : sorted) { + if (!syncAdapterType.type.accountType.equals(account.account.type)) { + continue; + } + int row = table.getNumRows(); + SyncStorageEngine.AuthorityInfo settings = + mSyncStorageEngine.getOrCreateAuthority( + account.account, account.userId, syncAdapterType.type.authority); + SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings); + + String authority = settings.authority; + if (authority.length() > 50) { + authority = authority.substring(authority.length() - 50); + } + table.set(row, 0, authority, settings.syncable, settings.enabled); + table.set(row, 4, + status.numSourceLocal, + status.numSourcePoll, + status.numSourcePeriodic, + status.numSourceServer, + status.numSourceUser, + status.numSyncs, + DateUtils.formatElapsedTime(status.totalElapsedTime / 1000)); + + + for (int i = 0; i < settings.periodicSyncs.size(); i++) { + final Pair<Bundle, Long> pair = settings.periodicSyncs.get(0); + final String period = String.valueOf(pair.second); + final String extras = pair.first.size() > 0 ? pair.first.toString() : ""; + final String next = formatTime(status.getPeriodicSyncTime(0) + + pair.second * 1000); + table.set(row + i * 2, 12, period + extras); + table.set(row + i * 2 + 1, 12, next); + } + + int row1 = row; + if (settings.delayUntil > now) { + table.set(row1++, 12, "D: " + (settings.delayUntil - now) / 1000); + if (settings.backoffTime > now) { + table.set(row1++, 12, "B: " + (settings.backoffTime - now) / 1000); + table.set(row1++, 12, settings.backoffDelay / 1000); + } + } + + if (status.lastSuccessTime != 0) { + table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource] + + " " + "SUCCESS"); + table.set(row1++, 11, formatTime(status.lastSuccessTime)); + } + if (status.lastFailureTime != 0) { + table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastFailureSource] + + " " + "FAILURE"); + table.set(row1++, 11, formatTime(status.lastFailureTime)); + //noinspection UnusedAssignment + table.set(row1++, 11, status.lastFailureMesg); + } + } + table.writeTo(pw); + } + } + + private String getLastFailureMessage(int code) { + switch (code) { + case ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS: + return "sync already in progress"; + + case ContentResolver.SYNC_ERROR_AUTHENTICATION: + return "authentication error"; + + case ContentResolver.SYNC_ERROR_IO: + return "I/O error"; + + case ContentResolver.SYNC_ERROR_PARSE: + return "parse error"; + + case ContentResolver.SYNC_ERROR_CONFLICT: + return "conflict error"; + + case ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS: + return "too many deletions error"; + + case ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES: + return "too many retries error"; + + case ContentResolver.SYNC_ERROR_INTERNAL: + return "internal error"; + + default: + return "unknown"; + } + } + + private void dumpTimeSec(PrintWriter pw, long time) { + pw.print(time/1000); pw.print('.'); pw.print((time/100)%10); + pw.print('s'); + } + + private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) { + pw.print("Success ("); pw.print(ds.successCount); + if (ds.successCount > 0) { + pw.print(" for "); dumpTimeSec(pw, ds.successTime); + pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount); + } + pw.print(") Failure ("); pw.print(ds.failureCount); + if (ds.failureCount > 0) { + pw.print(" for "); dumpTimeSec(pw, ds.failureTime); + pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount); + } + pw.println(")"); + } + + protected void dumpSyncHistory(PrintWriter pw) { + dumpRecentHistory(pw); + dumpDayStatistics(pw); + } + + private void dumpRecentHistory(PrintWriter pw) { + final ArrayList<SyncStorageEngine.SyncHistoryItem> items + = mSyncStorageEngine.getSyncHistory(); + if (items != null && items.size() > 0) { + final Map<String, AuthoritySyncStats> authorityMap = Maps.newHashMap(); + long totalElapsedTime = 0; + long totalTimes = 0; + final int N = items.size(); + + int maxAuthority = 0; + int maxAccount = 0; + for (SyncStorageEngine.SyncHistoryItem item : items) { + SyncStorageEngine.AuthorityInfo authority + = mSyncStorageEngine.getAuthority(item.authorityId); + final String authorityName; + final String accountKey; + if (authority != null) { + authorityName = authority.authority; + accountKey = authority.account.name + "/" + authority.account.type + + " u" + authority.userId; + } else { + authorityName = "Unknown"; + accountKey = "Unknown"; + } + + int length = authorityName.length(); + if (length > maxAuthority) { + maxAuthority = length; + } + length = accountKey.length(); + if (length > maxAccount) { + maxAccount = length; + } + + final long elapsedTime = item.elapsedTime; + totalElapsedTime += elapsedTime; + totalTimes++; + AuthoritySyncStats authoritySyncStats = authorityMap.get(authorityName); + if (authoritySyncStats == null) { + authoritySyncStats = new AuthoritySyncStats(authorityName); + authorityMap.put(authorityName, authoritySyncStats); + } + authoritySyncStats.elapsedTime += elapsedTime; + authoritySyncStats.times++; + final Map<String, AccountSyncStats> accountMap = authoritySyncStats.accountMap; + AccountSyncStats accountSyncStats = accountMap.get(accountKey); + if (accountSyncStats == null) { + accountSyncStats = new AccountSyncStats(accountKey); + accountMap.put(accountKey, accountSyncStats); + } + accountSyncStats.elapsedTime += elapsedTime; + accountSyncStats.times++; + + } + + if (totalElapsedTime > 0) { + pw.println(); + pw.printf("Detailed Statistics (Recent history): " + + "%d (# of times) %ds (sync time)\n", + totalTimes, totalElapsedTime / 1000); + + final List<AuthoritySyncStats> sortedAuthorities = + new ArrayList<AuthoritySyncStats>(authorityMap.values()); + Collections.sort(sortedAuthorities, new Comparator<AuthoritySyncStats>() { + @Override + public int compare(AuthoritySyncStats lhs, AuthoritySyncStats rhs) { + // reverse order + int compare = Integer.compare(rhs.times, lhs.times); + if (compare == 0) { + compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime); + } + return compare; + } + }); + + final int maxLength = Math.max(maxAuthority, maxAccount + 3); + final int padLength = 2 + 2 + maxLength + 2 + 10 + 11; + final char chars[] = new char[padLength]; + Arrays.fill(chars, '-'); + final String separator = new String(chars); + + final String authorityFormat = + String.format(" %%-%ds: %%-9s %%-11s\n", maxLength + 2); + final String accountFormat = + String.format(" %%-%ds: %%-9s %%-11s\n", maxLength); + + pw.println(separator); + for (AuthoritySyncStats authoritySyncStats : sortedAuthorities) { + String name = authoritySyncStats.name; + long elapsedTime; + int times; + String timeStr; + String timesStr; + + elapsedTime = authoritySyncStats.elapsedTime; + times = authoritySyncStats.times; + timeStr = String.format("%ds/%d%%", + elapsedTime / 1000, + elapsedTime * 100 / totalElapsedTime); + timesStr = String.format("%d/%d%%", + times, + times * 100 / totalTimes); + pw.printf(authorityFormat, name, timesStr, timeStr); + + final List<AccountSyncStats> sortedAccounts = + new ArrayList<AccountSyncStats>( + authoritySyncStats.accountMap.values()); + Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() { + @Override + public int compare(AccountSyncStats lhs, AccountSyncStats rhs) { + // reverse order + int compare = Integer.compare(rhs.times, lhs.times); + if (compare == 0) { + compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime); + } + return compare; + } + }); + for (AccountSyncStats stats: sortedAccounts) { + elapsedTime = stats.elapsedTime; + times = stats.times; + timeStr = String.format("%ds/%d%%", + elapsedTime / 1000, + elapsedTime * 100 / totalElapsedTime); + timesStr = String.format("%d/%d%%", + times, + times * 100 / totalTimes); + pw.printf(accountFormat, stats.name, timesStr, timeStr); + } + pw.println(separator); + } + } + + pw.println(); + pw.println("Recent Sync History"); + final String format = " %-" + maxAccount + "s %-" + maxAuthority + "s %s\n"; + final Map<String, Long> lastTimeMap = Maps.newHashMap(); + final PackageManager pm = mContext.getPackageManager(); + for (int i = 0; i < N; i++) { + SyncStorageEngine.SyncHistoryItem item = items.get(i); + SyncStorageEngine.AuthorityInfo authority + = mSyncStorageEngine.getAuthority(item.authorityId); + final String authorityName; + final String accountKey; + if (authority != null) { + authorityName = authority.authority; + accountKey = authority.account.name + "/" + authority.account.type + + " u" + authority.userId; + } else { + authorityName = "Unknown"; + accountKey = "Unknown"; + } + final long elapsedTime = item.elapsedTime; + final Time time = new Time(); + final long eventTime = item.eventTime; + time.set(eventTime); + + final String key = authorityName + "/" + accountKey; + final Long lastEventTime = lastTimeMap.get(key); + final String diffString; + if (lastEventTime == null) { + diffString = ""; + } else { + final long diff = (lastEventTime - eventTime) / 1000; + if (diff < 60) { + diffString = String.valueOf(diff); + } else if (diff < 3600) { + diffString = String.format("%02d:%02d", diff / 60, diff % 60); + } else { + final long sec = diff % 3600; + diffString = String.format("%02d:%02d:%02d", + diff / 3600, sec / 60, sec % 60); + } + } + lastTimeMap.put(key, eventTime); + + pw.printf(" #%-3d: %s %8s %5.1fs %8s", + i + 1, + formatTime(eventTime), + SyncStorageEngine.SOURCES[item.source], + ((float) elapsedTime) / 1000, + diffString); + pw.printf(format, accountKey, authorityName, + SyncOperation.reasonToString(pm, item.reason)); + + if (item.event != SyncStorageEngine.EVENT_STOP + || item.upstreamActivity != 0 + || item.downstreamActivity != 0) { + pw.printf(" event=%d upstreamActivity=%d downstreamActivity=%d\n", + item.event, + item.upstreamActivity, + item.downstreamActivity); + } + if (item.mesg != null + && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) { + pw.printf(" mesg=%s\n", item.mesg); + } + } + pw.println(); + pw.println("Recent Sync History Extras"); + for (int i = 0; i < N; i++) { + final SyncStorageEngine.SyncHistoryItem item = items.get(i); + final Bundle extras = item.extras; + if (extras == null || extras.size() == 0) { + continue; + } + final SyncStorageEngine.AuthorityInfo authority + = mSyncStorageEngine.getAuthority(item.authorityId); + final String authorityName; + final String accountKey; + if (authority != null) { + authorityName = authority.authority; + accountKey = authority.account.name + "/" + authority.account.type + + " u" + authority.userId; + } else { + authorityName = "Unknown"; + accountKey = "Unknown"; + } + final Time time = new Time(); + final long eventTime = item.eventTime; + time.set(eventTime); + + pw.printf(" #%-3d: %s %8s ", + i + 1, + formatTime(eventTime), + SyncStorageEngine.SOURCES[item.source]); + + pw.printf(format, accountKey, authorityName, extras); + } + } + } + + private void dumpDayStatistics(PrintWriter pw) { + SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics(); + if (dses != null && dses[0] != null) { + pw.println(); + pw.println("Sync Statistics"); + pw.print(" Today: "); dumpDayStatistic(pw, dses[0]); + int today = dses[0].day; + int i; + SyncStorageEngine.DayStats ds; + + // Print each day in the current week. + for (i=1; i<=6 && i < dses.length; i++) { + ds = dses[i]; + if (ds == null) break; + int delta = today-ds.day; + if (delta > 6) break; + + pw.print(" Day-"); pw.print(delta); pw.print(": "); + dumpDayStatistic(pw, ds); + } + + // Aggregate all following days into weeks and print totals. + int weekDay = today; + while (i < dses.length) { + SyncStorageEngine.DayStats aggr = null; + weekDay -= 7; + while (i < dses.length) { + ds = dses[i]; + if (ds == null) { + i = dses.length; + break; + } + int delta = weekDay-ds.day; + if (delta > 6) break; + i++; + + if (aggr == null) { + aggr = new SyncStorageEngine.DayStats(weekDay); + } + aggr.successCount += ds.successCount; + aggr.successTime += ds.successTime; + aggr.failureCount += ds.failureCount; + aggr.failureTime += ds.failureTime; + } + if (aggr != null) { + pw.print(" Week-"); pw.print((today-weekDay)/7); pw.print(": "); + dumpDayStatistic(pw, aggr); + } + } + } + } + + private void dumpSyncAdapters(IndentingPrintWriter pw) { + pw.println(); + final List<UserInfo> users = getAllUsers(); + if (users != null) { + for (UserInfo user : users) { + pw.println("Sync adapters for " + user + ":"); + pw.increaseIndent(); + for (RegisteredServicesCache.ServiceInfo<?> info : + mSyncAdapters.getAllServices(user.id)) { + pw.println(info); + } + pw.decreaseIndent(); + pw.println(); + } + } + } + + private static class AuthoritySyncStats { + String name; + long elapsedTime; + int times; + Map<String, AccountSyncStats> accountMap = Maps.newHashMap(); + + private AuthoritySyncStats(String name) { + this.name = name; + } + } + + private static class AccountSyncStats { + String name; + long elapsedTime; + int times; + + private AccountSyncStats(String name) { + this.name = name; + } + } + + /** + * A helper object to keep track of the time we have spent syncing since the last boot + */ + private class SyncTimeTracker { + /** True if a sync was in progress on the most recent call to update() */ + boolean mLastWasSyncing = false; + /** Used to track when lastWasSyncing was last set */ + long mWhenSyncStarted = 0; + /** The cumulative time we have spent syncing */ + private long mTimeSpentSyncing; + + /** Call to let the tracker know that the sync state may have changed */ + public synchronized void update() { + final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty(); + if (isSyncInProgress == mLastWasSyncing) return; + final long now = SystemClock.elapsedRealtime(); + if (isSyncInProgress) { + mWhenSyncStarted = now; + } else { + mTimeSpentSyncing += now - mWhenSyncStarted; + } + mLastWasSyncing = isSyncInProgress; + } + + /** Get how long we have been syncing, in ms */ + public synchronized long timeSpentSyncing() { + if (!mLastWasSyncing) return mTimeSpentSyncing; + + final long now = SystemClock.elapsedRealtime(); + return mTimeSpentSyncing + (now - mWhenSyncStarted); + } + } + + class ServiceConnectionData { + public final ActiveSyncContext activeSyncContext; + public final ISyncAdapter syncAdapter; + ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) { + this.activeSyncContext = activeSyncContext; + this.syncAdapter = syncAdapter; + } + } + + /** + * Handles SyncOperation Messages that are posted to the associated + * HandlerThread. + */ + class SyncHandler extends Handler { + // Messages that can be sent on mHandler + private static final int MESSAGE_SYNC_FINISHED = 1; + private static final int MESSAGE_SYNC_ALARM = 2; + private static final int MESSAGE_CHECK_ALARMS = 3; + private static final int MESSAGE_SERVICE_CONNECTED = 4; + private static final int MESSAGE_SERVICE_DISCONNECTED = 5; + private static final int MESSAGE_CANCEL = 6; + + public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo(); + private Long mAlarmScheduleTime = null; + public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker(); + private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks = + Maps.newHashMap(); + + private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1); + + public void onBootCompleted() { + mBootCompleted = true; + + doDatabaseCleanup(); + + if (mReadyToRunLatch != null) { + mReadyToRunLatch.countDown(); + } + } + + private PowerManager.WakeLock getSyncWakeLock(Account account, String authority) { + final Pair<Account, String> wakeLockKey = Pair.create(account, authority); + PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey); + if (wakeLock == null) { + final String name = SYNC_WAKE_LOCK_PREFIX + "_" + authority + "_" + account; + wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); + wakeLock.setReferenceCounted(false); + mWakeLocks.put(wakeLockKey, wakeLock); + } + return wakeLock; + } + + private void waitUntilReadyToRun() { + CountDownLatch latch = mReadyToRunLatch; + if (latch != null) { + while (true) { + try { + latch.await(); + mReadyToRunLatch = null; + return; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + /** + * Used to keep track of whether a sync notification is active and who it is for. + */ + class SyncNotificationInfo { + // true iff the notification manager has been asked to send the notification + public boolean isActive = false; + + // Set when we transition from not running a sync to running a sync, and cleared on + // the opposite transition. + public Long startTime = null; + + public void toString(StringBuilder sb) { + sb.append("isActive ").append(isActive).append(", startTime ").append(startTime); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + toString(sb); + return sb.toString(); + } + } + + public SyncHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + long earliestFuturePollTime = Long.MAX_VALUE; + long nextPendingSyncTime = Long.MAX_VALUE; + + // Setting the value here instead of a method because we want the dumpsys logs + // to have the most recent value used. + try { + waitUntilReadyToRun(); + mDataConnectionIsConnected = readDataConnectionState(); + mSyncManagerWakeLock.acquire(); + // Always do this first so that we be sure that any periodic syncs that + // are ready to run have been converted into pending syncs. This allows the + // logic that considers the next steps to take based on the set of pending syncs + // to also take into account the periodic syncs. + earliestFuturePollTime = scheduleReadyPeriodicSyncs(); + switch (msg.what) { + case SyncHandler.MESSAGE_CANCEL: { + Pair<Account, String> payload = (Pair<Account, String>)msg.obj; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: " + + payload.first + ", " + payload.second); + } + cancelActiveSyncLocked(payload.first, msg.arg1, payload.second); + nextPendingSyncTime = maybeStartNextSyncLocked(); + break; + } + + case SyncHandler.MESSAGE_SYNC_FINISHED: + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED"); + } + SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj; + if (!isSyncStillActive(payload.activeSyncContext)) { + Log.d(TAG, "handleSyncHandlerMessage: dropping since the " + + "sync is no longer active: " + + payload.activeSyncContext); + break; + } + runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext); + + // since a sync just finished check if it is time to start a new sync + nextPendingSyncTime = maybeStartNextSyncLocked(); + break; + + case SyncHandler.MESSAGE_SERVICE_CONNECTED: { + ServiceConnectionData msgData = (ServiceConnectionData)msg.obj; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: " + + msgData.activeSyncContext); + } + // check that this isn't an old message + if (isSyncStillActive(msgData.activeSyncContext)) { + runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter); + } + break; + } + + case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: { + final ActiveSyncContext currentSyncContext = + ((ServiceConnectionData)msg.obj).activeSyncContext; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: " + + currentSyncContext); + } + // check that this isn't an old message + if (isSyncStillActive(currentSyncContext)) { + // cancel the sync if we have a syncadapter, which means one is + // outstanding + if (currentSyncContext.mSyncAdapter != null) { + try { + currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext); + } catch (RemoteException e) { + // we don't need to retry this in this case + } + } + + // pretend that the sync failed with an IOException, + // which is a soft error + SyncResult syncResult = new SyncResult(); + syncResult.stats.numIoExceptions++; + runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext); + + // since a sync just finished check if it is time to start a new sync + nextPendingSyncTime = maybeStartNextSyncLocked(); + } + + break; + } + + case SyncHandler.MESSAGE_SYNC_ALARM: { + boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); + if (isLoggable) { + Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM"); + } + mAlarmScheduleTime = null; + try { + nextPendingSyncTime = maybeStartNextSyncLocked(); + } finally { + mHandleAlarmWakeLock.release(); + } + break; + } + + case SyncHandler.MESSAGE_CHECK_ALARMS: + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS"); + } + nextPendingSyncTime = maybeStartNextSyncLocked(); + break; + } + } finally { + manageSyncNotificationLocked(); + manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime); + mSyncTimeTracker.update(); + mSyncManagerWakeLock.release(); + } + } + + /** + * Turn any periodic sync operations that are ready to run into pending sync operations. + * @return the desired start time of the earliest future periodic sync operation, + * in milliseconds since boot + */ + private long scheduleReadyPeriodicSyncs() { + final boolean backgroundDataUsageAllowed = + getConnectivityManager().getBackgroundDataSetting(); + long earliestFuturePollTime = Long.MAX_VALUE; + if (!backgroundDataUsageAllowed) { + return earliestFuturePollTime; + } + + AccountAndUser[] accounts = mRunningAccounts; + + final long nowAbsolute = System.currentTimeMillis(); + final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis) + ? (nowAbsolute - mSyncRandomOffsetMillis) : 0; + + ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities(); + for (SyncStorageEngine.AuthorityInfo info : infos) { + // skip the sync if the account of this operation no longer exists + if (!containsAccountAndUser(accounts, info.account, info.userId)) { + continue; + } + + if (!mSyncStorageEngine.getMasterSyncAutomatically(info.userId) + || !mSyncStorageEngine.getSyncAutomatically(info.account, info.userId, + info.authority)) { + continue; + } + + if (mSyncStorageEngine.getIsSyncable(info.account, info.userId, info.authority) + == 0) { + continue; + } + + SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info); + for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) { + final Bundle extras = info.periodicSyncs.get(i).first; + final Long periodInMillis = info.periodicSyncs.get(i).second * 1000; + // find when this periodic sync was last scheduled to run + final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i); + + long remainingMillis + = periodInMillis - (shiftedNowAbsolute % periodInMillis); + + /* + * Sync scheduling strategy: + * Set the next periodic sync based on a random offset (in seconds). + * + * Also sync right now if any of the following cases hold + * and mark it as having been scheduled + * + * Case 1: This sync is ready to run now. + * Case 2: If the lastPollTimeAbsolute is in the future, + * sync now and reinitialize. This can happen for + * example if the user changed the time, synced and + * changed back. + * Case 3: If we failed to sync at the last scheduled time + */ + if (remainingMillis == periodInMillis // Case 1 + || lastPollTimeAbsolute > nowAbsolute // Case 2 + || (nowAbsolute - lastPollTimeAbsolute + >= periodInMillis)) { // Case 3 + // Sync now + final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff( + info.account, info.userId, info.authority); + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; + syncAdapterInfo = mSyncAdapters.getServiceInfo( + SyncAdapterType.newKey(info.authority, info.account.type), + info.userId); + if (syncAdapterInfo == null) { + continue; + } + scheduleSyncOperation( + new SyncOperation(info.account, info.userId, + SyncOperation.REASON_PERIODIC, + SyncStorageEngine.SOURCE_PERIODIC, + info.authority, extras, 0 /* delay */, + backoff != null ? backoff.first : 0, + mSyncStorageEngine.getDelayUntilTime( + info.account, info.userId, info.authority), + syncAdapterInfo.type.allowParallelSyncs())); + status.setPeriodicSyncTime(i, nowAbsolute); + } + // Compute when this periodic sync should next run + final long nextPollTimeAbsolute = nowAbsolute + remainingMillis; + + // remember this time if it is earlier than earliestFuturePollTime + if (nextPollTimeAbsolute < earliestFuturePollTime) { + earliestFuturePollTime = nextPollTimeAbsolute; + } + } + } + + if (earliestFuturePollTime == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + + // convert absolute time to elapsed time + return SystemClock.elapsedRealtime() + + ((earliestFuturePollTime < nowAbsolute) + ? 0 + : (earliestFuturePollTime - nowAbsolute)); + } + + private long maybeStartNextSyncLocked() { + final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); + if (isLoggable) Log.v(TAG, "maybeStartNextSync"); + + // If we aren't ready to run (e.g. the data connection is down), get out. + if (!mDataConnectionIsConnected) { + if (isLoggable) { + Log.v(TAG, "maybeStartNextSync: no data connection, skipping"); + } + return Long.MAX_VALUE; + } + + if (mStorageIsLow) { + if (isLoggable) { + Log.v(TAG, "maybeStartNextSync: memory low, skipping"); + } + return Long.MAX_VALUE; + } + + // If the accounts aren't known yet then we aren't ready to run. We will be kicked + // when the account lookup request does complete. + AccountAndUser[] accounts = mRunningAccounts; + if (accounts == INITIAL_ACCOUNTS_ARRAY) { + if (isLoggable) { + Log.v(TAG, "maybeStartNextSync: accounts not known, skipping"); + } + return Long.MAX_VALUE; + } + + // Otherwise consume SyncOperations from the head of the SyncQueue until one is + // found that is runnable (not disabled, etc). If that one is ready to run then + // start it, otherwise just get out. + final boolean backgroundDataUsageAllowed = + getConnectivityManager().getBackgroundDataSetting(); + + final long now = SystemClock.elapsedRealtime(); + + // will be set to the next time that a sync should be considered for running + long nextReadyToRunTime = Long.MAX_VALUE; + + // order the sync queue, dropping syncs that are not allowed + ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>(); + synchronized (mSyncQueue) { + if (isLoggable) { + Log.v(TAG, "build the operation array, syncQueue size is " + + mSyncQueue.getOperations().size()); + } + final Iterator<SyncOperation> operationIterator = mSyncQueue.getOperations() + .iterator(); + + final ActivityManager activityManager + = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + final Set<Integer> removedUsers = Sets.newHashSet(); + while (operationIterator.hasNext()) { + final SyncOperation op = operationIterator.next(); + + // drop the sync if the account of this operation no longer exists + if (!containsAccountAndUser(accounts, op.account, op.userId)) { + operationIterator.remove(); + mSyncStorageEngine.deleteFromPending(op.pendingOperation); + continue; + } + + // drop this sync request if it isn't syncable + int syncableState = mSyncStorageEngine.getIsSyncable( + op.account, op.userId, op.authority); + if (syncableState == 0) { + operationIterator.remove(); + mSyncStorageEngine.deleteFromPending(op.pendingOperation); + continue; + } + + // if the user in not running, drop the request + if (!activityManager.isUserRunning(op.userId)) { + final UserInfo userInfo = mUserManager.getUserInfo(op.userId); + if (userInfo == null) { + removedUsers.add(op.userId); + } + continue; + } + + // if the next run time is in the future, meaning there are no syncs ready + // to run, return the time + if (op.effectiveRunTime > now) { + if (nextReadyToRunTime > op.effectiveRunTime) { + nextReadyToRunTime = op.effectiveRunTime; + } + continue; + } + + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; + syncAdapterInfo = mSyncAdapters.getServiceInfo( + SyncAdapterType.newKey(op.authority, op.account.type), op.userId); + + // only proceed if network is connected for requesting UID + final boolean uidNetworkConnected; + if (syncAdapterInfo != null) { + final NetworkInfo networkInfo = getConnectivityManager() + .getActiveNetworkInfoForUid(syncAdapterInfo.uid); + uidNetworkConnected = networkInfo != null && networkInfo.isConnected(); + } else { + uidNetworkConnected = false; + } + + // skip the sync if it isn't manual, and auto sync or + // background data usage is disabled or network is + // disconnected for the target UID. + if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false) + && (syncableState > 0) + && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId) + || !backgroundDataUsageAllowed + || !uidNetworkConnected + || !mSyncStorageEngine.getSyncAutomatically( + op.account, op.userId, op.authority))) { + operationIterator.remove(); + mSyncStorageEngine.deleteFromPending(op.pendingOperation); + continue; + } + + operations.add(op); + } + for (Integer user : removedUsers) { + // if it's still removed + if (mUserManager.getUserInfo(user) == null) { + onUserRemoved(user); + } + } + } + + // find the next operation to dispatch, if one is ready + // iterate from the top, keep issuing (while potentially cancelling existing syncs) + // until the quotas are filled. + // once the quotas are filled iterate once more to find when the next one would be + // (also considering pre-emption reasons). + if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size()); + Collections.sort(operations); + if (isLoggable) Log.v(TAG, "dispatch all ready sync operations"); + for (int i = 0, N = operations.size(); i < N; i++) { + final SyncOperation candidate = operations.get(i); + final boolean candidateIsInitialization = candidate.isInitialization(); + + int numInit = 0; + int numRegular = 0; + ActiveSyncContext conflict = null; + ActiveSyncContext longRunning = null; + ActiveSyncContext toReschedule = null; + ActiveSyncContext oldestNonExpeditedRegular = null; + + for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) { + final SyncOperation activeOp = activeSyncContext.mSyncOperation; + if (activeOp.isInitialization()) { + numInit++; + } else { + numRegular++; + if (!activeOp.isExpedited()) { + if (oldestNonExpeditedRegular == null + || (oldestNonExpeditedRegular.mStartTime + > activeSyncContext.mStartTime)) { + oldestNonExpeditedRegular = activeSyncContext; + } + } + } + if (activeOp.account.type.equals(candidate.account.type) + && activeOp.authority.equals(candidate.authority) + && activeOp.userId == candidate.userId + && (!activeOp.allowParallelSyncs + || activeOp.account.name.equals(candidate.account.name))) { + conflict = activeSyncContext; + // don't break out since we want to do a full count of the varieties + } else { + if (candidateIsInitialization == activeOp.isInitialization() + && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) { + longRunning = activeSyncContext; + // don't break out since we want to do a full count of the varieties + } + } + } + + if (isLoggable) { + Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate); + Log.v(TAG, " numActiveInit=" + numInit + ", numActiveRegular=" + numRegular); + Log.v(TAG, " longRunning: " + longRunning); + Log.v(TAG, " conflict: " + conflict); + Log.v(TAG, " oldestNonExpeditedRegular: " + oldestNonExpeditedRegular); + } + + final boolean roomAvailable = candidateIsInitialization + ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS + : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS; + + if (conflict != null) { + if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization() + && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) { + toReschedule = conflict; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "canceling and rescheduling sync since an initialization " + + "takes higher priority, " + conflict); + } + } else if (candidate.expedited && !conflict.mSyncOperation.expedited + && (candidateIsInitialization + == conflict.mSyncOperation.isInitialization())) { + toReschedule = conflict; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "canceling and rescheduling sync since an expedited " + + "takes higher priority, " + conflict); + } + } else { + continue; + } + } else if (roomAvailable) { + // dispatch candidate + } else if (candidate.isExpedited() && oldestNonExpeditedRegular != null + && !candidateIsInitialization) { + // We found an active, non-expedited regular sync. We also know that the + // candidate doesn't conflict with this active sync since conflict + // is null. Reschedule the active sync and start the candidate. + toReschedule = oldestNonExpeditedRegular; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to run, " + + oldestNonExpeditedRegular); + } + } else if (longRunning != null + && (candidateIsInitialization + == longRunning.mSyncOperation.isInitialization())) { + // We found an active, long-running sync. Reschedule the active + // sync and start the candidate. + toReschedule = longRunning; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "canceling and rescheduling sync since it ran roo long, " + + longRunning); + } + } else { + // we were unable to find or make space to run this candidate, go on to + // the next one + continue; + } + + if (toReschedule != null) { + runSyncFinishedOrCanceledLocked(null, toReschedule); + scheduleSyncOperation(toReschedule.mSyncOperation); + } + synchronized (mSyncQueue) { + mSyncQueue.remove(candidate); + } + dispatchSyncOperation(candidate); + } + + return nextReadyToRunTime; + } + + private boolean dispatchSyncOperation(SyncOperation op) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "dispatchSyncOperation: we are going to sync " + op); + Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size()); + for (ActiveSyncContext syncContext : mActiveSyncContexts) { + Log.v(TAG, syncContext.toString()); + } + } + + // connect to the sync adapter + SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type); + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; + syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, op.userId); + if (syncAdapterInfo == null) { + Log.d(TAG, "can't find a sync adapter for " + syncAdapterType + + ", removing settings for it"); + mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority); + return false; + } + + ActiveSyncContext activeSyncContext = + new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid); + activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext); + mActiveSyncContexts.add(activeSyncContext); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext); + } + if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) { + Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo); + closeActiveSyncContext(activeSyncContext); + return false; + } + + return true; + } + + private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext, + ISyncAdapter syncAdapter) { + activeSyncContext.mSyncAdapter = syncAdapter; + final SyncOperation syncOperation = activeSyncContext.mSyncOperation; + try { + activeSyncContext.mIsLinkedToDeath = true; + syncAdapter.asBinder().linkToDeath(activeSyncContext, 0); + + syncAdapter.startSync(activeSyncContext, syncOperation.authority, + syncOperation.account, syncOperation.extras); + } catch (RemoteException remoteExc) { + Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc); + closeActiveSyncContext(activeSyncContext); + increaseBackoffSetting(syncOperation); + scheduleSyncOperation(new SyncOperation(syncOperation)); + } catch (RuntimeException exc) { + closeActiveSyncContext(activeSyncContext); + Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc); + } + } + + private void cancelActiveSyncLocked(Account account, int userId, String authority) { + ArrayList<ActiveSyncContext> activeSyncs = + new ArrayList<ActiveSyncContext>(mActiveSyncContexts); + for (ActiveSyncContext activeSyncContext : activeSyncs) { + if (activeSyncContext != null) { + // if an account was specified then only cancel the sync if it matches + if (account != null) { + if (!account.equals(activeSyncContext.mSyncOperation.account)) { + continue; + } + } + // if an authority was specified then only cancel the sync if it matches + if (authority != null) { + if (!authority.equals(activeSyncContext.mSyncOperation.authority)) { + continue; + } + } + // check if the userid matches + if (userId != UserHandle.USER_ALL + && userId != activeSyncContext.mSyncOperation.userId) { + continue; + } + runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */, + activeSyncContext); + } + } + } + + private void runSyncFinishedOrCanceledLocked(SyncResult syncResult, + ActiveSyncContext activeSyncContext) { + boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); + + if (activeSyncContext.mIsLinkedToDeath) { + activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0); + activeSyncContext.mIsLinkedToDeath = false; + } + closeActiveSyncContext(activeSyncContext); + + final SyncOperation syncOperation = activeSyncContext.mSyncOperation; + + final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime; + + String historyMessage; + int downstreamActivity; + int upstreamActivity; + if (syncResult != null) { + if (isLoggable) { + Log.v(TAG, "runSyncFinishedOrCanceled [finished]: " + + syncOperation + ", result " + syncResult); + } + + if (!syncResult.hasError()) { + historyMessage = SyncStorageEngine.MESG_SUCCESS; + // TODO: set these correctly when the SyncResult is extended to include it + downstreamActivity = 0; + upstreamActivity = 0; + clearBackoffSetting(syncOperation); + } else { + Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult); + // the operation failed so increase the backoff time + if (!syncResult.syncAlreadyInProgress) { + increaseBackoffSetting(syncOperation); + } + // reschedule the sync if so indicated by the syncResult + maybeRescheduleSync(syncResult, syncOperation); + historyMessage = ContentResolver.syncErrorToString( + syncResultToErrorNumber(syncResult)); + // TODO: set these correctly when the SyncResult is extended to include it + downstreamActivity = 0; + upstreamActivity = 0; + } + + setDelayUntilTime(syncOperation, syncResult.delayUntil); + } else { + if (isLoggable) { + Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation); + } + if (activeSyncContext.mSyncAdapter != null) { + try { + activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext); + } catch (RemoteException e) { + // we don't need to retry this in this case + } + } + historyMessage = SyncStorageEngine.MESG_CANCELED; + downstreamActivity = 0; + upstreamActivity = 0; + } + + stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage, + upstreamActivity, downstreamActivity, elapsedTime); + + if (syncResult != null && syncResult.tooManyDeletions) { + installHandleTooManyDeletesNotification(syncOperation.account, + syncOperation.authority, syncResult.stats.numDeletes, + syncOperation.userId); + } else { + mNotificationMgr.cancelAsUser(null, + syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(), + new UserHandle(syncOperation.userId)); + } + + if (syncResult != null && syncResult.fullSyncRequested) { + scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId, + syncOperation.reason, + syncOperation.syncSource, syncOperation.authority, new Bundle(), 0, + syncOperation.backoff, syncOperation.delayUntil, + syncOperation.allowParallelSyncs)); + } + // no need to schedule an alarm, as that will be done by our caller. + } + + private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) { + activeSyncContext.close(); + mActiveSyncContexts.remove(activeSyncContext); + mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo, + activeSyncContext.mSyncOperation.userId); + } + + /** + * Convert the error-containing SyncResult into the Sync.History error number. Since + * the SyncResult may indicate multiple errors at once, this method just returns the + * most "serious" error. + * @param syncResult the SyncResult from which to read + * @return the most "serious" error set in the SyncResult + * @throws IllegalStateException if the SyncResult does not indicate any errors. + * If SyncResult.error() is true then it is safe to call this. + */ + private int syncResultToErrorNumber(SyncResult syncResult) { + if (syncResult.syncAlreadyInProgress) + return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS; + if (syncResult.stats.numAuthExceptions > 0) + return ContentResolver.SYNC_ERROR_AUTHENTICATION; + if (syncResult.stats.numIoExceptions > 0) + return ContentResolver.SYNC_ERROR_IO; + if (syncResult.stats.numParseExceptions > 0) + return ContentResolver.SYNC_ERROR_PARSE; + if (syncResult.stats.numConflictDetectedExceptions > 0) + return ContentResolver.SYNC_ERROR_CONFLICT; + if (syncResult.tooManyDeletions) + return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS; + if (syncResult.tooManyRetries) + return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES; + if (syncResult.databaseError) + return ContentResolver.SYNC_ERROR_INTERNAL; + throw new IllegalStateException("we are not in an error state, " + syncResult); + } + + private void manageSyncNotificationLocked() { + boolean shouldCancel; + boolean shouldInstall; + + if (mActiveSyncContexts.isEmpty()) { + mSyncNotificationInfo.startTime = null; + + // we aren't syncing. if the notification is active then remember that we need + // to cancel it and then clear out the info + shouldCancel = mSyncNotificationInfo.isActive; + shouldInstall = false; + } else { + // we are syncing + final long now = SystemClock.elapsedRealtime(); + if (mSyncNotificationInfo.startTime == null) { + mSyncNotificationInfo.startTime = now; + } + + // there are three cases: + // - the notification is up: do nothing + // - the notification is not up but it isn't time yet: don't install + // - the notification is not up and it is time: need to install + + if (mSyncNotificationInfo.isActive) { + shouldInstall = shouldCancel = false; + } else { + // it isn't currently up, so there is nothing to cancel + shouldCancel = false; + + final boolean timeToShowNotification = + now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY; + if (timeToShowNotification) { + shouldInstall = true; + } else { + // show the notification immediately if this is a manual sync + shouldInstall = false; + for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) { + final boolean manualSync = activeSyncContext.mSyncOperation.extras + .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); + if (manualSync) { + shouldInstall = true; + break; + } + } + } + } + } + + if (shouldCancel && !shouldInstall) { + mNeedSyncActiveNotification = false; + sendSyncStateIntent(); + mSyncNotificationInfo.isActive = false; + } + + if (shouldInstall) { + mNeedSyncActiveNotification = true; + sendSyncStateIntent(); + mSyncNotificationInfo.isActive = true; + } + } + + private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime, + long nextPendingEventElapsedTime) { + // in each of these cases the sync loop will be kicked, which will cause this + // method to be called again + if (!mDataConnectionIsConnected) return; + if (mStorageIsLow) return; + + // When the status bar notification should be raised + final long notificationTime = + (!mSyncHandler.mSyncNotificationInfo.isActive + && mSyncHandler.mSyncNotificationInfo.startTime != null) + ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY + : Long.MAX_VALUE; + + // When we should consider canceling an active sync + long earliestTimeoutTime = Long.MAX_VALUE; + for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { + final long currentSyncTimeoutTime = + currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is " + + currentSyncTimeoutTime); + } + if (earliestTimeoutTime > currentSyncTimeoutTime) { + earliestTimeoutTime = currentSyncTimeoutTime; + } + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime); + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime); + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is " + + nextPeriodicEventElapsedTime); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is " + + nextPendingEventElapsedTime); + } + + long alarmTime = Math.min(notificationTime, earliestTimeoutTime); + alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime); + alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime); + + // Bound the alarm time. + final long now = SystemClock.elapsedRealtime(); + if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, " + + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN)); + } + alarmTime = now + SYNC_ALARM_TIMEOUT_MIN; + } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, " + + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN)); + } + alarmTime = now + SYNC_ALARM_TIMEOUT_MAX; + } + + // determine if we need to set or cancel the alarm + boolean shouldSet = false; + boolean shouldCancel = false; + final boolean alarmIsActive = mAlarmScheduleTime != null; + final boolean needAlarm = alarmTime != Long.MAX_VALUE; + if (needAlarm) { + if (!alarmIsActive || alarmTime < mAlarmScheduleTime) { + shouldSet = true; + } + } else { + shouldCancel = alarmIsActive; + } + + // set or cancel the alarm as directed + ensureAlarmService(); + if (shouldSet) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time " + + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000) + + " secs from now"); + } + mAlarmScheduleTime = alarmTime; + mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, + mSyncAlarmIntent); + } else if (shouldCancel) { + mAlarmScheduleTime = null; + mAlarmService.cancel(mSyncAlarmIntent); + } + } + + private void sendSyncStateIntent() { + Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED); + syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + syncStateIntent.putExtra("active", mNeedSyncActiveNotification); + syncStateIntent.putExtra("failing", false); + mContext.sendBroadcastAsUser(syncStateIntent, UserHandle.OWNER); + } + + private void installHandleTooManyDeletesNotification(Account account, String authority, + long numDeletes, int userId) { + if (mNotificationMgr == null) return; + + final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider( + authority, 0 /* flags */); + if (providerInfo == null) { + return; + } + CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager()); + + Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class); + clickIntent.putExtra("account", account); + clickIntent.putExtra("authority", authority); + clickIntent.putExtra("provider", authorityName.toString()); + clickIntent.putExtra("numDeletes", numDeletes); + + if (!isActivityAvailable(clickIntent)) { + Log.w(TAG, "No activity found to handle too many deletes."); + return; + } + + final PendingIntent pendingIntent = PendingIntent + .getActivityAsUser(mContext, 0, clickIntent, + PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(userId)); + + CharSequence tooManyDeletesDescFormat = mContext.getResources().getText( + R.string.contentServiceTooManyDeletesNotificationDesc); + + Notification notification = + new Notification(R.drawable.stat_notify_sync_error, + mContext.getString(R.string.contentServiceSync), + System.currentTimeMillis()); + notification.setLatestEventInfo(mContext, + mContext.getString(R.string.contentServiceSyncNotificationTitle), + String.format(tooManyDeletesDescFormat.toString(), authorityName), + pendingIntent); + notification.flags |= Notification.FLAG_ONGOING_EVENT; + mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(), + notification, new UserHandle(userId)); + } + + /** + * Checks whether an activity exists on the system image for the given intent. + * + * @param intent The intent for an activity. + * @return Whether or not an activity exists. + */ + private boolean isActivityAvailable(Intent intent) { + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); + int listSize = list.size(); + for (int i = 0; i < listSize; i++) { + ResolveInfo resolveInfo = list.get(i); + if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) + != 0) { + return true; + } + } + + return false; + } + + public long insertStartSyncEvent(SyncOperation syncOperation) { + final int source = syncOperation.syncSource; + final long now = System.currentTimeMillis(); + + EventLog.writeEvent(2720, syncOperation.authority, + SyncStorageEngine.EVENT_START, source, + syncOperation.account.name.hashCode()); + + return mSyncStorageEngine.insertStartSyncEvent( + syncOperation.account, syncOperation.userId, syncOperation.reason, + syncOperation.authority, + now, source, syncOperation.isInitialization(), syncOperation.extras + ); + } + + public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage, + int upstreamActivity, int downstreamActivity, long elapsedTime) { + EventLog.writeEvent(2720, syncOperation.authority, + SyncStorageEngine.EVENT_STOP, syncOperation.syncSource, + syncOperation.account.name.hashCode()); + + mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, + resultMessage, downstreamActivity, upstreamActivity); + } + } + + private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) { + for (ActiveSyncContext sync : mActiveSyncContexts) { + if (sync == activeSyncContext) { + return true; + } + } + return false; + } + + static class PrintTable { + private ArrayList<Object[]> mTable = Lists.newArrayList(); + private final int mCols; + + PrintTable(int cols) { + mCols = cols; + } + + void set(int row, int col, Object... values) { + if (col + values.length > mCols) { + throw new IndexOutOfBoundsException("Table only has " + mCols + + " columns. can't set " + values.length + " at column " + col); + } + for (int i = mTable.size(); i <= row; i++) { + final Object[] list = new Object[mCols]; + mTable.add(list); + for (int j = 0; j < mCols; j++) { + list[j] = ""; + } + } + System.arraycopy(values, 0, mTable.get(row), col, values.length); + } + + void writeTo(PrintWriter out) { + final String[] formats = new String[mCols]; + int totalLength = 0; + for (int col = 0; col < mCols; ++col) { + int maxLength = 0; + for (Object[] row : mTable) { + final int length = row[col].toString().length(); + if (length > maxLength) { + maxLength = length; + } + } + totalLength += maxLength; + formats[col] = String.format("%%-%ds", maxLength); + } + printRow(out, formats, mTable.get(0)); + totalLength += (mCols - 1) * 2; + for (int i = 0; i < totalLength; ++i) { + out.print("-"); + } + out.println(); + for (int i = 1, mTableSize = mTable.size(); i < mTableSize; i++) { + Object[] row = mTable.get(i); + printRow(out, formats, row); + } + } + + private void printRow(PrintWriter out, String[] formats, Object[] row) { + for (int j = 0, rowLength = row.length; j < rowLength; j++) { + out.printf(String.format(formats[j], row[j].toString())); + out.print(" "); + } + out.println(); + } + + public int getNumRows() { + return mTable.size(); + } + } +} diff --git a/services/java/com/android/server/content/SyncOperation.java b/services/java/com/android/server/content/SyncOperation.java new file mode 100644 index 0000000..eaad982 --- /dev/null +++ b/services/java/com/android/server/content/SyncOperation.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.content; + +import android.accounts.Account; +import android.content.pm.PackageManager; +import android.content.ContentResolver; +import android.os.Bundle; +import android.os.SystemClock; + +/** + * Value type that represents a sync operation. + * @hide + */ +public class SyncOperation implements Comparable { + public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1; + public static final int REASON_ACCOUNTS_UPDATED = -2; + public static final int REASON_SERVICE_CHANGED = -3; + public static final int REASON_PERIODIC = -4; + public static final int REASON_IS_SYNCABLE = -5; + public static final int REASON_SYNC_AUTO = -6; + public static final int REASON_MASTER_SYNC_AUTO = -7; + public static final int REASON_USER_START = -8; + + private static String[] REASON_NAMES = new String[] { + "DataSettingsChanged", + "AccountsUpdated", + "ServiceChanged", + "Periodic", + "IsSyncable", + "AutoSync", + "MasterSyncAuto", + "UserStart", + }; + + public final Account account; + public final int userId; + public final int reason; + public int syncSource; + public String authority; + public final boolean allowParallelSyncs; + public Bundle extras; + public final String key; + public long earliestRunTime; + public boolean expedited; + public SyncStorageEngine.PendingOperation pendingOperation; + public Long backoff; + public long delayUntil; + public long effectiveRunTime; + + public SyncOperation(Account account, int userId, int reason, int source, String authority, + Bundle extras, long delayInMs, long backoff, long delayUntil, + boolean allowParallelSyncs) { + this.account = account; + this.userId = userId; + this.reason = reason; + this.syncSource = source; + this.authority = authority; + this.allowParallelSyncs = allowParallelSyncs; + this.extras = new Bundle(extras); + removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD); + removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL); + removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS); + removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); + removeFalseExtra(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY); + removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS); + removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED); + removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS); + this.delayUntil = delayUntil; + this.backoff = backoff; + final long now = SystemClock.elapsedRealtime(); + if (delayInMs < 0) { + this.expedited = true; + this.earliestRunTime = now; + } else { + this.expedited = false; + this.earliestRunTime = now + delayInMs; + } + updateEffectiveRunTime(); + this.key = toKey(); + } + + private void removeFalseExtra(String extraName) { + if (!extras.getBoolean(extraName, false)) { + extras.remove(extraName); + } + } + + SyncOperation(SyncOperation other) { + this.account = other.account; + this.userId = other.userId; + this.reason = other.reason; + this.syncSource = other.syncSource; + this.authority = other.authority; + this.extras = new Bundle(other.extras); + this.expedited = other.expedited; + this.earliestRunTime = SystemClock.elapsedRealtime(); + this.backoff = other.backoff; + this.delayUntil = other.delayUntil; + this.allowParallelSyncs = other.allowParallelSyncs; + this.updateEffectiveRunTime(); + this.key = toKey(); + } + + public String toString() { + return dump(null, true); + } + + public String dump(PackageManager pm, boolean useOneLine) { + StringBuilder sb = new StringBuilder() + .append(account.name) + .append(" u") + .append(userId).append(" (") + .append(account.type) + .append(")") + .append(", ") + .append(authority) + .append(", ") + .append(SyncStorageEngine.SOURCES[syncSource]) + .append(", earliestRunTime ") + .append(earliestRunTime); + if (expedited) { + sb.append(", EXPEDITED"); + } + sb.append(", reason: "); + sb.append(reasonToString(pm, reason)); + if (!useOneLine && !extras.keySet().isEmpty()) { + sb.append("\n "); + extrasToStringBuilder(extras, sb); + } + return sb.toString(); + } + + public static String reasonToString(PackageManager pm, int reason) { + if (reason >= 0) { + if (pm != null) { + final String[] packages = pm.getPackagesForUid(reason); + if (packages != null && packages.length == 1) { + return packages[0]; + } + final String name = pm.getNameForUid(reason); + if (name != null) { + return name; + } + return String.valueOf(reason); + } else { + return String.valueOf(reason); + } + } else { + final int index = -reason - 1; + if (index >= REASON_NAMES.length) { + return String.valueOf(reason); + } else { + return REASON_NAMES[index]; + } + } + } + + public boolean isInitialization() { + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); + } + + public boolean isExpedited() { + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); + } + + public boolean ignoreBackoff() { + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); + } + + private String toKey() { + StringBuilder sb = new StringBuilder(); + sb.append("authority: ").append(authority); + sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type + + "}"); + sb.append(" extras: "); + extrasToStringBuilder(extras, sb); + return sb.toString(); + } + + public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { + sb.append("["); + for (String key : bundle.keySet()) { + sb.append(key).append("=").append(bundle.get(key)).append(" "); + } + sb.append("]"); + } + + public void updateEffectiveRunTime() { + effectiveRunTime = ignoreBackoff() + ? earliestRunTime + : Math.max( + Math.max(earliestRunTime, delayUntil), + backoff); + } + + public int compareTo(Object o) { + SyncOperation other = (SyncOperation)o; + + if (expedited != other.expedited) { + return expedited ? -1 : 1; + } + + if (effectiveRunTime == other.effectiveRunTime) { + return 0; + } + + return effectiveRunTime < other.effectiveRunTime ? -1 : 1; + } +} diff --git a/services/java/com/android/server/content/SyncQueue.java b/services/java/com/android/server/content/SyncQueue.java new file mode 100644 index 0000000..951e92c --- /dev/null +++ b/services/java/com/android/server/content/SyncQueue.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.content; + +import android.accounts.Account; +import android.content.pm.PackageManager; +import android.content.pm.RegisteredServicesCache; +import android.content.SyncAdapterType; +import android.content.SyncAdaptersCache; +import android.content.pm.RegisteredServicesCache.ServiceInfo; +import android.os.SystemClock; +import android.text.format.DateUtils; +import android.util.Log; +import android.util.Pair; + +import com.google.android.collect.Maps; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Queue of pending sync operations. Not inherently thread safe, external + * callers are responsible for locking. + * + * @hide + */ +public class SyncQueue { + private static final String TAG = "SyncManager"; + private final SyncStorageEngine mSyncStorageEngine; + private final SyncAdaptersCache mSyncAdapters; + private final PackageManager mPackageManager; + + // A Map of SyncOperations operationKey -> SyncOperation that is designed for + // quick lookup of an enqueued SyncOperation. + private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap(); + + public SyncQueue(PackageManager packageManager, SyncStorageEngine syncStorageEngine, + final SyncAdaptersCache syncAdapters) { + mPackageManager = packageManager; + mSyncStorageEngine = syncStorageEngine; + mSyncAdapters = syncAdapters; + } + + public void addPendingOperations(int userId) { + for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) { + if (op.userId != userId) continue; + + final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff( + op.account, op.userId, op.authority); + final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo( + SyncAdapterType.newKey(op.authority, op.account.type), op.userId); + if (syncAdapterInfo == null) { + Log.w(TAG, "Missing sync adapter info for authority " + op.authority + ", userId " + + op.userId); + continue; + } + SyncOperation syncOperation = new SyncOperation( + op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras, + 0 /* delay */, backoff != null ? backoff.first : 0, + mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority), + syncAdapterInfo.type.allowParallelSyncs()); + syncOperation.expedited = op.expedited; + syncOperation.pendingOperation = op; + add(syncOperation, op); + } + } + + public boolean add(SyncOperation operation) { + return add(operation, null /* this is not coming from the database */); + } + + private boolean add(SyncOperation operation, + SyncStorageEngine.PendingOperation pop) { + // - if an operation with the same key exists and this one should run earlier, + // update the earliestRunTime of the existing to the new time + // - if an operation with the same key exists and if this one should run + // later, ignore it + // - if no operation exists then add the new one + final String operationKey = operation.key; + final SyncOperation existingOperation = mOperationsMap.get(operationKey); + + if (existingOperation != null) { + boolean changed = false; + if (existingOperation.expedited == operation.expedited) { + final long newRunTime = + Math.min(existingOperation.earliestRunTime, operation.earliestRunTime); + if (existingOperation.earliestRunTime != newRunTime) { + existingOperation.earliestRunTime = newRunTime; + changed = true; + } + } else { + if (operation.expedited) { + existingOperation.expedited = true; + changed = true; + } + } + return changed; + } + + operation.pendingOperation = pop; + if (operation.pendingOperation == null) { + pop = new SyncStorageEngine.PendingOperation( + operation.account, operation.userId, operation.reason, operation.syncSource, + operation.authority, operation.extras, operation.expedited); + pop = mSyncStorageEngine.insertIntoPending(pop); + if (pop == null) { + throw new IllegalStateException("error adding pending sync operation " + + operation); + } + operation.pendingOperation = pop; + } + + mOperationsMap.put(operationKey, operation); + return true; + } + + public void removeUser(int userId) { + ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>(); + for (SyncOperation op : mOperationsMap.values()) { + if (op.userId == userId) { + opsToRemove.add(op); + } + } + + for (SyncOperation op : opsToRemove) { + remove(op); + } + } + + /** + * Remove the specified operation if it is in the queue. + * @param operation the operation to remove + */ + public void remove(SyncOperation operation) { + SyncOperation operationToRemove = mOperationsMap.remove(operation.key); + if (operationToRemove == null) { + return; + } + if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) { + final String errorMessage = "unable to find pending row for " + operationToRemove; + Log.e(TAG, errorMessage, new IllegalStateException(errorMessage)); + } + } + + public void onBackoffChanged(Account account, int userId, String providerName, long backoff) { + // for each op that matches the account and provider update its + // backoff and effectiveStartTime + for (SyncOperation op : mOperationsMap.values()) { + if (op.account.equals(account) && op.authority.equals(providerName) + && op.userId == userId) { + op.backoff = backoff; + op.updateEffectiveRunTime(); + } + } + } + + public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) { + // for each op that matches the account and provider update its + // delayUntilTime and effectiveStartTime + for (SyncOperation op : mOperationsMap.values()) { + if (op.account.equals(account) && op.authority.equals(providerName)) { + op.delayUntil = delayUntil; + op.updateEffectiveRunTime(); + } + } + } + + public void remove(Account account, int userId, String authority) { + Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry<String, SyncOperation> entry = entries.next(); + SyncOperation syncOperation = entry.getValue(); + if (account != null && !syncOperation.account.equals(account)) { + continue; + } + if (authority != null && !syncOperation.authority.equals(authority)) { + continue; + } + if (userId != syncOperation.userId) { + continue; + } + entries.remove(); + if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) { + final String errorMessage = "unable to find pending row for " + syncOperation; + Log.e(TAG, errorMessage, new IllegalStateException(errorMessage)); + } + } + } + + public Collection<SyncOperation> getOperations() { + return mOperationsMap.values(); + } + + public void dump(StringBuilder sb) { + final long now = SystemClock.elapsedRealtime(); + sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n"); + for (SyncOperation operation : mOperationsMap.values()) { + sb.append(" "); + if (operation.effectiveRunTime <= now) { + sb.append("READY"); + } else { + sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000)); + } + sb.append(" - "); + sb.append(operation.dump(mPackageManager, false)).append("\n"); + } + } +} diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java new file mode 100644 index 0000000..5b8d26f --- /dev/null +++ b/services/java/com/android/server/content/SyncStorageEngine.java @@ -0,0 +1,2303 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.content; + +import android.accounts.Account; +import android.accounts.AccountAndUser; +import android.content.ContentResolver; +import android.content.Context; +import android.content.ISyncStatusObserver; +import android.content.PeriodicSync; +import android.content.SyncInfo; +import android.content.SyncStatusInfo; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteQueryBuilder; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.os.Parcel; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.TimeZone; + +/** + * Singleton that tracks the sync data and overall sync + * history on the device. + * + * @hide + */ +public class SyncStorageEngine extends Handler { + + private static final String TAG = "SyncManager"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_FILE = false; + + private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId"; + private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles"; + private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds"; + private static final String XML_ATTR_ENABLED = "enabled"; + private static final String XML_ATTR_USER = "user"; + private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles"; + + private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day + + @VisibleForTesting + static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4; + + /** Enum value for a sync start event. */ + public static final int EVENT_START = 0; + + /** Enum value for a sync stop event. */ + public static final int EVENT_STOP = 1; + + // TODO: i18n -- grab these out of resources. + /** String names for the sync event types. */ + public static final String[] EVENTS = { "START", "STOP" }; + + /** Enum value for a server-initiated sync. */ + public static final int SOURCE_SERVER = 0; + + /** Enum value for a local-initiated sync. */ + public static final int SOURCE_LOCAL = 1; + /** + * Enum value for a poll-based sync (e.g., upon connection to + * network) + */ + public static final int SOURCE_POLL = 2; + + /** Enum value for a user-initiated sync. */ + public static final int SOURCE_USER = 3; + + /** Enum value for a periodic sync. */ + public static final int SOURCE_PERIODIC = 4; + + public static final long NOT_IN_BACKOFF_MODE = -1; + + // TODO: i18n -- grab these out of resources. + /** String names for the sync source types. */ + public static final String[] SOURCES = { "SERVER", + "LOCAL", + "POLL", + "USER", + "PERIODIC" }; + + // The MESG column will contain one of these or one of the Error types. + public static final String MESG_SUCCESS = "success"; + public static final String MESG_CANCELED = "canceled"; + + public static final int MAX_HISTORY = 100; + + private static final int MSG_WRITE_STATUS = 1; + private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes + + private static final int MSG_WRITE_STATISTICS = 2; + private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour + + private static final boolean SYNC_ENABLED_DEFAULT = false; + + // the version of the accounts xml file format + private static final int ACCOUNTS_VERSION = 2; + + private static HashMap<String, String> sAuthorityRenames; + + static { + sAuthorityRenames = new HashMap<String, String>(); + sAuthorityRenames.put("contacts", "com.android.contacts"); + sAuthorityRenames.put("calendar", "com.android.calendar"); + } + + public static class PendingOperation { + final Account account; + final int userId; + final int reason; + final int syncSource; + final String authority; + final Bundle extras; // note: read-only. + final boolean expedited; + + int authorityId; + byte[] flatExtras; + + PendingOperation(Account account, int userId, int reason,int source, + String authority, Bundle extras, boolean expedited) { + this.account = account; + this.userId = userId; + this.syncSource = source; + this.reason = reason; + this.authority = authority; + this.extras = extras != null ? new Bundle(extras) : extras; + this.expedited = expedited; + this.authorityId = -1; + } + + PendingOperation(PendingOperation other) { + this.account = other.account; + this.userId = other.userId; + this.reason = other.reason; + this.syncSource = other.syncSource; + this.authority = other.authority; + this.extras = other.extras; + this.authorityId = other.authorityId; + this.expedited = other.expedited; + } + } + + static class AccountInfo { + final AccountAndUser accountAndUser; + final HashMap<String, AuthorityInfo> authorities = + new HashMap<String, AuthorityInfo>(); + + AccountInfo(AccountAndUser accountAndUser) { + this.accountAndUser = accountAndUser; + } + } + + public static class AuthorityInfo { + final Account account; + final int userId; + final String authority; + final int ident; + boolean enabled; + int syncable; + long backoffTime; + long backoffDelay; + long delayUntil; + final ArrayList<Pair<Bundle, Long>> periodicSyncs; + + /** + * Copy constructor for making deep-ish copies. Only the bundles stored + * in periodic syncs can make unexpected changes. + * + * @param toCopy AuthorityInfo to be copied. + */ + AuthorityInfo(AuthorityInfo toCopy) { + account = toCopy.account; + userId = toCopy.userId; + authority = toCopy.authority; + ident = toCopy.ident; + enabled = toCopy.enabled; + syncable = toCopy.syncable; + backoffTime = toCopy.backoffTime; + backoffDelay = toCopy.backoffDelay; + delayUntil = toCopy.delayUntil; + periodicSyncs = new ArrayList<Pair<Bundle, Long>>(); + for (Pair<Bundle, Long> sync : toCopy.periodicSyncs) { + // Still not a perfect copy, because we are just copying the mappings. + periodicSyncs.add(Pair.create(new Bundle(sync.first), sync.second)); + } + } + + AuthorityInfo(Account account, int userId, String authority, int ident) { + this.account = account; + this.userId = userId; + this.authority = authority; + this.ident = ident; + enabled = SYNC_ENABLED_DEFAULT; + syncable = -1; // default to "unknown" + backoffTime = -1; // if < 0 then we aren't in backoff mode + backoffDelay = -1; // if < 0 then we aren't in backoff mode + periodicSyncs = new ArrayList<Pair<Bundle, Long>>(); + periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS)); + } + } + + public static class SyncHistoryItem { + int authorityId; + int historyId; + long eventTime; + long elapsedTime; + int source; + int event; + long upstreamActivity; + long downstreamActivity; + String mesg; + boolean initialization; + Bundle extras; + int reason; + } + + public static class DayStats { + public final int day; + public int successCount; + public long successTime; + public int failureCount; + public long failureTime; + + public DayStats(int day) { + this.day = day; + } + } + + interface OnSyncRequestListener { + /** + * Called when a sync is needed on an account(s) due to some change in state. + * @param account + * @param userId + * @param reason + * @param authority + * @param extras + */ + public void onSyncRequest(Account account, int userId, int reason, String authority, + Bundle extras); + } + + // Primary list of all syncable authorities. Also our global lock. + private final SparseArray<AuthorityInfo> mAuthorities = + new SparseArray<AuthorityInfo>(); + + private final HashMap<AccountAndUser, AccountInfo> mAccounts + = new HashMap<AccountAndUser, AccountInfo>(); + + private final ArrayList<PendingOperation> mPendingOperations = + new ArrayList<PendingOperation>(); + + private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs + = new SparseArray<ArrayList<SyncInfo>>(); + + private final SparseArray<SyncStatusInfo> mSyncStatus = + new SparseArray<SyncStatusInfo>(); + + private final ArrayList<SyncHistoryItem> mSyncHistory = + new ArrayList<SyncHistoryItem>(); + + private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners + = new RemoteCallbackList<ISyncStatusObserver>(); + + private int mNextAuthorityId = 0; + + // We keep 4 weeks of stats. + private final DayStats[] mDayStats = new DayStats[7*4]; + private final Calendar mCal; + private int mYear; + private int mYearInDays; + + private final Context mContext; + + private static volatile SyncStorageEngine sSyncStorageEngine = null; + + private int mSyncRandomOffset; + + /** + * This file contains the core engine state: all accounts and the + * settings for them. It must never be lost, and should be changed + * infrequently, so it is stored as an XML file. + */ + private final AtomicFile mAccountInfoFile; + + /** + * This file contains the current sync status. We would like to retain + * it across boots, but its loss is not the end of the world, so we store + * this information as binary data. + */ + private final AtomicFile mStatusFile; + + /** + * This file contains sync statistics. This is purely debugging information + * so is written infrequently and can be thrown away at any time. + */ + private final AtomicFile mStatisticsFile; + + /** + * This file contains the pending sync operations. It is a binary file, + * which must be updated every time an operation is added or removed, + * so we have special handling of it. + */ + private final AtomicFile mPendingFile; + private static final int PENDING_FINISH_TO_WRITE = 4; + private int mNumPendingFinished = 0; + + private int mNextHistoryId = 0; + private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>(); + private boolean mDefaultMasterSyncAutomatically; + + private OnSyncRequestListener mSyncRequestListener; + + private SyncStorageEngine(Context context, File dataDir) { + mContext = context; + sSyncStorageEngine = this; + + mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0")); + + mDefaultMasterSyncAutomatically = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically); + + File systemDir = new File(dataDir, "system"); + File syncDir = new File(systemDir, "sync"); + syncDir.mkdirs(); + mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); + mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); + mPendingFile = new AtomicFile(new File(syncDir, "pending.bin")); + mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin")); + + readAccountInfoLocked(); + readStatusLocked(); + readPendingOperationsLocked(); + readStatisticsLocked(); + readAndDeleteLegacyAccountInfoLocked(); + writeAccountInfoLocked(); + writeStatusLocked(); + writePendingOperationsLocked(); + writeStatisticsLocked(); + } + + public static SyncStorageEngine newTestInstance(Context context) { + return new SyncStorageEngine(context, context.getFilesDir()); + } + + public static void init(Context context) { + if (sSyncStorageEngine != null) { + return; + } + // This call will return the correct directory whether Encrypted File Systems is + // enabled or not. + File dataDir = Environment.getSecureDataDirectory(); + sSyncStorageEngine = new SyncStorageEngine(context, dataDir); + } + + public static SyncStorageEngine getSingleton() { + if (sSyncStorageEngine == null) { + throw new IllegalStateException("not initialized"); + } + return sSyncStorageEngine; + } + + protected void setOnSyncRequestListener(OnSyncRequestListener listener) { + if (mSyncRequestListener == null) { + mSyncRequestListener = listener; + } + } + + @Override public void handleMessage(Message msg) { + if (msg.what == MSG_WRITE_STATUS) { + synchronized (mAuthorities) { + writeStatusLocked(); + } + } else if (msg.what == MSG_WRITE_STATISTICS) { + synchronized (mAuthorities) { + writeStatisticsLocked(); + } + } + } + + public int getSyncRandomOffset() { + return mSyncRandomOffset; + } + + public void addStatusChangeListener(int mask, ISyncStatusObserver callback) { + synchronized (mAuthorities) { + mChangeListeners.register(callback, mask); + } + } + + public void removeStatusChangeListener(ISyncStatusObserver callback) { + synchronized (mAuthorities) { + mChangeListeners.unregister(callback); + } + } + + private void reportChange(int which) { + ArrayList<ISyncStatusObserver> reports = null; + synchronized (mAuthorities) { + int i = mChangeListeners.beginBroadcast(); + while (i > 0) { + i--; + Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i); + if ((which & mask.intValue()) == 0) { + continue; + } + if (reports == null) { + reports = new ArrayList<ISyncStatusObserver>(i); + } + reports.add(mChangeListeners.getBroadcastItem(i)); + } + mChangeListeners.finishBroadcast(); + } + + if (DEBUG) { + Log.v(TAG, "reportChange " + which + " to: " + reports); + } + + if (reports != null) { + int i = reports.size(); + while (i > 0) { + i--; + try { + reports.get(i).onStatusChanged(which); + } catch (RemoteException e) { + // The remote callback list will take care of this for us. + } + } + } + } + + public boolean getSyncAutomatically(Account account, int userId, String providerName) { + synchronized (mAuthorities) { + if (account != null) { + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, + "getSyncAutomatically"); + return authority != null && authority.enabled; + } + + int i = mAuthorities.size(); + while (i > 0) { + i--; + AuthorityInfo authority = mAuthorities.valueAt(i); + if (authority.authority.equals(providerName) + && authority.userId == userId + && authority.enabled) { + return true; + } + } + return false; + } + } + + public void setSyncAutomatically(Account account, int userId, String providerName, + boolean sync) { + if (DEBUG) { + Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName + + ", user " + userId + " -> " + sync); + } + synchronized (mAuthorities) { + AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1, + false); + if (authority.enabled == sync) { + if (DEBUG) { + Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing"); + } + return; + } + authority.enabled = sync; + writeAccountInfoLocked(); + } + + if (sync) { + requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName, + new Bundle()); + } + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + } + + public int getIsSyncable(Account account, int userId, String providerName) { + synchronized (mAuthorities) { + if (account != null) { + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, + "getIsSyncable"); + if (authority == null) { + return -1; + } + return authority.syncable; + } + + int i = mAuthorities.size(); + while (i > 0) { + i--; + AuthorityInfo authority = mAuthorities.valueAt(i); + if (authority.authority.equals(providerName)) { + return authority.syncable; + } + } + return -1; + } + } + + public void setIsSyncable(Account account, int userId, String providerName, int syncable) { + if (syncable > 1) { + syncable = 1; + } else if (syncable < -1) { + syncable = -1; + } + if (DEBUG) { + Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName + + ", user " + userId + " -> " + syncable); + } + synchronized (mAuthorities) { + AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1, + false); + if (authority.syncable == syncable) { + if (DEBUG) { + Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing"); + } + return; + } + authority.syncable = syncable; + writeAccountInfoLocked(); + } + + if (syncable > 0) { + requestSync(account, userId, SyncOperation.REASON_IS_SYNCABLE, providerName, + new Bundle()); + } + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + } + + public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) { + synchronized (mAuthorities) { + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, + "getBackoff"); + if (authority == null || authority.backoffTime < 0) { + return null; + } + return Pair.create(authority.backoffTime, authority.backoffDelay); + } + } + + public void setBackoff(Account account, int userId, String providerName, + long nextSyncTime, long nextDelay) { + if (DEBUG) { + Log.v(TAG, "setBackoff: " + account + ", provider " + providerName + + ", user " + userId + + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay); + } + boolean changed = false; + synchronized (mAuthorities) { + if (account == null || providerName == null) { + for (AccountInfo accountInfo : mAccounts.values()) { + if (account != null && !account.equals(accountInfo.accountAndUser.account) + && userId != accountInfo.accountAndUser.userId) { + continue; + } + for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { + if (providerName != null && !providerName.equals(authorityInfo.authority)) { + continue; + } + if (authorityInfo.backoffTime != nextSyncTime + || authorityInfo.backoffDelay != nextDelay) { + authorityInfo.backoffTime = nextSyncTime; + authorityInfo.backoffDelay = nextDelay; + changed = true; + } + } + } + } else { + AuthorityInfo authority = + getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */, + true); + if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) { + return; + } + authority.backoffTime = nextSyncTime; + authority.backoffDelay = nextDelay; + changed = true; + } + } + + if (changed) { + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + } + } + + public void clearAllBackoffs(SyncQueue syncQueue) { + boolean changed = false; + synchronized (mAuthorities) { + synchronized (syncQueue) { + for (AccountInfo accountInfo : mAccounts.values()) { + for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { + if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE + || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) { + if (DEBUG) { + Log.v(TAG, "clearAllBackoffs:" + + " authority:" + authorityInfo.authority + + " account:" + accountInfo.accountAndUser.account.name + + " user:" + accountInfo.accountAndUser.userId + + " backoffTime was: " + authorityInfo.backoffTime + + " backoffDelay was: " + authorityInfo.backoffDelay); + } + authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE; + authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE; + syncQueue.onBackoffChanged(accountInfo.accountAndUser.account, + accountInfo.accountAndUser.userId, authorityInfo.authority, 0); + changed = true; + } + } + } + } + } + + if (changed) { + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + } + } + + public void setDelayUntilTime(Account account, int userId, String providerName, + long delayUntil) { + if (DEBUG) { + Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName + + ", user " + userId + " -> delayUntil " + delayUntil); + } + synchronized (mAuthorities) { + AuthorityInfo authority = getOrCreateAuthorityLocked( + account, userId, providerName, -1 /* ident */, true); + if (authority.delayUntil == delayUntil) { + return; + } + authority.delayUntil = delayUntil; + } + + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + } + + public long getDelayUntilTime(Account account, int userId, String providerName) { + synchronized (mAuthorities) { + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, + "getDelayUntil"); + if (authority == null) { + return 0; + } + return authority.delayUntil; + } + } + + private void updateOrRemovePeriodicSync(Account account, int userId, String providerName, + Bundle extras, + long period, boolean add) { + if (period <= 0) { + period = 0; + } + if (extras == null) { + extras = new Bundle(); + } + if (DEBUG) { + Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", user " + userId + + ", provider " + providerName + + " -> period " + period + ", extras " + extras); + } + synchronized (mAuthorities) { + try { + AuthorityInfo authority = + getOrCreateAuthorityLocked(account, userId, providerName, -1, false); + if (add) { + // add this periodic sync if one with the same extras doesn't already + // exist in the periodicSyncs array + boolean alreadyPresent = false; + for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) { + Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i); + final Bundle existingExtras = syncInfo.first; + if (PeriodicSync.syncExtrasEquals(existingExtras, extras)) { + if (syncInfo.second == period) { + return; + } + authority.periodicSyncs.set(i, Pair.create(extras, period)); + alreadyPresent = true; + break; + } + } + // if we added an entry to the periodicSyncs array also add an entry to + // the periodic syncs status to correspond to it + if (!alreadyPresent) { + authority.periodicSyncs.add(Pair.create(extras, period)); + SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); + status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0); + } + } else { + // remove any periodic syncs that match the authority and extras + SyncStatusInfo status = mSyncStatus.get(authority.ident); + boolean changed = false; + Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator(); + int i = 0; + while (iterator.hasNext()) { + Pair<Bundle, Long> syncInfo = iterator.next(); + if (PeriodicSync.syncExtrasEquals(syncInfo.first, extras)) { + iterator.remove(); + changed = true; + // if we removed an entry from the periodicSyncs array also + // remove the corresponding entry from the status + if (status != null) { + status.removePeriodicSyncTime(i); + } + } else { + i++; + } + } + if (!changed) { + return; + } + } + } finally { + writeAccountInfoLocked(); + writeStatusLocked(); + } + } + + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + } + + public void addPeriodicSync(Account account, int userId, String providerName, Bundle extras, + long pollFrequency) { + updateOrRemovePeriodicSync(account, userId, providerName, extras, pollFrequency, + true /* add */); + } + + public void removePeriodicSync(Account account, int userId, String providerName, + Bundle extras) { + updateOrRemovePeriodicSync(account, userId, providerName, extras, 0 /* period, ignored */, + false /* remove */); + } + + public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) { + ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>(); + synchronized (mAuthorities) { + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, + "getPeriodicSyncs"); + if (authority != null) { + for (Pair<Bundle, Long> item : authority.periodicSyncs) { + syncs.add(new PeriodicSync(account, providerName, item.first, + item.second)); + } + } + } + return syncs; + } + + public void setMasterSyncAutomatically(boolean flag, int userId) { + synchronized (mAuthorities) { + Boolean auto = mMasterSyncAutomatically.get(userId); + if (auto != null && (boolean) auto == flag) { + return; + } + mMasterSyncAutomatically.put(userId, flag); + writeAccountInfoLocked(); + } + if (flag) { + requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null, + new Bundle()); + } + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED); + } + + public boolean getMasterSyncAutomatically(int userId) { + synchronized (mAuthorities) { + Boolean auto = mMasterSyncAutomatically.get(userId); + return auto == null ? mDefaultMasterSyncAutomatically : auto; + } + } + + public AuthorityInfo getOrCreateAuthority(Account account, int userId, String authority) { + synchronized (mAuthorities) { + return getOrCreateAuthorityLocked(account, userId, authority, + -1 /* assign a new identifier if creating a new authority */, + true /* write to storage if this results in a change */); + } + } + + public void removeAuthority(Account account, int userId, String authority) { + synchronized (mAuthorities) { + removeAuthorityLocked(account, userId, authority, true /* doWrite */); + } + } + + public AuthorityInfo getAuthority(int authorityId) { + synchronized (mAuthorities) { + return mAuthorities.get(authorityId); + } + } + + /** + * Returns true if there is currently a sync operation for the given + * account or authority actively being processed. + */ + public boolean isSyncActive(Account account, int userId, String authority) { + synchronized (mAuthorities) { + for (SyncInfo syncInfo : getCurrentSyncs(userId)) { + AuthorityInfo ainfo = getAuthority(syncInfo.authorityId); + if (ainfo != null && ainfo.account.equals(account) + && ainfo.authority.equals(authority) + && ainfo.userId == userId) { + return true; + } + } + } + + return false; + } + + public PendingOperation insertIntoPending(PendingOperation op) { + synchronized (mAuthorities) { + if (DEBUG) { + Log.v(TAG, "insertIntoPending: account=" + op.account + + " user=" + op.userId + + " auth=" + op.authority + + " src=" + op.syncSource + + " extras=" + op.extras); + } + + AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId, + op.authority, + -1 /* desired identifier */, + true /* write accounts to storage */); + if (authority == null) { + return null; + } + + op = new PendingOperation(op); + op.authorityId = authority.ident; + mPendingOperations.add(op); + appendPendingOperationLocked(op); + + SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); + status.pending = true; + } + + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); + return op; + } + + public boolean deleteFromPending(PendingOperation op) { + boolean res = false; + synchronized (mAuthorities) { + if (DEBUG) { + Log.v(TAG, "deleteFromPending: account=" + op.account + + " user=" + op.userId + + " auth=" + op.authority + + " src=" + op.syncSource + + " extras=" + op.extras); + } + if (mPendingOperations.remove(op)) { + if (mPendingOperations.size() == 0 + || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) { + writePendingOperationsLocked(); + mNumPendingFinished = 0; + } else { + mNumPendingFinished++; + } + + AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority, + "deleteFromPending"); + if (authority != null) { + if (DEBUG) Log.v(TAG, "removing - " + authority); + final int N = mPendingOperations.size(); + boolean morePending = false; + for (int i=0; i<N; i++) { + PendingOperation cur = mPendingOperations.get(i); + if (cur.account.equals(op.account) + && cur.authority.equals(op.authority) + && cur.userId == op.userId) { + morePending = true; + break; + } + } + + if (!morePending) { + if (DEBUG) Log.v(TAG, "no more pending!"); + SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); + status.pending = false; + } + } + + res = true; + } + } + + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); + return res; + } + + /** + * Return a copy of the current array of pending operations. The + * PendingOperation objects are the real objects stored inside, so that + * they can be used with deleteFromPending(). + */ + public ArrayList<PendingOperation> getPendingOperations() { + synchronized (mAuthorities) { + return new ArrayList<PendingOperation>(mPendingOperations); + } + } + + /** + * Return the number of currently pending operations. + */ + public int getPendingOperationCount() { + synchronized (mAuthorities) { + return mPendingOperations.size(); + } + } + + /** + * Called when the set of account has changed, given the new array of + * active accounts. + */ + public void doDatabaseCleanup(Account[] accounts, int userId) { + synchronized (mAuthorities) { + if (DEBUG) Log.v(TAG, "Updating for new accounts..."); + SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>(); + Iterator<AccountInfo> accIt = mAccounts.values().iterator(); + while (accIt.hasNext()) { + AccountInfo acc = accIt.next(); + if (!ArrayUtils.contains(accounts, acc.accountAndUser.account) + && acc.accountAndUser.userId == userId) { + // This account no longer exists... + if (DEBUG) { + Log.v(TAG, "Account removed: " + acc.accountAndUser); + } + for (AuthorityInfo auth : acc.authorities.values()) { + removing.put(auth.ident, auth); + } + accIt.remove(); + } + } + + // Clean out all data structures. + int i = removing.size(); + if (i > 0) { + while (i > 0) { + i--; + int ident = removing.keyAt(i); + mAuthorities.remove(ident); + int j = mSyncStatus.size(); + while (j > 0) { + j--; + if (mSyncStatus.keyAt(j) == ident) { + mSyncStatus.remove(mSyncStatus.keyAt(j)); + } + } + j = mSyncHistory.size(); + while (j > 0) { + j--; + if (mSyncHistory.get(j).authorityId == ident) { + mSyncHistory.remove(j); + } + } + } + writeAccountInfoLocked(); + writeStatusLocked(); + writePendingOperationsLocked(); + writeStatisticsLocked(); + } + } + } + + /** + * Called when a sync is starting. Supply a valid ActiveSyncContext with information + * about the sync. + */ + public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { + final SyncInfo syncInfo; + synchronized (mAuthorities) { + if (DEBUG) { + Log.v(TAG, "setActiveSync: account=" + + activeSyncContext.mSyncOperation.account + + " auth=" + activeSyncContext.mSyncOperation.authority + + " src=" + activeSyncContext.mSyncOperation.syncSource + + " extras=" + activeSyncContext.mSyncOperation.extras); + } + AuthorityInfo authority = getOrCreateAuthorityLocked( + activeSyncContext.mSyncOperation.account, + activeSyncContext.mSyncOperation.userId, + activeSyncContext.mSyncOperation.authority, + -1 /* assign a new identifier if creating a new authority */, + true /* write to storage if this results in a change */); + syncInfo = new SyncInfo(authority.ident, + authority.account, authority.authority, + activeSyncContext.mStartTime); + getCurrentSyncs(authority.userId).add(syncInfo); + } + + reportActiveChange(); + return syncInfo; + } + + /** + * Called to indicate that a previously active sync is no longer active. + */ + public void removeActiveSync(SyncInfo syncInfo, int userId) { + synchronized (mAuthorities) { + if (DEBUG) { + Log.v(TAG, "removeActiveSync: account=" + syncInfo.account + + " user=" + userId + + " auth=" + syncInfo.authority); + } + getCurrentSyncs(userId).remove(syncInfo); + } + + reportActiveChange(); + } + + /** + * To allow others to send active change reports, to poke clients. + */ + public void reportActiveChange() { + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE); + } + + /** + * Note that sync has started for the given account and authority. + */ + public long insertStartSyncEvent(Account accountName, int userId, int reason, + String authorityName, long now, int source, boolean initialization, Bundle extras) { + long id; + synchronized (mAuthorities) { + if (DEBUG) { + Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId + + " auth=" + authorityName + " source=" + source); + } + AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName, + "insertStartSyncEvent"); + if (authority == null) { + return -1; + } + SyncHistoryItem item = new SyncHistoryItem(); + item.initialization = initialization; + item.authorityId = authority.ident; + item.historyId = mNextHistoryId++; + if (mNextHistoryId < 0) mNextHistoryId = 0; + item.eventTime = now; + item.source = source; + item.reason = reason; + item.extras = extras; + item.event = EVENT_START; + mSyncHistory.add(0, item); + while (mSyncHistory.size() > MAX_HISTORY) { + mSyncHistory.remove(mSyncHistory.size()-1); + } + id = item.historyId; + if (DEBUG) Log.v(TAG, "returning historyId " + id); + } + + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); + return id; + } + + public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, + long downstreamActivity, long upstreamActivity) { + synchronized (mAuthorities) { + if (DEBUG) { + Log.v(TAG, "stopSyncEvent: historyId=" + historyId); + } + SyncHistoryItem item = null; + int i = mSyncHistory.size(); + while (i > 0) { + i--; + item = mSyncHistory.get(i); + if (item.historyId == historyId) { + break; + } + item = null; + } + + if (item == null) { + Log.w(TAG, "stopSyncEvent: no history for id " + historyId); + return; + } + + item.elapsedTime = elapsedTime; + item.event = EVENT_STOP; + item.mesg = resultMessage; + item.downstreamActivity = downstreamActivity; + item.upstreamActivity = upstreamActivity; + + SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId); + + status.numSyncs++; + status.totalElapsedTime += elapsedTime; + switch (item.source) { + case SOURCE_LOCAL: + status.numSourceLocal++; + break; + case SOURCE_POLL: + status.numSourcePoll++; + break; + case SOURCE_USER: + status.numSourceUser++; + break; + case SOURCE_SERVER: + status.numSourceServer++; + break; + case SOURCE_PERIODIC: + status.numSourcePeriodic++; + break; + } + + boolean writeStatisticsNow = false; + int day = getCurrentDayLocked(); + if (mDayStats[0] == null) { + mDayStats[0] = new DayStats(day); + } else if (day != mDayStats[0].day) { + System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1); + mDayStats[0] = new DayStats(day); + writeStatisticsNow = true; + } else if (mDayStats[0] == null) { + } + final DayStats ds = mDayStats[0]; + + final long lastSyncTime = (item.eventTime + elapsedTime); + boolean writeStatusNow = false; + if (MESG_SUCCESS.equals(resultMessage)) { + // - if successful, update the successful columns + if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) { + writeStatusNow = true; + } + status.lastSuccessTime = lastSyncTime; + status.lastSuccessSource = item.source; + status.lastFailureTime = 0; + status.lastFailureSource = -1; + status.lastFailureMesg = null; + status.initialFailureTime = 0; + ds.successCount++; + ds.successTime += elapsedTime; + } else if (!MESG_CANCELED.equals(resultMessage)) { + if (status.lastFailureTime == 0) { + writeStatusNow = true; + } + status.lastFailureTime = lastSyncTime; + status.lastFailureSource = item.source; + status.lastFailureMesg = resultMessage; + if (status.initialFailureTime == 0) { + status.initialFailureTime = lastSyncTime; + } + ds.failureCount++; + ds.failureTime += elapsedTime; + } + + if (writeStatusNow) { + writeStatusLocked(); + } else if (!hasMessages(MSG_WRITE_STATUS)) { + sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS), + WRITE_STATUS_DELAY); + } + if (writeStatisticsNow) { + writeStatisticsLocked(); + } else if (!hasMessages(MSG_WRITE_STATISTICS)) { + sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS), + WRITE_STATISTICS_DELAY); + } + } + + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); + } + + /** + * Return a list of the currently active syncs. Note that the returned items are the + * real, live active sync objects, so be careful what you do with it. + */ + public List<SyncInfo> getCurrentSyncs(int userId) { + synchronized (mAuthorities) { + ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId); + if (syncs == null) { + syncs = new ArrayList<SyncInfo>(); + mCurrentSyncs.put(userId, syncs); + } + return syncs; + } + } + + /** + * Return an array of the current sync status for all authorities. Note + * that the objects inside the array are the real, live status objects, + * so be careful what you do with them. + */ + public ArrayList<SyncStatusInfo> getSyncStatus() { + synchronized (mAuthorities) { + final int N = mSyncStatus.size(); + ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N); + for (int i=0; i<N; i++) { + ops.add(mSyncStatus.valueAt(i)); + } + return ops; + } + } + + /** + * Return an array of the current authorities. Note + * that the objects inside the array are the real, live objects, + * so be careful what you do with them. + */ + public ArrayList<AuthorityInfo> getAuthorities() { + synchronized (mAuthorities) { + final int N = mAuthorities.size(); + ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N); + for (int i=0; i<N; i++) { + // Make deep copy because AuthorityInfo syncs are liable to change. + infos.add(new AuthorityInfo(mAuthorities.valueAt(i))); + } + return infos; + } + } + + /** + * Returns the status that matches the authority and account. + * + * @param account the account we want to check + * @param authority the authority whose row should be selected + * @return the SyncStatusInfo for the authority + */ + public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId, + String authority) { + if (account == null || authority == null) { + throw new IllegalArgumentException(); + } + synchronized (mAuthorities) { + final int N = mSyncStatus.size(); + for (int i=0; i<N; i++) { + SyncStatusInfo cur = mSyncStatus.valueAt(i); + AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); + + if (ainfo != null && ainfo.authority.equals(authority) + && ainfo.userId == userId + && account.equals(ainfo.account)) { + return cur; + } + } + return null; + } + } + + /** + * Return true if the pending status is true of any matching authorities. + */ + public boolean isSyncPending(Account account, int userId, String authority) { + synchronized (mAuthorities) { + final int N = mSyncStatus.size(); + for (int i=0; i<N; i++) { + SyncStatusInfo cur = mSyncStatus.valueAt(i); + AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); + if (ainfo == null) { + continue; + } + if (userId != ainfo.userId) { + continue; + } + if (account != null && !ainfo.account.equals(account)) { + continue; + } + if (ainfo.authority.equals(authority) && cur.pending) { + return true; + } + } + return false; + } + } + + /** + * Return an array of the current sync status for all authorities. Note + * that the objects inside the array are the real, live status objects, + * so be careful what you do with them. + */ + public ArrayList<SyncHistoryItem> getSyncHistory() { + synchronized (mAuthorities) { + final int N = mSyncHistory.size(); + ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N); + for (int i=0; i<N; i++) { + items.add(mSyncHistory.get(i)); + } + return items; + } + } + + /** + * Return an array of the current per-day statistics. Note + * that the objects inside the array are the real, live status objects, + * so be careful what you do with them. + */ + public DayStats[] getDayStatistics() { + synchronized (mAuthorities) { + DayStats[] ds = new DayStats[mDayStats.length]; + System.arraycopy(mDayStats, 0, ds, 0, ds.length); + return ds; + } + } + + private int getCurrentDayLocked() { + mCal.setTimeInMillis(System.currentTimeMillis()); + final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); + if (mYear != mCal.get(Calendar.YEAR)) { + mYear = mCal.get(Calendar.YEAR); + mCal.clear(); + mCal.set(Calendar.YEAR, mYear); + mYearInDays = (int)(mCal.getTimeInMillis()/86400000); + } + return dayOfYear + mYearInDays; + } + + /** + * Retrieve an authority, returning null if one does not exist. + * + * @param accountName The name of the account for the authority. + * @param authorityName The name of the authority itself. + * @param tag If non-null, this will be used in a log message if the + * requested authority does not exist. + */ + private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName, + String tag) { + AccountAndUser au = new AccountAndUser(accountName, userId); + AccountInfo accountInfo = mAccounts.get(au); + if (accountInfo == null) { + if (tag != null) { + if (DEBUG) { + Log.v(TAG, tag + ": unknown account " + au); + } + } + return null; + } + AuthorityInfo authority = accountInfo.authorities.get(authorityName); + if (authority == null) { + if (tag != null) { + if (DEBUG) { + Log.v(TAG, tag + ": unknown authority " + authorityName); + } + } + return null; + } + + return authority; + } + + private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId, + String authorityName, int ident, boolean doWrite) { + AccountAndUser au = new AccountAndUser(accountName, userId); + AccountInfo account = mAccounts.get(au); + if (account == null) { + account = new AccountInfo(au); + mAccounts.put(au, account); + } + AuthorityInfo authority = account.authorities.get(authorityName); + if (authority == null) { + if (ident < 0) { + ident = mNextAuthorityId; + mNextAuthorityId++; + doWrite = true; + } + if (DEBUG) { + Log.v(TAG, "created a new AuthorityInfo for " + accountName + + ", user " + userId + + ", provider " + authorityName); + } + authority = new AuthorityInfo(accountName, userId, authorityName, ident); + account.authorities.put(authorityName, authority); + mAuthorities.put(ident, authority); + if (doWrite) { + writeAccountInfoLocked(); + } + } + + return authority; + } + + private void removeAuthorityLocked(Account account, int userId, String authorityName, + boolean doWrite) { + AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId)); + if (accountInfo != null) { + final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName); + if (authorityInfo != null) { + mAuthorities.remove(authorityInfo.ident); + if (doWrite) { + writeAccountInfoLocked(); + } + } + } + } + + public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) { + synchronized (mAuthorities) { + return getOrCreateSyncStatusLocked(authority.ident); + } + } + + private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) { + SyncStatusInfo status = mSyncStatus.get(authorityId); + if (status == null) { + status = new SyncStatusInfo(authorityId); + mSyncStatus.put(authorityId, status); + } + return status; + } + + public void writeAllState() { + synchronized (mAuthorities) { + // Account info is always written so no need to do it here. + + if (mNumPendingFinished > 0) { + // Only write these if they are out of date. + writePendingOperationsLocked(); + } + + // Just always write these... they are likely out of date. + writeStatusLocked(); + writeStatisticsLocked(); + } + } + + /** + * public for testing + */ + public void clearAndReadState() { + synchronized (mAuthorities) { + mAuthorities.clear(); + mAccounts.clear(); + mPendingOperations.clear(); + mSyncStatus.clear(); + mSyncHistory.clear(); + + readAccountInfoLocked(); + readStatusLocked(); + readPendingOperationsLocked(); + readStatisticsLocked(); + readAndDeleteLegacyAccountInfoLocked(); + writeAccountInfoLocked(); + writeStatusLocked(); + writePendingOperationsLocked(); + writeStatisticsLocked(); + } + } + + /** + * Read all account information back in to the initial engine state. + */ + private void readAccountInfoLocked() { + int highestAuthorityId = -1; + FileInputStream fis = null; + try { + fis = mAccountInfoFile.openRead(); + if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG) { + eventType = parser.next(); + } + String tagName = parser.getName(); + if ("accounts".equals(tagName)) { + String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES); + String versionString = parser.getAttributeValue(null, "version"); + int version; + try { + version = (versionString == null) ? 0 : Integer.parseInt(versionString); + } catch (NumberFormatException e) { + version = 0; + } + String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID); + try { + int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString); + mNextAuthorityId = Math.max(mNextAuthorityId, id); + } catch (NumberFormatException e) { + // don't care + } + String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET); + try { + mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString); + } catch (NumberFormatException e) { + mSyncRandomOffset = 0; + } + if (mSyncRandomOffset == 0) { + Random random = new Random(System.currentTimeMillis()); + mSyncRandomOffset = random.nextInt(86400); + } + mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen)); + eventType = parser.next(); + AuthorityInfo authority = null; + Pair<Bundle, Long> periodicSync = null; + do { + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.getName(); + if (parser.getDepth() == 2) { + if ("authority".equals(tagName)) { + authority = parseAuthority(parser, version); + periodicSync = null; + if (authority.ident > highestAuthorityId) { + highestAuthorityId = authority.ident; + } + } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) { + parseListenForTickles(parser); + } + } else if (parser.getDepth() == 3) { + if ("periodicSync".equals(tagName) && authority != null) { + periodicSync = parsePeriodicSync(parser, authority); + } + } else if (parser.getDepth() == 4 && periodicSync != null) { + if ("extra".equals(tagName)) { + parseExtra(parser, periodicSync); + } + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Error reading accounts", e); + return; + } catch (java.io.IOException e) { + if (fis == null) Log.i(TAG, "No initial accounts"); + else Log.w(TAG, "Error reading accounts", e); + return; + } finally { + mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId); + if (fis != null) { + try { + fis.close(); + } catch (java.io.IOException e1) { + } + } + } + + maybeMigrateSettingsForRenamedAuthorities(); + } + + /** + * some authority names have changed. copy over their settings and delete the old ones + * @return true if a change was made + */ + private boolean maybeMigrateSettingsForRenamedAuthorities() { + boolean writeNeeded = false; + + ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>(); + final int N = mAuthorities.size(); + for (int i=0; i<N; i++) { + AuthorityInfo authority = mAuthorities.valueAt(i); + // skip this authority if it isn't one of the renamed ones + final String newAuthorityName = sAuthorityRenames.get(authority.authority); + if (newAuthorityName == null) { + continue; + } + + // remember this authority so we can remove it later. we can't remove it + // now without messing up this loop iteration + authoritiesToRemove.add(authority); + + // this authority isn't enabled, no need to copy it to the new authority name since + // the default is "disabled" + if (!authority.enabled) { + continue; + } + + // if we already have a record of this new authority then don't copy over the settings + if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup") + != null) { + continue; + } + + AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account, + authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */); + newAuthority.enabled = true; + writeNeeded = true; + } + + for (AuthorityInfo authorityInfo : authoritiesToRemove) { + removeAuthorityLocked(authorityInfo.account, authorityInfo.userId, + authorityInfo.authority, false /* doWrite */); + writeNeeded = true; + } + + return writeNeeded; + } + + private void parseListenForTickles(XmlPullParser parser) { + String user = parser.getAttributeValue(null, XML_ATTR_USER); + int userId = 0; + try { + userId = Integer.parseInt(user); + } catch (NumberFormatException e) { + Log.e(TAG, "error parsing the user for listen-for-tickles", e); + } catch (NullPointerException e) { + Log.e(TAG, "the user in listen-for-tickles is null", e); + } + String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED); + boolean listen = enabled == null || Boolean.parseBoolean(enabled); + mMasterSyncAutomatically.put(userId, listen); + } + + private AuthorityInfo parseAuthority(XmlPullParser parser, int version) { + AuthorityInfo authority = null; + int id = -1; + try { + id = Integer.parseInt(parser.getAttributeValue( + null, "id")); + } catch (NumberFormatException e) { + Log.e(TAG, "error parsing the id of the authority", e); + } catch (NullPointerException e) { + Log.e(TAG, "the id of the authority is null", e); + } + if (id >= 0) { + String authorityName = parser.getAttributeValue(null, "authority"); + String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED); + String syncable = parser.getAttributeValue(null, "syncable"); + String accountName = parser.getAttributeValue(null, "account"); + String accountType = parser.getAttributeValue(null, "type"); + String user = parser.getAttributeValue(null, XML_ATTR_USER); + int userId = user == null ? 0 : Integer.parseInt(user); + if (accountType == null) { + accountType = "com.google"; + syncable = "unknown"; + } + authority = mAuthorities.get(id); + if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" + + accountName + " auth=" + authorityName + + " user=" + userId + + " enabled=" + enabled + + " syncable=" + syncable); + if (authority == null) { + if (DEBUG_FILE) Log.v(TAG, "Creating entry"); + authority = getOrCreateAuthorityLocked( + new Account(accountName, accountType), userId, authorityName, id, false); + // If the version is 0 then we are upgrading from a file format that did not + // know about periodic syncs. In that case don't clear the list since we + // want the default, which is a daily periodioc sync. + // Otherwise clear out this default list since we will populate it later with + // the periodic sync descriptions that are read from the configuration file. + if (version > 0) { + authority.periodicSyncs.clear(); + } + } + if (authority != null) { + authority.enabled = enabled == null || Boolean.parseBoolean(enabled); + if ("unknown".equals(syncable)) { + authority.syncable = -1; + } else { + authority.syncable = + (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0; + } + } else { + Log.w(TAG, "Failure adding authority: account=" + + accountName + " auth=" + authorityName + + " enabled=" + enabled + + " syncable=" + syncable); + } + } + + return authority; + } + + private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) { + Bundle extras = new Bundle(); + String periodValue = parser.getAttributeValue(null, "period"); + final long period; + try { + period = Long.parseLong(periodValue); + } catch (NumberFormatException e) { + Log.e(TAG, "error parsing the period of a periodic sync", e); + return null; + } catch (NullPointerException e) { + Log.e(TAG, "the period of a periodic sync is null", e); + return null; + } + final Pair<Bundle, Long> periodicSync = Pair.create(extras, period); + authority.periodicSyncs.add(periodicSync); + + return periodicSync; + } + + private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) { + final Bundle extras = periodicSync.first; + String name = parser.getAttributeValue(null, "name"); + String type = parser.getAttributeValue(null, "type"); + String value1 = parser.getAttributeValue(null, "value1"); + String value2 = parser.getAttributeValue(null, "value2"); + + try { + if ("long".equals(type)) { + extras.putLong(name, Long.parseLong(value1)); + } else if ("integer".equals(type)) { + extras.putInt(name, Integer.parseInt(value1)); + } else if ("double".equals(type)) { + extras.putDouble(name, Double.parseDouble(value1)); + } else if ("float".equals(type)) { + extras.putFloat(name, Float.parseFloat(value1)); + } else if ("boolean".equals(type)) { + extras.putBoolean(name, Boolean.parseBoolean(value1)); + } else if ("string".equals(type)) { + extras.putString(name, value1); + } else if ("account".equals(type)) { + extras.putParcelable(name, new Account(value1, value2)); + } + } catch (NumberFormatException e) { + Log.e(TAG, "error parsing bundle value", e); + } catch (NullPointerException e) { + Log.e(TAG, "error parsing bundle value", e); + } + } + + /** + * Write all account information to the account file. + */ + private void writeAccountInfoLocked() { + if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); + FileOutputStream fos = null; + + try { + fos = mAccountInfoFile.startWrite(); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + out.startDocument(null, true); + out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + out.startTag(null, "accounts"); + out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION)); + out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId)); + out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset)); + + // Write the Sync Automatically flags for each user + final int M = mMasterSyncAutomatically.size(); + for (int m = 0; m < M; m++) { + int userId = mMasterSyncAutomatically.keyAt(m); + Boolean listen = mMasterSyncAutomatically.valueAt(m); + out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES); + out.attribute(null, XML_ATTR_USER, Integer.toString(userId)); + out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen)); + out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES); + } + + final int N = mAuthorities.size(); + for (int i=0; i<N; i++) { + AuthorityInfo authority = mAuthorities.valueAt(i); + out.startTag(null, "authority"); + out.attribute(null, "id", Integer.toString(authority.ident)); + out.attribute(null, "account", authority.account.name); + out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId)); + out.attribute(null, "type", authority.account.type); + out.attribute(null, "authority", authority.authority); + out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled)); + if (authority.syncable < 0) { + out.attribute(null, "syncable", "unknown"); + } else { + out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0)); + } + for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) { + out.startTag(null, "periodicSync"); + out.attribute(null, "period", Long.toString(periodicSync.second)); + final Bundle extras = periodicSync.first; + for (String key : extras.keySet()) { + out.startTag(null, "extra"); + out.attribute(null, "name", key); + final Object value = extras.get(key); + if (value instanceof Long) { + out.attribute(null, "type", "long"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Integer) { + out.attribute(null, "type", "integer"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Boolean) { + out.attribute(null, "type", "boolean"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Float) { + out.attribute(null, "type", "float"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Double) { + out.attribute(null, "type", "double"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof String) { + out.attribute(null, "type", "string"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Account) { + out.attribute(null, "type", "account"); + out.attribute(null, "value1", ((Account)value).name); + out.attribute(null, "value2", ((Account)value).type); + } + out.endTag(null, "extra"); + } + out.endTag(null, "periodicSync"); + } + out.endTag(null, "authority"); + } + + out.endTag(null, "accounts"); + + out.endDocument(); + + mAccountInfoFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing accounts", e1); + if (fos != null) { + mAccountInfoFile.failWrite(fos); + } + } + } + + static int getIntColumn(Cursor c, String name) { + return c.getInt(c.getColumnIndex(name)); + } + + static long getLongColumn(Cursor c, String name) { + return c.getLong(c.getColumnIndex(name)); + } + + /** + * Load sync engine state from the old syncmanager database, and then + * erase it. Note that we don't deal with pending operations, active + * sync, or history. + */ + private void readAndDeleteLegacyAccountInfoLocked() { + // Look for old database to initialize from. + File file = mContext.getDatabasePath("syncmanager.db"); + if (!file.exists()) { + return; + } + String path = file.getPath(); + SQLiteDatabase db = null; + try { + db = SQLiteDatabase.openDatabase(path, null, + SQLiteDatabase.OPEN_READONLY); + } catch (SQLiteException e) { + } + + if (db != null) { + final boolean hasType = db.getVersion() >= 11; + + // Copy in all of the status information, as well as accounts. + if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db"); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables("stats, status"); + HashMap<String,String> map = new HashMap<String,String>(); + map.put("_id", "status._id as _id"); + map.put("account", "stats.account as account"); + if (hasType) { + map.put("account_type", "stats.account_type as account_type"); + } + map.put("authority", "stats.authority as authority"); + map.put("totalElapsedTime", "totalElapsedTime"); + map.put("numSyncs", "numSyncs"); + map.put("numSourceLocal", "numSourceLocal"); + map.put("numSourcePoll", "numSourcePoll"); + map.put("numSourceServer", "numSourceServer"); + map.put("numSourceUser", "numSourceUser"); + map.put("lastSuccessSource", "lastSuccessSource"); + map.put("lastSuccessTime", "lastSuccessTime"); + map.put("lastFailureSource", "lastFailureSource"); + map.put("lastFailureTime", "lastFailureTime"); + map.put("lastFailureMesg", "lastFailureMesg"); + map.put("pending", "pending"); + qb.setProjectionMap(map); + qb.appendWhere("stats._id = status.stats_id"); + Cursor c = qb.query(db, null, null, null, null, null, null); + while (c.moveToNext()) { + String accountName = c.getString(c.getColumnIndex("account")); + String accountType = hasType + ? c.getString(c.getColumnIndex("account_type")) : null; + if (accountType == null) { + accountType = "com.google"; + } + String authorityName = c.getString(c.getColumnIndex("authority")); + AuthorityInfo authority = this.getOrCreateAuthorityLocked( + new Account(accountName, accountType), 0 /* legacy is single-user */, + authorityName, -1, false); + if (authority != null) { + int i = mSyncStatus.size(); + boolean found = false; + SyncStatusInfo st = null; + while (i > 0) { + i--; + st = mSyncStatus.valueAt(i); + if (st.authorityId == authority.ident) { + found = true; + break; + } + } + if (!found) { + st = new SyncStatusInfo(authority.ident); + mSyncStatus.put(authority.ident, st); + } + st.totalElapsedTime = getLongColumn(c, "totalElapsedTime"); + st.numSyncs = getIntColumn(c, "numSyncs"); + st.numSourceLocal = getIntColumn(c, "numSourceLocal"); + st.numSourcePoll = getIntColumn(c, "numSourcePoll"); + st.numSourceServer = getIntColumn(c, "numSourceServer"); + st.numSourceUser = getIntColumn(c, "numSourceUser"); + st.numSourcePeriodic = 0; + st.lastSuccessSource = getIntColumn(c, "lastSuccessSource"); + st.lastSuccessTime = getLongColumn(c, "lastSuccessTime"); + st.lastFailureSource = getIntColumn(c, "lastFailureSource"); + st.lastFailureTime = getLongColumn(c, "lastFailureTime"); + st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg")); + st.pending = getIntColumn(c, "pending") != 0; + } + } + + c.close(); + + // Retrieve the settings. + qb = new SQLiteQueryBuilder(); + qb.setTables("settings"); + c = qb.query(db, null, null, null, null, null, null); + while (c.moveToNext()) { + String name = c.getString(c.getColumnIndex("name")); + String value = c.getString(c.getColumnIndex("value")); + if (name == null) continue; + if (name.equals("listen_for_tickles")) { + setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0); + } else if (name.startsWith("sync_provider_")) { + String provider = name.substring("sync_provider_".length(), + name.length()); + int i = mAuthorities.size(); + while (i > 0) { + i--; + AuthorityInfo authority = mAuthorities.valueAt(i); + if (authority.authority.equals(provider)) { + authority.enabled = value == null || Boolean.parseBoolean(value); + authority.syncable = 1; + } + } + } + } + + c.close(); + + db.close(); + + (new File(path)).delete(); + } + } + + public static final int STATUS_FILE_END = 0; + public static final int STATUS_FILE_ITEM = 100; + + /** + * Read all sync status back in to the initial engine state. + */ + private void readStatusLocked() { + if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile()); + try { + byte[] data = mStatusFile.readFully(); + Parcel in = Parcel.obtain(); + in.unmarshall(data, 0, data.length); + in.setDataPosition(0); + int token; + while ((token=in.readInt()) != STATUS_FILE_END) { + if (token == STATUS_FILE_ITEM) { + SyncStatusInfo status = new SyncStatusInfo(in); + if (mAuthorities.indexOfKey(status.authorityId) >= 0) { + status.pending = false; + if (DEBUG_FILE) Log.v(TAG, "Adding status for id " + + status.authorityId); + mSyncStatus.put(status.authorityId, status); + } + } else { + // Ooops. + Log.w(TAG, "Unknown status token: " + token); + break; + } + } + } catch (java.io.IOException e) { + Log.i(TAG, "No initial status"); + } + } + + /** + * Write all sync status to the sync status file. + */ + private void writeStatusLocked() { + if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile()); + + // The file is being written, so we don't need to have a scheduled + // write until the next change. + removeMessages(MSG_WRITE_STATUS); + + FileOutputStream fos = null; + try { + fos = mStatusFile.startWrite(); + Parcel out = Parcel.obtain(); + final int N = mSyncStatus.size(); + for (int i=0; i<N; i++) { + SyncStatusInfo status = mSyncStatus.valueAt(i); + out.writeInt(STATUS_FILE_ITEM); + status.writeToParcel(out, 0); + } + out.writeInt(STATUS_FILE_END); + fos.write(out.marshall()); + out.recycle(); + + mStatusFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing status", e1); + if (fos != null) { + mStatusFile.failWrite(fos); + } + } + } + + public static final int PENDING_OPERATION_VERSION = 3; + + /** + * Read all pending operations back in to the initial engine state. + */ + private void readPendingOperationsLocked() { + if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile()); + try { + byte[] data = mPendingFile.readFully(); + Parcel in = Parcel.obtain(); + in.unmarshall(data, 0, data.length); + in.setDataPosition(0); + final int SIZE = in.dataSize(); + while (in.dataPosition() < SIZE) { + int version = in.readInt(); + if (version != PENDING_OPERATION_VERSION && version != 1) { + Log.w(TAG, "Unknown pending operation version " + + version + "; dropping all ops"); + break; + } + int authorityId = in.readInt(); + int syncSource = in.readInt(); + byte[] flatExtras = in.createByteArray(); + boolean expedited; + if (version == PENDING_OPERATION_VERSION) { + expedited = in.readInt() != 0; + } else { + expedited = false; + } + int reason = in.readInt(); + AuthorityInfo authority = mAuthorities.get(authorityId); + if (authority != null) { + Bundle extras; + if (flatExtras != null) { + extras = unflattenBundle(flatExtras); + } else { + // if we are unable to parse the extras for whatever reason convert this + // to a regular sync by creating an empty extras + extras = new Bundle(); + } + PendingOperation op = new PendingOperation( + authority.account, authority.userId, reason, syncSource, + authority.authority, extras, expedited); + op.authorityId = authorityId; + op.flatExtras = flatExtras; + if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account + + " auth=" + op.authority + + " src=" + op.syncSource + + " reason=" + op.reason + + " expedited=" + op.expedited + + " extras=" + op.extras); + mPendingOperations.add(op); + } + } + } catch (java.io.IOException e) { + Log.i(TAG, "No initial pending operations"); + } + } + + private void writePendingOperationLocked(PendingOperation op, Parcel out) { + out.writeInt(PENDING_OPERATION_VERSION); + out.writeInt(op.authorityId); + out.writeInt(op.syncSource); + if (op.flatExtras == null && op.extras != null) { + op.flatExtras = flattenBundle(op.extras); + } + out.writeByteArray(op.flatExtras); + out.writeInt(op.expedited ? 1 : 0); + out.writeInt(op.reason); + } + + /** + * Write all currently pending ops to the pending ops file. + */ + private void writePendingOperationsLocked() { + final int N = mPendingOperations.size(); + FileOutputStream fos = null; + try { + if (N == 0) { + if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile()); + mPendingFile.truncate(); + return; + } + + if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile()); + fos = mPendingFile.startWrite(); + + Parcel out = Parcel.obtain(); + for (int i=0; i<N; i++) { + PendingOperation op = mPendingOperations.get(i); + writePendingOperationLocked(op, out); + } + fos.write(out.marshall()); + out.recycle(); + + mPendingFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing pending operations", e1); + if (fos != null) { + mPendingFile.failWrite(fos); + } + } + } + + /** + * Append the given operation to the pending ops file; if unable to, + * write all pending ops. + */ + private void appendPendingOperationLocked(PendingOperation op) { + if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); + FileOutputStream fos = null; + try { + fos = mPendingFile.openAppend(); + } catch (java.io.IOException e) { + if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file"); + writePendingOperationsLocked(); + return; + } + + try { + Parcel out = Parcel.obtain(); + writePendingOperationLocked(op, out); + fos.write(out.marshall()); + out.recycle(); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing pending operations", e1); + } finally { + try { + fos.close(); + } catch (java.io.IOException e2) { + } + } + } + + static private byte[] flattenBundle(Bundle bundle) { + byte[] flatData = null; + Parcel parcel = Parcel.obtain(); + try { + bundle.writeToParcel(parcel, 0); + flatData = parcel.marshall(); + } finally { + parcel.recycle(); + } + return flatData; + } + + static private Bundle unflattenBundle(byte[] flatData) { + Bundle bundle; + Parcel parcel = Parcel.obtain(); + try { + parcel.unmarshall(flatData, 0, flatData.length); + parcel.setDataPosition(0); + bundle = parcel.readBundle(); + } catch (RuntimeException e) { + // A RuntimeException is thrown if we were unable to parse the parcel. + // Create an empty parcel in this case. + bundle = new Bundle(); + } finally { + parcel.recycle(); + } + return bundle; + } + + private void requestSync(Account account, int userId, int reason, String authority, + Bundle extras) { + // If this is happening in the system process, then call the syncrequest listener + // to make a request back to the SyncManager directly. + // If this is probably a test instance, then call back through the ContentResolver + // which will know which userId to apply based on the Binder id. + if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID + && mSyncRequestListener != null) { + mSyncRequestListener.onSyncRequest(account, userId, reason, authority, extras); + } else { + ContentResolver.requestSync(account, authority, extras); + } + } + + public static final int STATISTICS_FILE_END = 0; + public static final int STATISTICS_FILE_ITEM_OLD = 100; + public static final int STATISTICS_FILE_ITEM = 101; + + /** + * Read all sync statistics back in to the initial engine state. + */ + private void readStatisticsLocked() { + try { + byte[] data = mStatisticsFile.readFully(); + Parcel in = Parcel.obtain(); + in.unmarshall(data, 0, data.length); + in.setDataPosition(0); + int token; + int index = 0; + while ((token=in.readInt()) != STATISTICS_FILE_END) { + if (token == STATISTICS_FILE_ITEM + || token == STATISTICS_FILE_ITEM_OLD) { + int day = in.readInt(); + if (token == STATISTICS_FILE_ITEM_OLD) { + day = day - 2009 + 14245; // Magic! + } + DayStats ds = new DayStats(day); + ds.successCount = in.readInt(); + ds.successTime = in.readLong(); + ds.failureCount = in.readInt(); + ds.failureTime = in.readLong(); + if (index < mDayStats.length) { + mDayStats[index] = ds; + index++; + } + } else { + // Ooops. + Log.w(TAG, "Unknown stats token: " + token); + break; + } + } + } catch (java.io.IOException e) { + Log.i(TAG, "No initial statistics"); + } + } + + /** + * Write all sync statistics to the sync status file. + */ + private void writeStatisticsLocked() { + if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); + + // The file is being written, so we don't need to have a scheduled + // write until the next change. + removeMessages(MSG_WRITE_STATISTICS); + + FileOutputStream fos = null; + try { + fos = mStatisticsFile.startWrite(); + Parcel out = Parcel.obtain(); + final int N = mDayStats.length; + for (int i=0; i<N; i++) { + DayStats ds = mDayStats[i]; + if (ds == null) { + break; + } + out.writeInt(STATISTICS_FILE_ITEM); + out.writeInt(ds.day); + out.writeInt(ds.successCount); + out.writeLong(ds.successTime); + out.writeInt(ds.failureCount); + out.writeLong(ds.failureTime); + } + out.writeInt(STATISTICS_FILE_END); + fos.write(out.marshall()); + out.recycle(); + + mStatisticsFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing stats", e1); + if (fos != null) { + mStatisticsFile.failWrite(fos); + } + } + } +} diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java index b37d57f..ee2d617 100644 --- a/services/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/java/com/android/server/display/LocalDisplayAdapter.java @@ -60,31 +60,38 @@ final class LocalDisplayAdapter extends DisplayAdapter { super.registerLocked(); mHotplugReceiver = new HotplugDisplayEventReceiver(getHandler().getLooper()); - scanDisplaysLocked(); - } - private void scanDisplaysLocked() { for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) { - IBinder displayToken = Surface.getBuiltInDisplay(builtInDisplayId); - if (displayToken != null && Surface.getDisplayInfo(displayToken, mTempPhys)) { - LocalDisplayDevice device = mDevices.get(builtInDisplayId); - if (device == null) { - // Display was added. - device = new LocalDisplayDevice(displayToken, builtInDisplayId, mTempPhys); - mDevices.put(builtInDisplayId, device); - sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); - } else if (device.updatePhysicalDisplayInfoLocked(mTempPhys)) { - // Display properties changed. - sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); - } - } else { - LocalDisplayDevice device = mDevices.get(builtInDisplayId); - if (device != null) { - // Display was removed. - mDevices.remove(builtInDisplayId); - sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED); - } + tryConnectDisplayLocked(builtInDisplayId); + } + } + + private void tryConnectDisplayLocked(int builtInDisplayId) { + IBinder displayToken = Surface.getBuiltInDisplay(builtInDisplayId); + if (displayToken != null && Surface.getDisplayInfo(displayToken, mTempPhys)) { + LocalDisplayDevice device = mDevices.get(builtInDisplayId); + if (device == null) { + // Display was added. + device = new LocalDisplayDevice(displayToken, builtInDisplayId, mTempPhys); + mDevices.put(builtInDisplayId, device); + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); + } else if (device.updatePhysicalDisplayInfoLocked(mTempPhys)) { + // Display properties changed. + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); } + } else { + // The display is no longer available. Ignore the attempt to add it. + // If it was connected but has already been disconnected, we'll get a + // disconnect event that will remove it from mDevices. + } + } + + private void tryDisconnectDisplayLocked(int builtInDisplayId) { + LocalDisplayDevice device = mDevices.get(builtInDisplayId); + if (device != null) { + // Display was removed. + mDevices.remove(builtInDisplayId); + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED); } } @@ -191,7 +198,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { @Override public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) { synchronized (getSyncRoot()) { - scanDisplaysLocked(); + if (connected) { + tryConnectDisplayLocked(builtInDisplayId); + } else { + tryDisconnectDisplayLocked(builtInDisplayId); + } } } } diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index c272da4..efba10d 100644 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -1452,11 +1452,11 @@ public class GpsLocationProvider implements LocationProviderInterface { private void requestRefLocation(int flags) { TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) { + final int phoneType = phone.getPhoneType(); + if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation(); - if ((gsm_cell != null) && (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) && - (phone.getNetworkOperator() != null) && - (phone.getNetworkOperator().length() > 3)) { + if ((gsm_cell != null) && (phone.getNetworkOperator() != null) + && (phone.getNetworkOperator().length() > 3)) { int type; int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0,3)); int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3)); @@ -1474,9 +1474,8 @@ public class GpsLocationProvider implements LocationProviderInterface { } else { Log.e(TAG,"Error getting cell location info."); } - } - else { - Log.e(TAG,"CDMA not supported."); + } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { + Log.e(TAG, "CDMA not supported."); } } diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index b09390c..cbd2a0f 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -52,7 +52,6 @@ import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.TrafficStats.MB_IN_BYTES; -import static android.net.wifi.WifiInfo.removeDoubleQuotes; import static android.net.wifi.WifiManager.CHANGE_REASON_ADDED; import static android.net.wifi.WifiManager.CHANGE_REASON_REMOVED; import static android.net.wifi.WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION; @@ -551,8 +550,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final WifiConfiguration config = intent.getParcelableExtra( EXTRA_WIFI_CONFIGURATION); if (config.SSID != null) { - final NetworkTemplate template = NetworkTemplate.buildTemplateWifi( - removeDoubleQuotes(config.SSID)); + final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(config.SSID); synchronized (mRulesLock) { if (mNetworkPolicy.containsKey(template)) { mNetworkPolicy.remove(template); @@ -581,8 +579,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final WifiInfo info = intent.getParcelableExtra(EXTRA_WIFI_INFO); final boolean meteredHint = info.getMeteredHint(); - final NetworkTemplate template = NetworkTemplate.buildTemplateWifi( - removeDoubleQuotes(info.getSSID())); + final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(info.getSSID()); synchronized (mRulesLock) { NetworkPolicy policy = mNetworkPolicy.get(template); if (policy == null && meteredHint) { diff --git a/services/java/com/android/server/os/SchedulingPolicyService.java b/services/java/com/android/server/os/SchedulingPolicyService.java new file mode 100644 index 0000000..c0123bf --- /dev/null +++ b/services/java/com/android/server/os/SchedulingPolicyService.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.os; + +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.ISchedulingPolicyService; +import android.os.Process; + +/** + * The implementation of the scheduling policy service interface. + * + * @hide + */ +public class SchedulingPolicyService extends ISchedulingPolicyService.Stub { + + private static final String TAG = "SchedulingPolicyService"; + + // Minimum and maximum values allowed for requestPriority parameter prio + private static final int PRIORITY_MIN = 1; + private static final int PRIORITY_MAX = 3; + + public SchedulingPolicyService() { + } + + public int requestPriority(int pid, int tid, int prio) { + //Log.i(TAG, "requestPriority(pid=" + pid + ", tid=" + tid + ", prio=" + prio + ")"); + + // Verify that caller is mediaserver, priority is in range, and that the + // callback thread specified by app belongs to the app that called mediaserver. + // Once we've verified that the caller is mediaserver, we can trust the pid but + // we can't trust the tid. No need to explicitly check for pid == 0 || tid == 0, + // since if not the case then the getThreadGroupLeader() test will also fail. + if (Binder.getCallingUid() != Process.MEDIA_UID || prio < PRIORITY_MIN || + prio > PRIORITY_MAX || Process.getThreadGroupLeader(tid) != pid) { + return PackageManager.PERMISSION_DENIED; + } + try { + // make good use of our CAP_SYS_NICE capability + Process.setThreadGroup(tid, Binder.getCallingPid() == pid ? + Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_AUDIO_APP); + // must be in this order or it fails the schedulability constraint + Process.setThreadScheduler(tid, Process.SCHED_FIFO, prio); + } catch (RuntimeException e) { + return PackageManager.PERMISSION_DENIED; + } + return PackageManager.PERMISSION_GRANTED; + } + +} diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index 71a6a01..e96820f 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -188,7 +188,12 @@ public final class Installer { } } - public int install(String name, int uid, int gid) { + /** + * @param restrictHomeDir if {@code true}, installd will create the application's + * home directory with {@code 0700} permissions. If false, {@code 0751} will + * be used instead. + */ + public int install(String name, int uid, int gid, boolean restrictHomeDir) { StringBuilder builder = new StringBuilder("install"); builder.append(' '); builder.append(name); @@ -196,6 +201,8 @@ public final class Installer { builder.append(uid); builder.append(' '); builder.append(gid); + builder.append(' '); + builder.append(restrictHomeDir); return execute(builder.toString()); } @@ -263,7 +270,12 @@ public final class Installer { return execute(builder.toString()); } - public int createUserData(String name, int uid, int userId) { + /** + * @param restrictHomeDir if {@code true}, installd will create the application's + * home directory with {@code 0700} permissions. If false, {@code 0751} will + * be used instead. + */ + public int createUserData(String name, int uid, int userId, boolean restrictHomeDir) { StringBuilder builder = new StringBuilder("mkuserdata"); builder.append(' '); builder.append(name); @@ -271,6 +283,8 @@ public final class Installer { builder.append(uid); builder.append(' '); builder.append(userId); + builder.append(' '); + builder.append(restrictHomeDir); return execute(builder.toString()); } @@ -290,27 +304,6 @@ public final class Installer { return execute(builder.toString()); } - /** - * Clone all the package data directories from srcUserId to targetUserId. If copyData is true, - * some of the data is also copied, otherwise just empty directories are created with the - * correct access rights. - * @param srcUserId user to copy the data directories from - * @param targetUserId user to copy the data directories to - * @param copyData whether the data itself is to be copied. If false, empty directories are - * created. - * @return success/error code - */ - public int cloneUserData(int srcUserId, int targetUserId, boolean copyData) { - StringBuilder builder = new StringBuilder("cloneuserdata"); - builder.append(' '); - builder.append(srcUserId); - builder.append(' '); - builder.append(targetUserId); - builder.append(' '); - builder.append(copyData ? '1' : '0'); - return execute(builder.toString()); - } - public boolean ping() { if (execute("ping") < 0) { return false; diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 2238f17..aa2b461 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -24,7 +24,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static com.android.internal.util.ArrayUtils.appendInt; import static com.android.internal.util.ArrayUtils.removeInt; -import static libcore.io.OsConstants.S_ISLNK; import static libcore.io.OsConstants.S_IRWXU; import static libcore.io.OsConstants.S_IRGRP; import static libcore.io.OsConstants.S_IXGRP; @@ -111,7 +110,6 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.Environment.UserEnvironment; -import android.provider.Settings.Secure; import android.security.SystemKeyStore; import android.util.DisplayMetrics; import android.util.EventLog; @@ -148,13 +146,11 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import libcore.io.ErrnoException; import libcore.io.IoUtils; import libcore.io.Libcore; -import libcore.io.OsConstants; import libcore.io.StructStat; /** @@ -3582,16 +3578,17 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private int createDataDirsLI(String packageName, int uid) { + private int createDataDirsLI(String packageName, int uid, int targetSdkVersion) { int[] users = sUserManager.getUserIds(); - int res = mInstaller.install(packageName, uid, uid); + boolean restrictHomeDir = (targetSdkVersion >= Build.VERSION_CODES.K); + int res = mInstaller.install(packageName, uid, uid, restrictHomeDir); if (res < 0) { return res; } for (int user : users) { if (user != 0) { res = mInstaller.createUserData(packageName, - UserHandle.getUid(user, uid), user); + UserHandle.getUid(user, uid), user, restrictHomeDir); if (res < 0) { return res; } @@ -3985,7 +3982,8 @@ public class PackageManagerService extends IPackageManager.Stub { recovered = true; // And now re-install the app. - ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid); + ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid, + pkg.applicationInfo.targetSdkVersion); if (ret == -1) { // Ack should not happen! msg = prefix + pkg.packageName @@ -4030,8 +4028,9 @@ public class PackageManagerService extends IPackageManager.Stub { if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) Log.v(TAG, "Want this data dir: " + dataPath); } - //invoke installer to do the actual installation - int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid); + // invoke installer to do the actual installation + int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid, + pkg.applicationInfo.targetSdkVersion); if (ret < 0) { // Error from installer mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 06f11bc..f791a6e 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -25,7 +25,6 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; -import com.android.server.IntentResolver; import com.android.server.pm.PackageManagerService.DumpState; import org.xmlpull.v1.XmlPullParser; @@ -46,6 +45,7 @@ import android.content.pm.UserInfo; import android.content.pm.PackageUserState; import android.content.pm.VerifierDeviceIdentity; import android.os.Binder; +import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.Process; @@ -2335,9 +2335,11 @@ final class Settings { for (PackageSetting ps : mPackages.values()) { // Only system apps are initially installed. ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle); + boolean restrictHomeDir = (ps.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.K); // Need to create a data directory for all apps under this user. installer.createUserData(ps.name, - UserHandle.getUid(userHandle, ps.appId), userHandle); + UserHandle.getUid(userHandle, ps.appId), userHandle, + restrictHomeDir); } readDefaultPreferredAppsLPw(userHandle); writePackageRestrictionsLPr(userHandle); diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index 5a5d910..ec82290 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -238,11 +238,20 @@ public final class PowerManagerService extends IPowerManager.Stub // is actually on or actually off or whatever was requested. private boolean mDisplayReady; - // True if holding a wake-lock to block suspend of the CPU. + // The suspend blocker used to keep the CPU alive when an application has acquired + // a wake lock. + private final SuspendBlocker mWakeLockSuspendBlocker; + + // True if the wake lock suspend blocker has been acquired. private boolean mHoldingWakeLockSuspendBlocker; - // The suspend blocker used to keep the CPU alive when wake locks have been acquired. - private final SuspendBlocker mWakeLockSuspendBlocker; + // The suspend blocker used to keep the CPU alive when the display is on, the + // display is getting ready or there is user activity (in which case the display + // must be on). + private final SuspendBlocker mDisplaySuspendBlocker; + + // True if the display suspend blocker has been acquired. + private boolean mHoldingDisplaySuspendBlocker; // The screen on blocker used to keep the screen from turning on while the lock // screen is coming up. @@ -368,11 +377,13 @@ public final class PowerManagerService extends IPowerManager.Stub public PowerManagerService() { synchronized (mLock) { - mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService"); - mWakeLockSuspendBlocker.acquire(); + mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks"); + mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display"); + mDisplaySuspendBlocker.acquire(); + mHoldingDisplaySuspendBlocker = true; + mScreenOnBlocker = new ScreenOnBlockerImpl(); mDisplayBlanker = new DisplayBlankerImpl(); - mHoldingWakeLockSuspendBlocker = true; mWakefulness = WAKEFULNESS_AWAKE; } @@ -653,17 +664,21 @@ public final class PowerManagerService extends IPowerManager.Stub private void releaseWakeLockInternal(IBinder lock, int flags) { synchronized (mLock) { - if (DEBUG_SPEW) { - Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock) - + ", flags=0x" + Integer.toHexString(flags)); - } - int index = findWakeLockIndexLocked(lock); if (index < 0) { + if (DEBUG_SPEW) { + Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock) + + " [not found], flags=0x" + Integer.toHexString(flags)); + } return; } WakeLock wakeLock = mWakeLocks.get(index); + if (DEBUG_SPEW) { + Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock) + + " [" + wakeLock.mTag + "], flags=0x" + Integer.toHexString(flags)); + } + mWakeLocks.remove(index); notifyWakeLockReleasedLocked(wakeLock); wakeLock.mLock.unlinkToDeath(wakeLock, 0); @@ -681,7 +696,8 @@ public final class PowerManagerService extends IPowerManager.Stub private void handleWakeLockDeath(WakeLock wakeLock) { synchronized (mLock) { if (DEBUG_SPEW) { - Slog.d(TAG, "handleWakeLockDeath: lock=" + Objects.hashCode(wakeLock.mLock)); + Slog.d(TAG, "handleWakeLockDeath: lock=" + Objects.hashCode(wakeLock.mLock) + + " [" + wakeLock.mTag + "]"); } int index = mWakeLocks.indexOf(wakeLock); @@ -733,10 +749,19 @@ public final class PowerManagerService extends IPowerManager.Stub synchronized (mLock) { int index = findWakeLockIndexLocked(lock); if (index < 0) { + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock) + + " [not found], ws=" + ws); + } throw new IllegalArgumentException("Wake lock not active"); } WakeLock wakeLock = mWakeLocks.get(index); + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock) + + " [" + wakeLock.mTag + "], ws=" + ws); + } + if (!wakeLock.hasSameWorkSource(ws)) { notifyWakeLockReleasedLocked(wakeLock); wakeLock.updateWorkSource(ws); @@ -1719,29 +1744,30 @@ public final class PowerManagerService extends IPowerManager.Stub * This function must have no other side-effects. */ private void updateSuspendBlockerLocked() { - boolean wantCpu = isCpuNeededLocked(); - if (wantCpu != mHoldingWakeLockSuspendBlocker) { - mHoldingWakeLockSuspendBlocker = wantCpu; - if (wantCpu) { - if (DEBUG) { - Slog.d(TAG, "updateSuspendBlockerLocked: Acquiring suspend blocker."); - } - mWakeLockSuspendBlocker.acquire(); - } else { - if (DEBUG) { - Slog.d(TAG, "updateSuspendBlockerLocked: Releasing suspend blocker."); - } - mWakeLockSuspendBlocker.release(); - } + final boolean needWakeLockSuspendBlocker = (mWakeLockSummary != 0); + final boolean needDisplaySuspendBlocker = (mUserActivitySummary != 0 + || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF + || !mDisplayReady || !mBootCompleted); + + // First acquire suspend blockers if needed. + if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) { + mWakeLockSuspendBlocker.acquire(); + mHoldingWakeLockSuspendBlocker = true; + } + if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) { + mDisplaySuspendBlocker.acquire(); + mHoldingDisplaySuspendBlocker = true; } - } - private boolean isCpuNeededLocked() { - return !mBootCompleted - || mWakeLockSummary != 0 - || mUserActivitySummary != 0 - || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF - || !mDisplayReady; + // Then release suspend blockers if needed. + if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) { + mWakeLockSuspendBlocker.release(); + mHoldingWakeLockSuspendBlocker = false; + } + if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) { + mDisplaySuspendBlocker.release(); + mHoldingDisplaySuspendBlocker = false; + } } @Override // Binder call @@ -2211,6 +2237,7 @@ public final class PowerManagerService extends IPowerManager.Stub + TimeUtils.formatUptime(mLastUserActivityTimeNoChangeLights)); pw.println(" mDisplayReady=" + mDisplayReady); pw.println(" mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker); + pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker); pw.println(); pw.println("Settings and Configuration:"); @@ -2506,6 +2533,9 @@ public final class PowerManagerService extends IPowerManager.Stub synchronized (this) { mReferenceCount += 1; if (mReferenceCount == 1) { + if (DEBUG_SPEW) { + Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\"."); + } nativeAcquireSuspendBlocker(mName); } } @@ -2516,6 +2546,9 @@ public final class PowerManagerService extends IPowerManager.Stub synchronized (this) { mReferenceCount -= 1; if (mReferenceCount == 0) { + if (DEBUG_SPEW) { + Slog.d(TAG, "Releasing suspend blocker \"" + mName + "\"."); + } nativeReleaseSuspendBlocker(mName); } else if (mReferenceCount < 0) { Log.wtf(TAG, "Suspend blocker \"" + mName diff --git a/services/java/com/android/server/search/SearchManagerService.java b/services/java/com/android/server/search/SearchManagerService.java new file mode 100644 index 0000000..132ae79 --- /dev/null +++ b/services/java/com/android/server/search/SearchManagerService.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.search; + +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.AppGlobals; +import android.app.ISearchManager; +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.database.ContentObserver; +import android.os.Binder; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.content.PackageMonitor; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * The search manager service handles the search UI, and maintains a registry of searchable + * activities. + */ +public class SearchManagerService extends ISearchManager.Stub { + + // general debugging support + private static final String TAG = "SearchManagerService"; + + // Context that the service is running in. + private final Context mContext; + + // This field is initialized lazily in getSearchables(), and then never modified. + private final SparseArray<Searchables> mSearchables = new SparseArray<Searchables>(); + + /** + * Initializes the Search Manager service in the provided system context. + * Only one instance of this object should be created! + * + * @param context to use for accessing DB, window manager, etc. + */ + public SearchManagerService(Context context) { + mContext = context; + mContext.registerReceiver(new BootCompletedReceiver(), + new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + mContext.registerReceiver(new UserReceiver(), + new IntentFilter(Intent.ACTION_USER_REMOVED)); + new MyPackageMonitor().register(context, null, UserHandle.ALL, true); + } + + private Searchables getSearchables(int userId) { + long origId = Binder.clearCallingIdentity(); + try { + boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)) + .getUserInfo(userId) != null; + if (!userExists) return null; + } finally { + Binder.restoreCallingIdentity(origId); + } + synchronized (mSearchables) { + Searchables searchables = mSearchables.get(userId); + + if (searchables == null) { + //Log.i(TAG, "Building list of searchable activities for userId=" + userId); + searchables = new Searchables(mContext, userId); + searchables.buildSearchableList(); + mSearchables.append(userId, searchables); + } + return searchables; + } + } + + private void onUserRemoved(int userId) { + if (userId != UserHandle.USER_OWNER) { + synchronized (mSearchables) { + mSearchables.remove(userId); + } + } + } + + /** + * Creates the initial searchables list after boot. + */ + private final class BootCompletedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + new Thread() { + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + mContext.unregisterReceiver(BootCompletedReceiver.this); + getSearchables(0); + } + }.start(); + } + } + + private final class UserReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_OWNER)); + } + } + + /** + * Refreshes the "searchables" list when packages are added/removed. + */ + class MyPackageMonitor extends PackageMonitor { + + @Override + public void onSomePackagesChanged() { + updateSearchables(); + } + + @Override + public void onPackageModified(String pkg) { + updateSearchables(); + } + + private void updateSearchables() { + final int changingUserId = getChangingUserId(); + synchronized (mSearchables) { + // Update list of searchable activities + for (int i = 0; i < mSearchables.size(); i++) { + if (changingUserId == mSearchables.keyAt(i)) { + getSearchables(mSearchables.keyAt(i)).buildSearchableList(); + break; + } + } + } + // Inform all listeners that the list of searchables has been updated. + Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING + | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId)); + } + } + + class GlobalSearchProviderObserver extends ContentObserver { + private final ContentResolver mResolver; + + public GlobalSearchProviderObserver(ContentResolver resolver) { + super(null); + mResolver = resolver; + mResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY), + false /* notifyDescendants */, + this); + } + + @Override + public void onChange(boolean selfChange) { + synchronized (mSearchables) { + for (int i = 0; i < mSearchables.size(); i++) { + getSearchables(mSearchables.keyAt(i)).buildSearchableList(); + } + } + Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + + } + + // + // Searchable activities API + // + + /** + * Returns the SearchableInfo for a given activity. + * + * @param launchActivity The activity from which we're launching this search. + * @return Returns a SearchableInfo record describing the parameters of the search, + * or null if no searchable metadata was available. + */ + public SearchableInfo getSearchableInfo(final ComponentName launchActivity) { + if (launchActivity == null) { + Log.e(TAG, "getSearchableInfo(), activity == null"); + return null; + } + return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity); + } + + /** + * Returns a list of the searchable activities that can be included in global search. + */ + public List<SearchableInfo> getSearchablesInGlobalSearch() { + return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList(); + } + + public List<ResolveInfo> getGlobalSearchActivities() { + return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities(); + } + + /** + * Gets the name of the global search activity. + */ + public ComponentName getGlobalSearchActivity() { + return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity(); + } + + /** + * Gets the name of the web search activity. + */ + public ComponentName getWebSearchActivity() { + return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity(); + } + + @Override + public ComponentName getAssistIntent(int userHandle) { + try { + if (userHandle != UserHandle.getCallingUserId()) { + // Requesting a different user, make sure that they have the permission + if (ActivityManager.checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Binder.getCallingUid(), -1, true) + == PackageManager.PERMISSION_GRANTED) { + // Translate to the current user id, if caller wasn't aware + if (userHandle == UserHandle.USER_CURRENT) { + long identity = Binder.clearCallingIdentity(); + userHandle = ActivityManagerNative.getDefault().getCurrentUser().id; + Binder.restoreCallingIdentity(identity); + } + } else { + String msg = "Permission Denial: " + + "Request to getAssistIntent for " + userHandle + + " but is calling from user " + UserHandle.getCallingUserId() + + "; this requires " + + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + return null; + } + } + IPackageManager pm = AppGlobals.getPackageManager(); + Intent assistIntent = new Intent(Intent.ACTION_ASSIST); + ResolveInfo info = + pm.resolveIntent(assistIntent, + assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.MATCH_DEFAULT_ONLY, userHandle); + if (info != null) { + return new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + } + } catch (RemoteException re) { + // Local call + Log.e(TAG, "RemoteException in getAssistIntent: " + re); + } catch (Exception e) { + Log.e(TAG, "Exception in getAssistIntent: " + e); + } + return null; + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + synchronized (mSearchables) { + for (int i = 0; i < mSearchables.size(); i++) { + ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i)); + ipw.increaseIndent(); + mSearchables.valueAt(i).dump(fd, ipw, args); + ipw.decreaseIndent(); + } + } + } +} diff --git a/services/java/com/android/server/search/Searchables.java b/services/java/com/android/server/search/Searchables.java new file mode 100644 index 0000000..0ffbb7d --- /dev/null +++ b/services/java/com/android/server/search/Searchables.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.search; + +import android.app.AppGlobals; +import android.app.SearchManager; +import android.app.SearchableInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Binder; +import android.os.Bundle; +import android.os.RemoteException; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +/** + * This class maintains the information about all searchable activities. + * This is a hidden class. + */ +public class Searchables { + + private static final String LOG_TAG = "Searchables"; + + // static strings used for XML lookups, etc. + // TODO how should these be documented for the developer, in a more structured way than + // the current long wordy javadoc in SearchManager.java ? + private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable"; + private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*"; + + private Context mContext; + + private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null; + private ArrayList<SearchableInfo> mSearchablesList = null; + private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null; + // Contains all installed activities that handle the global search + // intent. + private List<ResolveInfo> mGlobalSearchActivities; + private ComponentName mCurrentGlobalSearchActivity = null; + private ComponentName mWebSearchActivity = null; + + public static String GOOGLE_SEARCH_COMPONENT_NAME = + "com.android.googlesearch/.GoogleSearch"; + public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME = + "com.google.android.providers.enhancedgooglesearch/.Launcher"; + + // Cache the package manager instance + final private IPackageManager mPm; + // User for which this Searchables caches information + private int mUserId; + + /** + * + * @param context Context to use for looking up activities etc. + */ + public Searchables (Context context, int userId) { + mContext = context; + mUserId = userId; + mPm = AppGlobals.getPackageManager(); + } + + /** + * Look up, or construct, based on the activity. + * + * The activities fall into three cases, based on meta-data found in + * the manifest entry: + * <ol> + * <li>The activity itself implements search. This is indicated by the + * presence of a "android.app.searchable" meta-data attribute. + * The value is a reference to an XML file containing search information.</li> + * <li>A related activity implements search. This is indicated by the + * presence of a "android.app.default_searchable" meta-data attribute. + * The value is a string naming the activity implementing search. In this + * case the factory will "redirect" and return the searchable data.</li> + * <li>No searchability data is provided. We return null here and other + * code will insert the "default" (e.g. contacts) search. + * + * TODO: cache the result in the map, and check the map first. + * TODO: it might make sense to implement the searchable reference as + * an application meta-data entry. This way we don't have to pepper each + * and every activity. + * TODO: can we skip the constructor step if it's a non-searchable? + * TODO: does it make sense to plug the default into a slot here for + * automatic return? Probably not, but it's one way to do it. + * + * @param activity The name of the current activity, or null if the + * activity does not define any explicit searchable metadata. + */ + public SearchableInfo getSearchableInfo(ComponentName activity) { + // Step 1. Is the result already hashed? (case 1) + SearchableInfo result; + synchronized (this) { + result = mSearchablesMap.get(activity); + if (result != null) return result; + } + + // Step 2. See if the current activity references a searchable. + // Note: Conceptually, this could be a while(true) loop, but there's + // no point in implementing reference chaining here and risking a loop. + // References must point directly to searchable activities. + + ActivityInfo ai = null; + try { + ai = mPm.getActivityInfo(activity, PackageManager.GET_META_DATA, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting activity info " + re); + return null; + } + String refActivityName = null; + + // First look for activity-specific reference + Bundle md = ai.metaData; + if (md != null) { + refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE); + } + // If not found, try for app-wide reference + if (refActivityName == null) { + md = ai.applicationInfo.metaData; + if (md != null) { + refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE); + } + } + + // Irrespective of source, if a reference was found, follow it. + if (refActivityName != null) + { + // This value is deprecated, return null + if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) { + return null; + } + String pkg = activity.getPackageName(); + ComponentName referredActivity; + if (refActivityName.charAt(0) == '.') { + referredActivity = new ComponentName(pkg, pkg + refActivityName); + } else { + referredActivity = new ComponentName(pkg, refActivityName); + } + + // Now try the referred activity, and if found, cache + // it against the original name so we can skip the check + synchronized (this) { + result = mSearchablesMap.get(referredActivity); + if (result != null) { + mSearchablesMap.put(activity, result); + return result; + } + } + } + + // Step 3. None found. Return null. + return null; + + } + + /** + * Builds an entire list (suitable for display) of + * activities that are searchable, by iterating the entire set of + * ACTION_SEARCH & ACTION_WEB_SEARCH intents. + * + * Also clears the hash of all activities -> searches which will + * refill as the user clicks "search". + * + * This should only be done at startup and again if we know that the + * list has changed. + * + * TODO: every activity that provides a ACTION_SEARCH intent should + * also provide searchability meta-data. There are a bunch of checks here + * that, if data is not found, silently skip to the next activity. This + * won't help a developer trying to figure out why their activity isn't + * showing up in the list, but an exception here is too rough. I would + * like to find a better notification mechanism. + * + * TODO: sort the list somehow? UI choice. + */ + public void buildSearchableList() { + // These will become the new values at the end of the method + HashMap<ComponentName, SearchableInfo> newSearchablesMap + = new HashMap<ComponentName, SearchableInfo>(); + ArrayList<SearchableInfo> newSearchablesList + = new ArrayList<SearchableInfo>(); + ArrayList<SearchableInfo> newSearchablesInGlobalSearchList + = new ArrayList<SearchableInfo>(); + + // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers. + List<ResolveInfo> searchList; + final Intent intent = new Intent(Intent.ACTION_SEARCH); + + long ident = Binder.clearCallingIdentity(); + try { + searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA); + + List<ResolveInfo> webSearchInfoList; + final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH); + webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA); + + // analyze each one, generate a Searchables record, and record + if (searchList != null || webSearchInfoList != null) { + int search_count = (searchList == null ? 0 : searchList.size()); + int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size()); + int count = search_count + web_search_count; + for (int ii = 0; ii < count; ii++) { + // for each component, try to find metadata + ResolveInfo info = (ii < search_count) + ? searchList.get(ii) + : webSearchInfoList.get(ii - search_count); + ActivityInfo ai = info.activityInfo; + // Check first to avoid duplicate entries. + if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) { + SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai, + mUserId); + if (searchable != null) { + newSearchablesList.add(searchable); + newSearchablesMap.put(searchable.getSearchActivity(), searchable); + if (searchable.shouldIncludeInGlobalSearch()) { + newSearchablesInGlobalSearchList.add(searchable); + } + } + } + } + } + + List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities(); + + // Find the global search activity + ComponentName newGlobalSearchActivity = findGlobalSearchActivity( + newGlobalSearchActivities); + + // Find the web search activity + ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity); + + // Store a consistent set of new values + synchronized (this) { + mSearchablesMap = newSearchablesMap; + mSearchablesList = newSearchablesList; + mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList; + mGlobalSearchActivities = newGlobalSearchActivities; + mCurrentGlobalSearchActivity = newGlobalSearchActivity; + mWebSearchActivity = newWebSearchActivity; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * Returns a sorted list of installed search providers as per + * the following heuristics: + * + * (a) System apps are given priority over non system apps. + * (b) Among system apps and non system apps, the relative ordering + * is defined by their declared priority. + */ + private List<ResolveInfo> findGlobalSearchActivities() { + // Step 1 : Query the package manager for a list + // of activities that can handle the GLOBAL_SEARCH intent. + Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); + List<ResolveInfo> activities = + queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + if (activities != null && !activities.isEmpty()) { + // Step 2: Rank matching activities according to our heuristics. + Collections.sort(activities, GLOBAL_SEARCH_RANKER); + } + + return activities; + } + + /** + * Finds the global search activity. + */ + private ComponentName findGlobalSearchActivity(List<ResolveInfo> installed) { + // Fetch the global search provider from the system settings, + // and if it's still installed, return it. + final String searchProviderSetting = getGlobalSearchProviderSetting(); + if (!TextUtils.isEmpty(searchProviderSetting)) { + final ComponentName globalSearchComponent = ComponentName.unflattenFromString( + searchProviderSetting); + if (globalSearchComponent != null && isInstalled(globalSearchComponent)) { + return globalSearchComponent; + } + } + + return getDefaultGlobalSearchProvider(installed); + } + + /** + * Checks whether the global search provider with a given + * component name is installed on the system or not. This deals with + * cases such as the removal of an installed provider. + */ + private boolean isInstalled(ComponentName globalSearch) { + Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); + intent.setComponent(globalSearch); + + List<ResolveInfo> activities = queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + if (activities != null && !activities.isEmpty()) { + return true; + } + + return false; + } + + private static final Comparator<ResolveInfo> GLOBAL_SEARCH_RANKER = + new Comparator<ResolveInfo>() { + @Override + public int compare(ResolveInfo lhs, ResolveInfo rhs) { + if (lhs == rhs) { + return 0; + } + boolean lhsSystem = isSystemApp(lhs); + boolean rhsSystem = isSystemApp(rhs); + + if (lhsSystem && !rhsSystem) { + return -1; + } else if (rhsSystem && !lhsSystem) { + return 1; + } else { + // Either both system engines, or both non system + // engines. + // + // Note, this isn't a typo. Higher priority numbers imply + // higher priority, but are "lower" in the sort order. + return rhs.priority - lhs.priority; + } + } + }; + + /** + * @return true iff. the resolve info corresponds to a system application. + */ + private static final boolean isSystemApp(ResolveInfo res) { + return (res.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + /** + * Returns the highest ranked search provider as per the + * ranking defined in {@link #getGlobalSearchActivities()}. + */ + private ComponentName getDefaultGlobalSearchProvider(List<ResolveInfo> providerList) { + if (providerList != null && !providerList.isEmpty()) { + ActivityInfo ai = providerList.get(0).activityInfo; + return new ComponentName(ai.packageName, ai.name); + } + + Log.w(LOG_TAG, "No global search activity found"); + return null; + } + + private String getGlobalSearchProviderSetting() { + return Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY); + } + + /** + * Finds the web search activity. + * + * Only looks in the package of the global search activity. + */ + private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) { + if (globalSearchActivity == null) { + return null; + } + Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); + intent.setPackage(globalSearchActivity.getPackageName()); + List<ResolveInfo> activities = + queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + + if (activities != null && !activities.isEmpty()) { + ActivityInfo ai = activities.get(0).activityInfo; + // TODO: do some sanity checks here? + return new ComponentName(ai.packageName, ai.name); + } + Log.w(LOG_TAG, "No web search activity found"); + return null; + } + + private List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { + List<ResolveInfo> activities = null; + try { + activities = + mPm.queryIntentActivities(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags, mUserId); + } catch (RemoteException re) { + // Local call + } + return activities; + } + + /** + * Returns the list of searchable activities. + */ + public synchronized ArrayList<SearchableInfo> getSearchablesList() { + ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList); + return result; + } + + /** + * Returns a list of the searchable activities that can be included in global search. + */ + public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() { + return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList); + } + + /** + * Returns a list of activities that handle the global search intent. + */ + public synchronized ArrayList<ResolveInfo> getGlobalSearchActivities() { + return new ArrayList<ResolveInfo>(mGlobalSearchActivities); + } + + /** + * Gets the name of the global search activity. + */ + public synchronized ComponentName getGlobalSearchActivity() { + return mCurrentGlobalSearchActivity; + } + + /** + * Gets the name of the web search activity. + */ + public synchronized ComponentName getWebSearchActivity() { + return mWebSearchActivity; + } + + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Searchable authorities:"); + synchronized (this) { + if (mSearchablesList != null) { + for (SearchableInfo info: mSearchablesList) { + pw.print(" "); pw.println(info.getSuggestAuthority()); + } + } + } + } +} diff --git a/services/java/com/android/server/wm/AppTransition.java b/services/java/com/android/server/wm/AppTransition.java new file mode 100644 index 0000000..92fd68b --- /dev/null +++ b/services/java/com/android/server/wm/AppTransition.java @@ -0,0 +1,629 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.app.ActivityOptions; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.os.Debug; +import android.os.Handler; +import android.os.IRemoteCallback; +import android.util.Slog; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.ScaleAnimation; + +import com.android.internal.util.DumpUtils.Dump; +import com.android.server.AttributeCache; +import com.android.server.wm.WindowManagerService.H; + +import java.io.PrintWriter; + +import static android.view.WindowManagerPolicy.TRANSIT_NONE; +import static android.view.WindowManagerPolicy.TRANSIT_UNSET; + +// State management of app transitions. When we are preparing for a +// transition, mNextAppTransition will be the kind of transition to +// perform or TRANSIT_NONE if we are not waiting. If we are waiting, +// mOpeningApps and mClosingApps are the lists of tokens that will be +// made visible or hidden at the next transition. +public class AppTransition implements Dump { + private static final String TAG = "AppTransition"; + private static final float THUMBNAIL_ANIMATION_DECELERATE_FACTOR = 1.5f; + private static final boolean DEBUG_APP_TRANSITIONS = WindowManagerService.DEBUG_APP_TRANSITIONS; + private static final boolean DEBUG_ANIM = WindowManagerService.DEBUG_APP_TRANSITIONS; + + final Context mContext; + final Handler mH; + + int mNextAppTransition = TRANSIT_UNSET; + int mNextAppTransitionType = ActivityOptions.ANIM_NONE; + String mNextAppTransitionPackage; + Bitmap mNextAppTransitionThumbnail; + // Used for thumbnail transitions. True if we're scaling up, false if scaling down + boolean mNextAppTransitionScaleUp; + IRemoteCallback mNextAppTransitionCallback; + int mNextAppTransitionEnter; + int mNextAppTransitionExit; + int mNextAppTransitionStartX; + int mNextAppTransitionStartY; + int mNextAppTransitionStartWidth; + int mNextAppTransitionStartHeight; + boolean mAppTransitionReady = false; + boolean mAppTransitionRunning = false; + boolean mAppTransitionTimeout = false; + + final int mConfigShortAnimTime; + final Interpolator mInterpolator; + + AppTransition(Context context, Handler h) { + mContext = context; + mH = h; + mConfigShortAnimTime = context.getResources().getInteger( + com.android.internal.R.integer.config_shortAnimTime); + mInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.decelerate_quad); + } + + boolean isTransitionSet() { + return mNextAppTransition != TRANSIT_UNSET; + } + + boolean isTransitionNone() { + return mNextAppTransition == TRANSIT_NONE; + } + + boolean isTransitionEqual(int transit) { + return mNextAppTransition == transit; + } + + int getAppTransition() { + return mNextAppTransition; + } + + void setAppTransition(int transit) { + mNextAppTransition = transit; + } + + boolean isReady() { + return mAppTransitionReady; + } + + void setReady() { + mAppTransitionReady = true; + } + + boolean isRunning() { + return mAppTransitionRunning; + } + + void setRunning(boolean running) { + mAppTransitionRunning = running; + } + + boolean isTimeout() { + return mAppTransitionTimeout; + } + + void setTimeout(boolean timeout) { + mAppTransitionTimeout = timeout; + } + + Bitmap getNextAppTransitionThumbnail() { + return mNextAppTransitionThumbnail; + } + + void getStartingPoint(Point outPoint) { + outPoint.x = mNextAppTransitionStartX; + outPoint.y = mNextAppTransitionStartY; + } + + int getType() { + return mNextAppTransitionType; + } + + void prepare() { + mAppTransitionReady = false; + mAppTransitionTimeout = false; + } + + void goodToGo() { + mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; + mAppTransitionReady = false; + mAppTransitionRunning = true; + mAppTransitionTimeout = false; + } + + void clear() { + mNextAppTransitionType = ActivityOptions.ANIM_NONE; + mNextAppTransitionPackage = null; + mNextAppTransitionThumbnail = null; + } + + private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) { + if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" + + (lp != null ? lp.packageName : null) + + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null)); + if (lp != null && lp.windowAnimations != 0) { + // If this is a system resource, don't try to load it from the + // application resources. It is nice to avoid loading application + // resources if we can. + String packageName = lp.packageName != null ? lp.packageName : "android"; + int resId = lp.windowAnimations; + if ((resId&0xFF000000) == 0x01000000) { + packageName = "android"; + } + if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package=" + + packageName); + return AttributeCache.instance().get(packageName, resId, + com.android.internal.R.styleable.WindowAnimation); + } + return null; + } + + private AttributeCache.Entry getCachedAnimations(String packageName, int resId) { + if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package=" + + packageName + " resId=0x" + Integer.toHexString(resId)); + if (packageName != null) { + if ((resId&0xFF000000) == 0x01000000) { + packageName = "android"; + } + if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package=" + + packageName); + return AttributeCache.instance().get(packageName, resId, + com.android.internal.R.styleable.WindowAnimation); + } + return null; + } + + Animation loadAnimation(WindowManager.LayoutParams lp, int animAttr) { + int anim = 0; + Context context = mContext; + if (animAttr >= 0) { + AttributeCache.Entry ent = getCachedAnimations(lp); + if (ent != null) { + context = ent.context; + anim = ent.array.getResourceId(animAttr, 0); + } + } + if (anim != 0) { + return AnimationUtils.loadAnimation(context, anim); + } + return null; + } + + private Animation loadAnimation(String packageName, int resId) { + int anim = 0; + Context context = mContext; + if (resId >= 0) { + AttributeCache.Entry ent = getCachedAnimations(packageName, resId); + if (ent != null) { + context = ent.context; + anim = resId; + } + } + if (anim != 0) { + return AnimationUtils.loadAnimation(context, anim); + } + return null; + } + + private Animation createExitAnimationLocked(int transit, int duration) { + if (transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN || + transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE) { + // If we are on top of the wallpaper, we need an animation that + // correctly handles the wallpaper staying static behind all of + // the animated elements. To do this, will just have the existing + // element fade out. + Animation a = new AlphaAnimation(1, 0); + a.setDetachWallpaper(true); + a.setDuration(duration); + return a; + } + // For normal animations, the exiting element just holds in place. + Animation a = new AlphaAnimation(1, 1); + a.setDuration(duration); + return a; + } + + /** + * Compute the pivot point for an animation that is scaling from a small + * rect on screen to a larger rect. The pivot point varies depending on + * the distance between the inner and outer edges on both sides. This + * function computes the pivot point for one dimension. + * @param startPos Offset from left/top edge of outer rectangle to + * left/top edge of inner rectangle. + * @param finalScale The scaling factor between the size of the outer + * and inner rectangles. + */ + private static float computePivot(int startPos, float finalScale) { + final float denom = finalScale-1; + if (Math.abs(denom) < .0001f) { + return startPos; + } + return -startPos / denom; + } + + private Animation createScaleUpAnimationLocked(int transit, boolean enter, + int appWidth, int appHeight) { + Animation a = null; + // Pick the desired duration. If this is an inter-activity transition, + // it is the standard duration for that. Otherwise we use the longer + // task transition duration. + int duration; + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + duration = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shortAnimTime); + break; + default: + duration = 300; + break; + } + if (enter) { + // Entering app zooms out from the center of the initial rect. + float scaleW = mNextAppTransitionStartWidth / (float) appWidth; + float scaleH = mNextAppTransitionStartHeight / (float) appHeight; + Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mNextAppTransitionStartX, scaleW), + computePivot(mNextAppTransitionStartY, scaleH)); + scale.setDuration(duration); + AnimationSet set = new AnimationSet(true); + Animation alpha = new AlphaAnimation(0, 1); + scale.setDuration(duration); + set.addAnimation(scale); + alpha.setDuration(duration); + set.addAnimation(alpha); + set.setDetachWallpaper(true); + a = set; + } else { + a = createExitAnimationLocked(transit, duration); + } + a.setFillAfter(true); + final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext, + com.android.internal.R.interpolator.decelerate_cubic); + a.setInterpolator(interpolator); + a.initialize(appWidth, appHeight, appWidth, appHeight); + return a; + } + + Animation createThumbnailAnimationLocked(int transit, boolean enter, boolean thumb, + int appWidth, int appHeight) { + Animation a; + final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); + final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; + final int thumbHeightI = mNextAppTransitionThumbnail.getHeight(); + final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; + // Pick the desired duration. If this is an inter-activity transition, + // it is the standard duration for that. Otherwise we use the longer + // task transition duration. + int duration; + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + duration = mConfigShortAnimTime; + break; + default: + duration = 250; + break; + } + if (thumb) { + // Animation for zooming thumbnail from its initial size to + // filling the screen. + if (mNextAppTransitionScaleUp) { + float scaleW = appWidth / thumbWidth; + float scaleH = appHeight / thumbHeight; + + Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, + computePivot(mNextAppTransitionStartX, 1 / scaleW), + computePivot(mNextAppTransitionStartY, 1 / scaleH)); + AnimationSet set = new AnimationSet(true); + Animation alpha = new AlphaAnimation(1, 0); + scale.setDuration(duration); + scale.setInterpolator( + new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + set.addAnimation(scale); + alpha.setDuration(duration); + set.addAnimation(alpha); + set.setFillBefore(true); + a = set; + } else { + float scaleW = appWidth / thumbWidth; + float scaleH = appHeight / thumbHeight; + + Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mNextAppTransitionStartX, 1 / scaleW), + computePivot(mNextAppTransitionStartY, 1 / scaleH)); + AnimationSet set = new AnimationSet(true); + Animation alpha = new AlphaAnimation(1, 1); + scale.setDuration(duration); + scale.setInterpolator( + new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + set.addAnimation(scale); + alpha.setDuration(duration); + set.addAnimation(alpha); + set.setFillBefore(true); + + a = set; + } + } else if (enter) { + // Entering app zooms out from the center of the thumbnail. + if (mNextAppTransitionScaleUp) { + float scaleW = thumbWidth / appWidth; + float scaleH = thumbHeight / appHeight; + Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mNextAppTransitionStartX, scaleW), + computePivot(mNextAppTransitionStartY, scaleH)); + scale.setDuration(duration); + scale.setInterpolator( + new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + scale.setFillBefore(true); + a = scale; + } else { + // noop animation + a = new AlphaAnimation(1, 1); + a.setDuration(duration); + } + } else { + // Exiting app + if (mNextAppTransitionScaleUp) { + if (transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN) { + // Fade out while bringing up selected activity. This keeps the + // current activity from showing through a launching wallpaper + // activity. + a = new AlphaAnimation(1, 0); + } else { + // noop animation + a = new AlphaAnimation(1, 1); + } + a.setDuration(duration); + } else { + float scaleW = thumbWidth / appWidth; + float scaleH = thumbHeight / appHeight; + Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, + computePivot(mNextAppTransitionStartX, scaleW), + computePivot(mNextAppTransitionStartY, scaleH)); + scale.setDuration(duration); + scale.setInterpolator( + new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + scale.setFillBefore(true); + AnimationSet set = new AnimationSet(true); + Animation alpha = new AlphaAnimation(1, 0); + set.addAnimation(scale); + alpha.setDuration(duration); + alpha.setInterpolator(new DecelerateInterpolator( + THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + set.addAnimation(alpha); + set.setFillBefore(true); + set.setZAdjustment(Animation.ZORDER_TOP); + a = set; + } + } + a.setFillAfter(true); + a.setInterpolator(mInterpolator); + a.initialize(appWidth, appHeight, appWidth, appHeight); + return a; + } + + + Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, + int appWidth, int appHeight) { + Animation a; + if (mNextAppTransitionType == ActivityOptions.ANIM_CUSTOM) { + a = loadAnimation(mNextAppTransitionPackage, enter ? + mNextAppTransitionEnter : mNextAppTransitionExit); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "applyAnimation:" + + " anim=" + a + " nextAppTransition=ANIM_CUSTOM" + + " transit=" + transit + " isEntrance=" + enter + + " Callers=" + Debug.getCallers(3)); + } else if (mNextAppTransitionType == ActivityOptions.ANIM_SCALE_UP) { + a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "applyAnimation:" + + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP" + + " transit=" + transit + " isEntrance=" + enter + + " Callers=" + Debug.getCallers(3)); + } else if (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP || + mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN) { + mNextAppTransitionScaleUp = + (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP); + a = createThumbnailAnimationLocked(transit, enter, false, appWidth, appHeight); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { + String animName = mNextAppTransitionScaleUp ? + "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN"; + Slog.v(TAG, "applyAnimation:" + + " anim=" + a + " nextAppTransition=" + animName + + " transit=" + transit + " isEntrance=" + enter + + " Callers=" + Debug.getCallers(3)); + } + } else { + int animAttr = 0; + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_TASK_OPEN: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_TASK_CLOSE: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation; + break; + } + a = animAttr != 0 ? loadAnimation(lp, animAttr) : null; + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "applyAnimation:" + + " anim=" + a + + " animAttr=0x" + Integer.toHexString(animAttr) + + " transit=" + transit + " isEntrance=" + enter + + " Callers=" + Debug.getCallers(3)); + } + return a; + } + + void postAnimationCallback() { + if (mNextAppTransitionCallback != null) { + mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback)); + mNextAppTransitionCallback = null; + } + } + + void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, + IRemoteCallback startedCallback) { + if (isTransitionSet()) { + mNextAppTransitionType = ActivityOptions.ANIM_CUSTOM; + mNextAppTransitionPackage = packageName; + mNextAppTransitionThumbnail = null; + mNextAppTransitionEnter = enterAnim; + mNextAppTransitionExit = exitAnim; + postAnimationCallback(); + mNextAppTransitionCallback = startedCallback; + } else { + postAnimationCallback(); + } + } + + void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth, + int startHeight) { + if (isTransitionSet()) { + mNextAppTransitionType = ActivityOptions.ANIM_SCALE_UP; + mNextAppTransitionPackage = null; + mNextAppTransitionThumbnail = null; + mNextAppTransitionStartX = startX; + mNextAppTransitionStartY = startY; + mNextAppTransitionStartWidth = startWidth; + mNextAppTransitionStartHeight = startHeight; + postAnimationCallback(); + mNextAppTransitionCallback = null; + } + } + + void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY, + IRemoteCallback startedCallback, boolean scaleUp) { + if (isTransitionSet()) { + mNextAppTransitionType = scaleUp ? ActivityOptions.ANIM_THUMBNAIL_SCALE_UP + : ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; + mNextAppTransitionPackage = null; + mNextAppTransitionThumbnail = srcThumb; + mNextAppTransitionScaleUp = scaleUp; + mNextAppTransitionStartX = startX; + mNextAppTransitionStartY = startY; + postAnimationCallback(); + mNextAppTransitionCallback = startedCallback; + } else { + postAnimationCallback(); + } + } + + @Override + public String toString() { + return "mNextAppTransition=0x" + Integer.toHexString(mNextAppTransition); + } + + @Override + public void dump(PrintWriter pw) { + pw.print(" " + this); + pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady); + pw.print(" mAppTransitionRunning="); pw.print(mAppTransitionRunning); + pw.print(" mAppTransitionTimeout="); pw.println(mAppTransitionTimeout); + if (mNextAppTransitionType != ActivityOptions.ANIM_NONE) { + pw.print(" mNextAppTransitionType="); pw.println(mNextAppTransitionType); + } + switch (mNextAppTransitionType) { + case ActivityOptions.ANIM_CUSTOM: + pw.print(" mNextAppTransitionPackage="); + pw.println(mNextAppTransitionPackage); + pw.print(" mNextAppTransitionEnter=0x"); + pw.print(Integer.toHexString(mNextAppTransitionEnter)); + pw.print(" mNextAppTransitionExit=0x"); + pw.println(Integer.toHexString(mNextAppTransitionExit)); + break; + case ActivityOptions.ANIM_SCALE_UP: + pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX); + pw.print(" mNextAppTransitionStartY="); + pw.println(mNextAppTransitionStartY); + pw.print(" mNextAppTransitionStartWidth="); + pw.print(mNextAppTransitionStartWidth); + pw.print(" mNextAppTransitionStartHeight="); + pw.println(mNextAppTransitionStartHeight); + break; + case ActivityOptions.ANIM_THUMBNAIL_SCALE_UP: + case ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN: + pw.print(" mNextAppTransitionThumbnail="); + pw.print(mNextAppTransitionThumbnail); + pw.print(" mNextAppTransitionStartX="); + pw.print(mNextAppTransitionStartX); + pw.print(" mNextAppTransitionStartY="); + pw.println(mNextAppTransitionStartY); + pw.print(" mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp); + break; + } + if (mNextAppTransitionCallback != null) { + pw.print(" mNextAppTransitionCallback="); + pw.println(mNextAppTransitionCallback); + } + } +} diff --git a/services/java/com/android/server/wm/AppWindowAnimator.java b/services/java/com/android/server/wm/AppWindowAnimator.java index ca94d04..34513a1 100644 --- a/services/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/java/com/android/server/wm/AppWindowAnimator.java @@ -22,7 +22,6 @@ public class AppWindowAnimator { boolean animating; Animation animation; - boolean animInitialized; boolean hasTransformation; final Transformation transformation = new Transformation(); @@ -58,12 +57,15 @@ public class AppWindowAnimator { mAnimator = atoken.mAnimator; } - public void setAnimation(Animation anim, boolean initialized) { + public void setAnimation(Animation anim, int width, int height) { if (WindowManagerService.localLOGV) Slog.v( - TAG, "Setting animation in " + mAppToken + ": " + anim); + TAG, "Setting animation in " + mAppToken + ": " + anim + + " wxh=" + width + "x" + height); animation = anim; animating = false; - animInitialized = initialized; + if (!anim.isInitialized()) { + anim.initialize(width, height, width, height); + } anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); anim.scaleCurrentDuration(mService.mTransitionAnimationScale); int zorder = anim.getZAdjustment(); @@ -87,7 +89,6 @@ public class AppWindowAnimator { public void setDummyAnimation() { if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken); animation = sDummyAnimation; - animInitialized = false; hasTransformation = true; transformation.clear(); transformation.setAlpha(mAppToken.reportedVisible ? 1 : 0); @@ -97,9 +98,12 @@ public class AppWindowAnimator { if (animation != null) { animation = null; animating = true; - animInitialized = false; } clearThumbnail(); + if (mAppToken.deferClearAllDrawn) { + mAppToken.allDrawn = false; + mAppToken.deferClearAllDrawn = false; + } } public void clearThumbnail() { @@ -125,7 +129,7 @@ public class AppWindowAnimator { if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) { mService.setInputMethodAnimLayerAdjustment(adj); } - if (w == mAnimator.mWallpaperTarget && mAnimator.mLowerWallpaperTarget == null) { + if (w == mService.mWallpaperTarget && mService.mLowerWallpaperTarget == null) { mService.setWallpaperAnimLayerAdjustmentLocked(adj); } } @@ -185,7 +189,7 @@ public class AppWindowAnimator { } // This must be called while inside a transaction. - boolean stepAnimationLocked(long currentTime, int dw, int dh) { + boolean stepAnimationLocked(long currentTime) { if (mService.okToDisplay()) { // We will run animations as long as the display isn't frozen. @@ -202,12 +206,8 @@ public class AppWindowAnimator { if (!animating) { if (WindowManagerService.DEBUG_ANIM) Slog.v( TAG, "Starting animation in " + mAppToken + - " @ " + currentTime + ": dw=" + dw + " dh=" + dh - + " scale=" + mService.mTransitionAnimationScale + " @ " + currentTime + " scale=" + mService.mTransitionAnimationScale + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating); - if (!animInitialized) { - animation.initialize(dw, dh, dw, dh); - } animation.setStartTime(currentTime); animating = true; if (thumbnail != null) { @@ -286,8 +286,7 @@ public class AppWindowAnimator { pw.print(" allDrawn="); pw.print(allDrawn); pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment); if (animating || animation != null) { - pw.print(prefix); pw.print("animating="); pw.print(animating); - pw.print(" animInitialized="); pw.println(animInitialized); + pw.print(prefix); pw.print("animating="); pw.println(animating); pw.print(prefix); pw.print("animation="); pw.println(animation); } if (hasTransformation) { diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index 2802ad7..275c9bf 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -30,21 +30,18 @@ import android.view.View; import android.view.WindowManager; import java.io.PrintWriter; -import java.util.ArrayList; /** * Version of WindowToken that is specifically for a particular application (or * really activity) that is displaying windows. */ class AppWindowToken extends WindowToken { - // The user who owns this app window token. - final int userId; // Non-null only for application tokens. final IApplicationToken appToken; // All of the windows and child windows that are included in this // application token. Note this list is NOT sorted! - final ArrayList<WindowState> allAppWindows = new ArrayList<WindowState>(); + final WindowList allAppWindows = new WindowList(); final AppWindowAnimator mAppAnimator; final WindowAnimator mAnimator; @@ -66,6 +63,9 @@ class AppWindowToken extends WindowToken { int numDrawnWindows; boolean inPendingTransaction; boolean allDrawn; + // Set to true when this app creates a surface while in the middle of an animation. In that + // case do not clear allDrawn until the animation completes. + boolean deferClearAllDrawn; // Is this token going to be hidden in a little while? If so, it // won't be taken into account for setting the screen orientation. @@ -100,10 +100,9 @@ class AppWindowToken extends WindowToken { // Input application handle used by the input dispatcher. final InputApplicationHandle mInputApplicationHandle; - AppWindowToken(WindowManagerService _service, int _userId, IApplicationToken _token) { + AppWindowToken(WindowManagerService _service, IApplicationToken _token) { super(_service, _token.asBinder(), WindowManager.LayoutParams.TYPE_APPLICATION, true); - userId = _userId; appWindowToken = this; appToken = _token; mInputApplicationHandle = new InputApplicationHandle(this); @@ -228,8 +227,7 @@ class AppWindowToken extends WindowToken { void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); if (appToken != null) { - pw.print(prefix); pw.print("app=true"); - pw.print(" userId="); pw.println(userId); + pw.print(prefix); pw.println("app=true"); } if (allAppWindows.size() > 0) { pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows); diff --git a/services/java/com/android/server/wm/DimSurface.java b/services/java/com/android/server/wm/DimSurface.java index 4225868..511d388 100644 --- a/services/java/com/android/server/wm/DimSurface.java +++ b/services/java/com/android/server/wm/DimSurface.java @@ -18,6 +18,7 @@ package com.android.server.wm; import android.graphics.PixelFormat; import android.util.Slog; +import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceSession; @@ -31,8 +32,11 @@ class DimSurface { int mDimColor = 0; int mLayer = -1; int mLastDimWidth, mLastDimHeight; + final DisplayContent mDisplayContent; - DimSurface(SurfaceSession session, final int layerStack) { + DimSurface(SurfaceSession session, DisplayContent displayContent) { + mDisplayContent = displayContent; + final int layerStack = displayContent.getDisplayId(); try { if (WindowManagerService.DEBUG_SURFACE_TRACE) { mDimSurface = new WindowStateAnimator.SurfaceTrace(session, @@ -58,7 +62,10 @@ class DimSurface { /** * Show the dim surface. */ - void show(int dw, int dh, int layer, int color) { + void show(int layer, int color) { + final DisplayInfo info = mDisplayContent.getDisplayInfo(); + final int dw = info.logicalWidth; + final int dh = info.logicalHeight; if (mDimSurface == null) { Slog.e(TAG, "show: no Surface"); return; diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java index 68cdbfc..59e4b0e 100644 --- a/services/java/com/android/server/wm/DisplayContent.java +++ b/services/java/com/android/server/wm/DisplayContent.java @@ -16,10 +16,8 @@ package com.android.server.wm; -import android.os.RemoteCallbackList; import android.view.Display; import android.view.DisplayInfo; -import android.view.IDisplayContentChangeListener; import java.io.PrintWriter; import java.util.ArrayList; @@ -43,12 +41,6 @@ class DisplayContent { * from mDisplayWindows; */ private WindowList mWindows = new WindowList(); - // Specification for magnifying the display content. - MagnificationSpec mMagnificationSpec; - - // Callback for observing content changes on a display. - RemoteCallbackList<IDisplayContentChangeListener> mDisplayContentChangeListeners; - // This protects the following display size properties, so that // getDisplaySize() doesn't need to acquire the global lock. This is // needed because the window manager sometimes needs to use ActivityThread @@ -128,9 +120,6 @@ class DisplayContent { pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth); pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight); pw.print(subPrefix); pw.print("layoutNeeded="); pw.print(layoutNeeded); - if (mMagnificationSpec != null) { - pw.print(" mMagnificationSpec="); pw.print(mMagnificationSpec); - } pw.println(); } } diff --git a/services/java/com/android/server/wm/DisplayMagnifier.java b/services/java/com/android/server/wm/DisplayMagnifier.java new file mode 100644 index 0000000..cd5ae4b --- /dev/null +++ b/services/java/com/android/server/wm/DisplayMagnifier.java @@ -0,0 +1,732 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.app.Service; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Pools.SimplePool; +import android.util.Slog; +import android.util.SparseArray; +import android.util.TypedValue; +import android.view.IMagnificationCallbacks; +import android.view.MagnificationSpec; +import android.view.Surface; +import android.view.Surface.OutOfResourcesException; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +import com.android.internal.R; +import com.android.internal.os.SomeArgs; +import com.android.internal.policy.impl.PhoneWindowManager; + +/** + * This class is a part of the window manager and encapsulates the + * functionality related to display magnification. + */ +final class DisplayMagnifier { + private static final String LOG_TAG = DisplayMagnifier.class.getSimpleName(); + + private static final boolean DEBUG_WINDOW_TRANSITIONS = false; + private static final boolean DEBUG_ROTATION = false; + private static final boolean DEBUG_LAYERS = false; + private static final boolean DEBUG_RECTANGLE_REQUESTED = false; + private static final boolean DEBUG_VIEWPORT_WINDOW = false; + + private final Rect mTempRect1 = new Rect(); + private final Rect mTempRect2 = new Rect(); + + private final Region mTempRegion1 = new Region(); + private final Region mTempRegion2 = new Region(); + private final Region mTempRegion3 = new Region(); + private final Region mTempRegion4 = new Region(); + + private final Context mContext; + private final WindowManagerService mWindowManagerService; + private final MagnifiedViewport mMagnifedViewport; + private final Handler mHandler; + + private final IMagnificationCallbacks mCallbacks; + + private final long mLongAnimationDuration; + + public DisplayMagnifier(WindowManagerService windowManagerService, + IMagnificationCallbacks callbacks) { + mContext = windowManagerService.mContext; + mWindowManagerService = windowManagerService; + mCallbacks = callbacks; + mMagnifedViewport = new MagnifiedViewport(); + mHandler = new MyHandler(mWindowManagerService.mH.getLooper()); + mLongAnimationDuration = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longAnimTime); + } + + public void setMagnificationSpecLocked(MagnificationSpec spec) { + mMagnifedViewport.updateMagnificationSpecLocked(spec); + mMagnifedViewport.recomputeBoundsLocked(); + mWindowManagerService.scheduleAnimationLocked(); + } + + public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) { + if (DEBUG_RECTANGLE_REQUESTED) { + Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle); + } + if (!mMagnifedViewport.isMagnifyingLocked()) { + return; + } + Rect magnifiedRegionBounds = mTempRect2; + mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds); + if (magnifiedRegionBounds.contains(rectangle)) { + return; + } + SomeArgs args = SomeArgs.obtain(); + args.argi1 = rectangle.left; + args.argi2 = rectangle.top; + args.argi3 = rectangle.right; + args.argi4 = rectangle.bottom; + mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED, + args).sendToTarget(); + } + + public void onWindowLayersChangedLocked() { + if (DEBUG_LAYERS) { + Slog.i(LOG_TAG, "Layers changed."); + } + mMagnifedViewport.recomputeBoundsLocked(); + mWindowManagerService.scheduleAnimationLocked(); + } + + public void onRotationChangedLocked(DisplayContent displayContent, int rotation) { + if (DEBUG_ROTATION) { + Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation) + + " displayId: " + displayContent.getDisplayId()); + } + mMagnifedViewport.onRotationChangedLocked(); + mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED); + } + + public void onWindowTransitionLocked(WindowState windowState, int transition) { + if (DEBUG_WINDOW_TRANSITIONS) { + Slog.i(LOG_TAG, "Window transition: " + + PhoneWindowManager.windowTransitionToString(transition) + + " displayId: " + windowState.getDisplayId()); + } + final boolean magnifying = mMagnifedViewport.isMagnifyingLocked(); + if (magnifying) { + switch (transition) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: + case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: + case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: + case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: { + mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED); + } + } + } + final int type = windowState.mAttrs.type; + switch (transition) { + case WindowManagerPolicy.TRANSIT_ENTER: + case WindowManagerPolicy.TRANSIT_SHOW: { + if (!magnifying) { + break; + } + switch (type) { + case WindowManager.LayoutParams.TYPE_APPLICATION: + case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: + case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: + case WindowManager.LayoutParams.TYPE_SEARCH_BAR: + case WindowManager.LayoutParams.TYPE_PHONE: + case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: + case WindowManager.LayoutParams.TYPE_TOAST: + case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: + case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: + case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: + case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: + case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: + case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: + case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: + case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: { + Rect magnifiedRegionBounds = mTempRect2; + mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked( + magnifiedRegionBounds); + Rect touchableRegionBounds = mTempRect1; + windowState.getTouchableRegion(mTempRegion1); + mTempRegion1.getBounds(touchableRegionBounds); + if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) { + try { + mCallbacks.onRectangleOnScreenRequested( + touchableRegionBounds.left, + touchableRegionBounds.top, + touchableRegionBounds.right, + touchableRegionBounds.bottom); + } catch (RemoteException re) { + /* ignore */ + } + } + } break; + } break; + } + } + } + + public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) { + MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked(); + if (spec != null && !spec.isNop()) { + WindowManagerPolicy policy = mWindowManagerService.mPolicy; + final int windowType = windowState.mAttrs.type; + if (!policy.isTopLevelWindow(windowType) && windowState.mAttachedWindow != null + && !policy.canMagnifyWindow(windowType)) { + return null; + } + if (!policy.canMagnifyWindow(windowState.mAttrs.type)) { + return null; + } + } + return spec; + } + + /** NOTE: This has to be called within a surface transaction. */ + public void drawMagnifiedRegionBorderIfNeededLocked() { + mMagnifedViewport.drawWindowIfNeededLocked(); + } + + private final class MagnifiedViewport { + + private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5; + + private final SparseArray<WindowStateInfo> mTempWindowStateInfos = + new SparseArray<WindowStateInfo>(); + + private final float[] mTempFloats = new float[9]; + + private final RectF mTempRectF = new RectF(); + + private final Point mTempPoint = new Point(); + + private final Matrix mTempMatrix = new Matrix(); + + private final Region mMagnifiedBounds = new Region(); + private final Region mOldMagnifiedBounds = new Region(); + + private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain(); + + private final WindowManager mWindowManager; + + private final int mBorderWidth; + private final int mHalfBorderWidth; + + private ViewportWindow mWindow; + + private boolean mFullRedrawNeeded; + + public MagnifiedViewport() { + mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE); + mBorderWidth = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP, + mContext.getResources().getDisplayMetrics()); + mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2; + mWindow = new ViewportWindow(mContext); + } + + public void updateMagnificationSpecLocked(MagnificationSpec spec) { + if (spec != null) { + mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY); + } else { + mMagnificationSpec.clear(); + } + // If this message is pending we are in a rotation animation and do not want + // to show the border. We will do so when the pending message is handled. + if (!mHandler.hasMessages(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) { + setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true); + } + } + + public void recomputeBoundsLocked() { + mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + final int screenWidth = mTempPoint.x; + final int screenHeight = mTempPoint.y; + + Region magnifiedBounds = mMagnifiedBounds; + magnifiedBounds.set(0, 0, 0, 0); + + Region availableBounds = mTempRegion1; + availableBounds.set(0, 0, screenWidth, screenHeight); + + Region nonMagnifiedBounds = mTempRegion4; + nonMagnifiedBounds.set(0, 0, 0, 0); + + SparseArray<WindowStateInfo> visibleWindows = mTempWindowStateInfos; + visibleWindows.clear(); + getWindowsOnScreenLocked(visibleWindows); + + final int visibleWindowCount = visibleWindows.size(); + for (int i = visibleWindowCount - 1; i >= 0; i--) { + WindowStateInfo info = visibleWindows.valueAt(i); + if (info.mWindowState.mAttrs.type == WindowManager + .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) { + continue; + } + + Region windowBounds = mTempRegion2; + Matrix matrix = mTempMatrix; + populateTransformationMatrix(info.mWindowState, matrix); + RectF windowFrame = mTempRectF; + + if (mWindowManagerService.mPolicy.canMagnifyWindow(info.mWindowState.mAttrs.type)) { + windowFrame.set(info.mWindowState.mFrame); + windowFrame.offset(-windowFrame.left, -windowFrame.top); + matrix.mapRect(windowFrame); + windowBounds.set((int) windowFrame.left, (int) windowFrame.top, + (int) windowFrame.right, (int) windowFrame.bottom); + magnifiedBounds.op(windowBounds, Region.Op.UNION); + magnifiedBounds.op(availableBounds, Region.Op.INTERSECT); + } else { + windowFrame.set(info.mTouchableRegion); + windowFrame.offset(-info.mWindowState.mFrame.left, + -info.mWindowState.mFrame.top); + matrix.mapRect(windowFrame); + windowBounds.set((int) windowFrame.left, (int) windowFrame.top, + (int) windowFrame.right, (int) windowFrame.bottom); + nonMagnifiedBounds.op(windowBounds, Region.Op.UNION); + windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE); + availableBounds.op(windowBounds, Region.Op.DIFFERENCE); + } + + Region accountedBounds = mTempRegion2; + accountedBounds.set(magnifiedBounds); + accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION); + accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT); + + if (accountedBounds.isRect()) { + Rect accountedFrame = mTempRect1; + accountedBounds.getBounds(accountedFrame); + if (accountedFrame.width() == screenWidth + && accountedFrame.height() == screenHeight) { + break; + } + } + } + + for (int i = visibleWindowCount - 1; i >= 0; i--) { + WindowStateInfo info = visibleWindows.valueAt(i); + info.recycle(); + visibleWindows.removeAt(i); + } + + magnifiedBounds.op(mHalfBorderWidth, mHalfBorderWidth, + screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth, + Region.Op.INTERSECT); + + if (!mOldMagnifiedBounds.equals(magnifiedBounds)) { + Region bounds = Region.obtain(); + bounds.set(magnifiedBounds); + mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, + bounds).sendToTarget(); + + mWindow.setBounds(magnifiedBounds); + Rect dirtyRect = mTempRect1; + if (mFullRedrawNeeded) { + mFullRedrawNeeded = false; + dirtyRect.set(mHalfBorderWidth, mHalfBorderWidth, + screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth); + mWindow.invalidate(dirtyRect); + } else { + Region dirtyRegion = mTempRegion3; + dirtyRegion.set(magnifiedBounds); + dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION); + dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT); + dirtyRegion.getBounds(dirtyRect); + mWindow.invalidate(dirtyRect); + } + + mOldMagnifiedBounds.set(magnifiedBounds); + } + } + + private void populateTransformationMatrix(WindowState windowState, Matrix outMatrix) { + mTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx; + mTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx; + mTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy; + mTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy; + mTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left; + mTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top; + mTempFloats[Matrix.MPERSP_0] = 0; + mTempFloats[Matrix.MPERSP_1] = 0; + mTempFloats[Matrix.MPERSP_2] = 1; + outMatrix.setValues(mTempFloats); + } + + private void getWindowsOnScreenLocked(SparseArray<WindowStateInfo> outWindowStates) { + DisplayContent displayContent = mWindowManagerService.getDefaultDisplayContentLocked(); + WindowList windowList = displayContent.getWindowList(); + final int windowCount = windowList.size(); + for (int i = 0; i < windowCount; i++) { + WindowState windowState = windowList.get(i); + if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager + .LayoutParams.TYPE_UNIVERSE_BACKGROUND) + && !windowState.mWinAnimator.mEnterAnimationPending) { + outWindowStates.put(windowState.mLayer, WindowStateInfo.obtain(windowState)); + } + } + } + + public void onRotationChangedLocked() { + // If we are magnifying, hide the magnified border window immediately so + // the user does not see strange artifacts during rotation. The screenshot + // used for rotation has already the border. After the rotation is complete + // we will show the border. + if (isMagnifyingLocked()) { + setMagnifiedRegionBorderShownLocked(false, false); + final long delay = (long) (mLongAnimationDuration + * mWindowManagerService.mWindowAnimationScale); + Message message = mHandler.obtainMessage( + MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED); + mHandler.sendMessageDelayed(message, delay); + } + recomputeBoundsLocked(); + mWindow.updateSize(); + } + + public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) { + if (shown) { + mFullRedrawNeeded = true; + mOldMagnifiedBounds.set(0, 0, 0, 0); + } + mWindow.setShown(shown, animate); + } + + public void getMagnifiedFrameInContentCoordsLocked(Rect rect) { + MagnificationSpec spec = mMagnificationSpec; + mMagnifiedBounds.getBounds(rect); + rect.offset((int) -spec.offsetX, (int) -spec.offsetY); + rect.scale(1.0f / spec.scale); + } + + public boolean isMagnifyingLocked() { + return mMagnificationSpec.scale > 1.0f; + } + + public MagnificationSpec getMagnificationSpecLocked() { + return mMagnificationSpec; + } + + /** NOTE: This has to be called within a surface transaction. */ + public void drawWindowIfNeededLocked() { + recomputeBoundsLocked(); + mWindow.drawIfNeeded(); + } + + private final class ViewportWindow { + private static final String SURFACE_TITLE = "Magnification Overlay"; + + private static final String PROPERTY_NAME_ALPHA = "alpha"; + + private static final int MIN_ALPHA = 0; + private static final int MAX_ALPHA = 255; + + private final Point mTempPoint = new Point(); + private final Region mBounds = new Region(); + private final Rect mDirtyRect = new Rect(); + private final Paint mPaint = new Paint(); + + private final ValueAnimator mShowHideFrameAnimator; + private final Surface mSurface; + + private boolean mShown; + private int mAlpha; + + private boolean mInvalidated; + + public ViewportWindow(Context context) { + Surface surface = null; + try { + mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + surface = new Surface(mWindowManagerService.mFxSession, SURFACE_TITLE, + mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, Surface.HIDDEN); + } catch (OutOfResourcesException oore) { + /* ignore */ + } + mSurface = surface; + mSurface.setLayerStack(mWindowManager.getDefaultDisplay().getLayerStack()); + mSurface.setLayer(mWindowManagerService.mPolicy.windowTypeToLayerLw( + WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) + * WindowManagerService.TYPE_LAYER_MULTIPLIER); + mSurface.setPosition(0, 0); + + TypedValue typedValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight, + typedValue, true); + final int borderColor = context.getResources().getColor(typedValue.resourceId); + + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(mBorderWidth); + mPaint.setColor(borderColor); + + Interpolator interpolator = new DecelerateInterpolator(2.5f); + final long longAnimationDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_longAnimTime); + + mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA, + MIN_ALPHA, MAX_ALPHA); + mShowHideFrameAnimator.setInterpolator(interpolator); + mShowHideFrameAnimator.setDuration(longAnimationDuration); + mInvalidated = true; + } + + public void setShown(boolean shown, boolean animate) { + synchronized (mWindowManagerService.mWindowMap) { + if (mShown == shown) { + return; + } + mShown = shown; + if (animate) { + if (mShowHideFrameAnimator.isRunning()) { + mShowHideFrameAnimator.reverse(); + } else { + if (shown) { + mShowHideFrameAnimator.start(); + } else { + mShowHideFrameAnimator.reverse(); + } + } + } else { + mShowHideFrameAnimator.cancel(); + if (shown) { + setAlpha(MAX_ALPHA); + } else { + setAlpha(MIN_ALPHA); + } + } + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown); + } + } + } + + @SuppressWarnings("unused") + // Called reflectively from an animator. + public int getAlpha() { + synchronized (mWindowManagerService.mWindowMap) { + return mAlpha; + } + } + + public void setAlpha(int alpha) { + synchronized (mWindowManagerService.mWindowMap) { + if (mAlpha == alpha) { + return; + } + mAlpha = alpha; + invalidate(null); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha); + } + } + } + + public void setBounds(Region bounds) { + synchronized (mWindowManagerService.mWindowMap) { + if (mBounds.equals(bounds)) { + return; + } + mBounds.set(bounds); + invalidate(mDirtyRect); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds); + } + } + } + + public void updateSize() { + synchronized (mWindowManagerService.mWindowMap) { + mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + mSurface.setSize(mTempPoint.x, mTempPoint.y); + invalidate(mDirtyRect); + } + } + + public void invalidate(Rect dirtyRect) { + if (dirtyRect != null) { + mDirtyRect.set(dirtyRect); + } else { + mDirtyRect.setEmpty(); + } + mInvalidated = true; + mWindowManagerService.scheduleAnimationLocked(); + } + + /** NOTE: This has to be called within a surface transaction. */ + public void drawIfNeeded() { + synchronized (mWindowManagerService.mWindowMap) { + if (!mInvalidated) { + return; + } + mInvalidated = false; + Canvas canvas = null; + try { + // Empty dirty rectangle means unspecified. + if (mDirtyRect.isEmpty()) { + mBounds.getBounds(mDirtyRect); + } + mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth); + canvas = mSurface.lockCanvas(mDirtyRect); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); + } + } catch (IllegalArgumentException iae) { + /* ignore */ + } catch (OutOfResourcesException oore) { + /* ignore */ + } + if (canvas == null) { + return; + } + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "Bounds: " + mBounds); + } + canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); + mPaint.setAlpha(mAlpha); + Path path = mBounds.getBoundaryPath(); + canvas.drawPath(path, mPaint); + + mSurface.unlockCanvasAndPost(canvas); + + if (mAlpha > 0) { + mSurface.show(); + } else { + mSurface.hide(); + } + } + } + } + } + + private static final class WindowStateInfo { + private static final int MAX_POOL_SIZE = 30; + + private static final SimplePool<WindowStateInfo> sPool = + new SimplePool<WindowStateInfo>(MAX_POOL_SIZE); + + private static final Region mTempRegion = new Region(); + + public WindowState mWindowState; + public final Rect mTouchableRegion = new Rect(); + + public static WindowStateInfo obtain(WindowState windowState) { + WindowStateInfo info = sPool.acquire(); + if (info == null) { + info = new WindowStateInfo(); + } + info.mWindowState = windowState; + windowState.getTouchableRegion(mTempRegion); + mTempRegion.getBounds(info.mTouchableRegion); + return info; + } + + public void recycle() { + mWindowState = null; + mTouchableRegion.setEmpty(); + sPool.release(this); + } + } + + private class MyHandler extends Handler { + public static final int MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED = 1; + public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2; + public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3; + public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4; + public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5; + + public MyHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: { + Region bounds = (Region) message.obj; + try { + mCallbacks.onMagnifedBoundsChanged(bounds); + } catch (RemoteException re) { + /* ignore */ + } finally { + bounds.recycle(); + } + } break; + case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: { + SomeArgs args = (SomeArgs) message.obj; + final int left = args.argi1; + final int top = args.argi2; + final int right = args.argi3; + final int bottom = args.argi4; + try { + mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom); + } catch (RemoteException re) { + /* ignore */ + } finally { + args.recycle(); + } + } break; + case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: { + try { + mCallbacks.onUserContextChanged(); + } catch (RemoteException re) { + /* ignore */ + } + } break; + case MESSAGE_NOTIFY_ROTATION_CHANGED: { + final int rotation = message.arg1; + try { + mCallbacks.onRotationChanged(rotation); + } catch (RemoteException re) { + /* ignore */ + } + } break; + case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : { + synchronized (mWindowManagerService.mWindowMap) { + if (mMagnifedViewport.isMagnifyingLocked()) { + mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true); + mWindowManagerService.scheduleAnimationLocked(); + } + } + } break; + } + } + } +} diff --git a/services/java/com/android/server/wm/MagnificationSpec.java b/services/java/com/android/server/wm/MagnificationSpec.java deleted file mode 100644 index 31aae66..0000000 --- a/services/java/com/android/server/wm/MagnificationSpec.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -public class MagnificationSpec { - public float mScale = 1.0f; - public float mOffsetX; - public float mOffsetY; - - public void initialize(float scale, float offsetX, float offsetY) { - mScale = scale; - mOffsetX = offsetX; - mOffsetY = offsetY; - } - - public boolean isNop() { - return mScale == 1.0f && mOffsetX == 0 && mOffsetY == 0; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("<scale:"); - builder.append(mScale); - builder.append(",offsetX:"); - builder.append(mOffsetX); - builder.append(",offsetY:"); - builder.append(mOffsetY); - builder.append(">"); - return builder.toString(); - } -} diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index 54914c2..a9f3c0f 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -9,8 +9,7 @@ import static com.android.server.wm.WindowManagerService.LayoutFields.SET_UPDATE import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_MAY_CHANGE; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_FORCE_HIDING_CHANGED; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE; - -import static com.android.server.wm.WindowManagerService.H.UPDATE_ANIM_PARAMETERS; +import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_ACTION_PENDING; import android.content.Context; import android.os.Debug; @@ -25,9 +24,8 @@ import android.view.Surface; import android.view.WindowManagerPolicy; import android.view.animation.Animation; -import com.android.server.wm.WindowManagerService.AppWindowAnimParams; +import com.android.server.wm.WindowManagerService.DisplayContentsIterator; import com.android.server.wm.WindowManagerService.LayoutFields; -import com.android.server.wm.WindowManagerService.LayoutToAnimatorParams; import java.io.PrintWriter; import java.util.ArrayList; @@ -49,16 +47,6 @@ public class WindowAnimator { int mAdjResult; - // Layout changes for individual Displays. Indexed by displayId. - SparseIntArray mPendingLayoutChanges = new SparseIntArray(); - - // TODO: Assign these from each iteration through DisplayContent. Only valid between loops. - /** Overall window dimensions */ - int mDw, mDh; - - /** Interior window dimensions */ - int mInnerDw, mInnerDh; - /** Time of current animation step. Reset on each iteration */ long mCurrentTime; @@ -66,10 +54,10 @@ public class WindowAnimator { * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */ private int mAnimTransactionSequence; - // Window currently running an animation that has requested it be detached - // from the wallpaper. This means we need to ensure the wallpaper is - // visible behind it in case it animates in a way that would allow it to be - // seen. If multiple windows satisfy this, use the lowest window. + /** Window currently running an animation that has requested it be detached + * from the wallpaper. This means we need to ensure the wallpaper is + * visible behind it in case it animates in a way that would allow it to be + * seen. If multiple windows satisfy this, use the lowest window. */ WindowState mWindowDetachedWallpaper = null; WindowStateAnimator mUniverseBackground = null; @@ -80,28 +68,6 @@ public class WindowAnimator { SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<WindowAnimator.DisplayContentsAnimator>(); - static final int WALLPAPER_ACTION_PENDING = 1; - int mPendingActions; - - WindowState mWallpaperTarget = null; - AppWindowAnimator mWpAppAnimator = null; - WindowState mLowerWallpaperTarget = null; - WindowState mUpperWallpaperTarget = null; - - ArrayList<AppWindowAnimator> mAppAnimators = new ArrayList<AppWindowAnimator>(); - - ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); - - /** Parameters being passed from this into mService. */ - static class AnimatorToLayoutParams { - boolean mUpdateQueued; - int mBulkUpdateParams; - SparseIntArray mPendingLayoutChanges; - WindowState mWindowDetachedWallpaper; - } - /** Do not modify unless holding mService.mWindowMap or this and mAnimToLayout in that order */ - final AnimatorToLayoutParams mAnimToLayout = new AnimatorToLayoutParams(); - boolean mInitialized = false; // forceHiding states. @@ -129,13 +95,9 @@ public class WindowAnimator { mAnimationRunnable = new Runnable() { @Override public void run() { - // TODO(cmautner): When full isolation is achieved for animation, the first lock - // goes away and only the WindowAnimator.this remains. - synchronized(mService.mWindowMap) { - synchronized(WindowAnimator.this) { - copyLayoutToAnimParamsLocked(); - animateLocked(); - } + synchronized (mService.mWindowMap) { + mService.mAnimationScheduled = false; + animateLocked(); } } }; @@ -169,117 +131,17 @@ public class WindowAnimator { mDisplayContentsAnimators.delete(displayId); } - /** Locked on mAnimToLayout */ - void updateAnimToLayoutLocked() { - final AnimatorToLayoutParams animToLayout = mAnimToLayout; - synchronized (animToLayout) { - animToLayout.mBulkUpdateParams = mBulkUpdateParams; - animToLayout.mPendingLayoutChanges = mPendingLayoutChanges.clone(); - animToLayout.mWindowDetachedWallpaper = mWindowDetachedWallpaper; - - if (!animToLayout.mUpdateQueued) { - animToLayout.mUpdateQueued = true; - mService.mH.sendMessage(mService.mH.obtainMessage(UPDATE_ANIM_PARAMETERS)); - } - } - } - - /** Copy all WindowManagerService params into local params here. Locked on 'this'. */ - private void copyLayoutToAnimParamsLocked() { - final LayoutToAnimatorParams layoutToAnim = mService.mLayoutToAnim; - synchronized(layoutToAnim) { - layoutToAnim.mAnimationScheduled = false; - - if (!layoutToAnim.mParamsModified) { - return; - } - layoutToAnim.mParamsModified = false; - - if ((layoutToAnim.mChanges & LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED) != 0) { - layoutToAnim.mChanges &= ~LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED; - mWallpaperTokens = new ArrayList<WindowToken>(layoutToAnim.mWallpaperTokens); - } - - if (WindowManagerService.DEBUG_WALLPAPER_LIGHT) { - if (mWallpaperTarget != layoutToAnim.mWallpaperTarget - || mLowerWallpaperTarget != layoutToAnim.mLowerWallpaperTarget - || mUpperWallpaperTarget != layoutToAnim.mUpperWallpaperTarget) { - Slog.d(TAG, "Pulling anim wallpaper: target=" + layoutToAnim.mWallpaperTarget - + " lower=" + layoutToAnim.mLowerWallpaperTarget + " upper=" - + layoutToAnim.mUpperWallpaperTarget); - } - } - mWallpaperTarget = layoutToAnim.mWallpaperTarget; - mWpAppAnimator = mWallpaperTarget == null - ? null : mWallpaperTarget.mAppToken == null - ? null : mWallpaperTarget.mAppToken.mAppAnimator; - mLowerWallpaperTarget = layoutToAnim.mLowerWallpaperTarget; - mUpperWallpaperTarget = layoutToAnim.mUpperWallpaperTarget; - - // Set the new DimAnimator params. - final int numDisplays = mDisplayContentsAnimators.size(); - for (int i = 0; i < numDisplays; i++) { - final int displayId = mDisplayContentsAnimators.keyAt(i); - DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); - - displayAnimator.mWinAnimators.clear(); - final WinAnimatorList winAnimators = layoutToAnim.mWinAnimatorLists.get(displayId); - if (winAnimators != null) { - displayAnimator.mWinAnimators.addAll(winAnimators); - } - - DimAnimator.Parameters dimParams = layoutToAnim.mDimParams.get(displayId); - if (dimParams == null) { - displayAnimator.mDimParams = null; - } else { - final WindowStateAnimator newWinAnimator = dimParams.mDimWinAnimator; - - // Only set dim params on the highest dimmed layer. - final WindowStateAnimator existingDimWinAnimator = - displayAnimator.mDimParams == null ? - null : displayAnimator.mDimParams.mDimWinAnimator; - // Don't turn on for an unshown surface, or for any layer but the highest - // dimmed layer. - if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null - || !existingDimWinAnimator.mSurfaceShown - || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) { - displayAnimator.mDimParams = new DimAnimator.Parameters(dimParams); - } - } - } - - mAppAnimators.clear(); - final int N = layoutToAnim.mAppWindowAnimParams.size(); - for (int i = 0; i < N; i++) { - final AppWindowAnimParams params = layoutToAnim.mAppWindowAnimParams.get(i); - AppWindowAnimator appAnimator = params.mAppAnimator; - appAnimator.mAllAppWinAnimators.clear(); - appAnimator.mAllAppWinAnimators.addAll(params.mWinAnimators); - mAppAnimators.add(appAnimator); - } - } + AppWindowAnimator getWallpaperAppAnimator() { + return mService.mWallpaperTarget == null + ? null : mService.mWallpaperTarget.mAppToken == null + ? null : mService.mWallpaperTarget.mAppToken.mAppAnimator; } - void hideWallpapersLocked(final WindowState w, boolean fromAnimator) { - // There is an issue where this function can be called either from - // the animation or the layout side of the window manager. The problem - // is that if it is called from the layout side, we may not yet have - // propagated the current layout wallpaper state over into the animation - // state. If that is the case, we can do bad things like hide the - // wallpaper when we had just made it shown because the animation side - // doesn't yet see that there is now a wallpaper target. As a temporary - // work-around, we tell the function here which side of the window manager - // is calling so it can use the right state. - if (fromAnimator) { - hideWallpapersLocked(w, mWallpaperTarget, mLowerWallpaperTarget, mWallpaperTokens); - } else { - hideWallpapersLocked(w, mService.mWallpaperTarget, - mService.mLowerWallpaperTarget, mService.mWallpaperTokens); - } - } + void hideWallpapersLocked(final WindowState w) { + final WindowState wallpaperTarget = mService.mWallpaperTarget; + final WindowState lowerWallpaperTarget = mService.mLowerWallpaperTarget; + final ArrayList<WindowToken> wallpaperTokens = mService.mWallpaperTokens; - void hideWallpapersLocked(final WindowState w, final WindowState wallpaperTarget, - final WindowState lowerWallpaperTarget, final ArrayList<WindowToken> wallpaperTokens) { if ((wallpaperTarget == w && lowerWallpaperTarget == null) || wallpaperTarget == null) { final int numTokens = wallpaperTokens.size(); for (int i = numTokens - 1; i >= 0; i--) { @@ -306,12 +168,13 @@ public class WindowAnimator { private void updateAppWindowsLocked() { int i; - final int NAT = mAppAnimators.size(); + final ArrayList<AppWindowToken> appTokens = mService.mAnimatingAppTokens; + final int NAT = appTokens.size(); for (i=0; i<NAT; i++) { - final AppWindowAnimator appAnimator = mAppAnimators.get(i); + final AppWindowAnimator appAnimator = appTokens.get(i).mAppAnimator; final boolean wasAnimating = appAnimator.animation != null && appAnimator.animation != AppWindowAnimator.sDummyAnimation; - if (appAnimator.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) { + if (appAnimator.stepAnimationLocked(mCurrentTime)) { mAnimating = true; } else if (wasAnimating) { // stopped animating, do one more pass through the layout @@ -327,7 +190,7 @@ public class WindowAnimator { final AppWindowAnimator appAnimator = mService.mExitingAppTokens.get(i).mAppAnimator; final boolean wasAnimating = appAnimator.animation != null && appAnimator.animation != AppWindowAnimator.sDummyAnimation; - if (appAnimator.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) { + if (appAnimator.stepAnimationLocked(mCurrentTime)) { mAnimating = true; } else if (wasAnimating) { // stopped animating, do one more pass through the layout @@ -342,15 +205,14 @@ public class WindowAnimator { private void updateWindowsLocked(final int displayId) { ++mAnimTransactionSequence; - final WinAnimatorList winAnimatorList = - getDisplayContentsAnimatorLocked(displayId).mWinAnimators; + final WindowList windows = mService.getWindowListLocked(displayId); ArrayList<WindowStateAnimator> unForceHiding = null; boolean wallpaperInUnForceHiding = false; mForceHiding = KEYGUARD_NOT_SHOWN; - for (int i = winAnimatorList.size() - 1; i >= 0; i--) { - WindowStateAnimator winAnimator = winAnimatorList.get(i); - WindowState win = winAnimator.mWin; + for (int i = windows.size() - 1; i >= 0; i--) { + WindowState win = windows.get(i); + WindowStateAnimator winAnimator = win.mWinAnimator; final int flags = winAnimator.mAttrFlags; if (winAnimator.mSurface != null) { @@ -362,13 +224,13 @@ public class WindowAnimator { ", nowAnimating=" + nowAnimating); } - if (wasAnimating && !winAnimator.mAnimating && mWallpaperTarget == win) { + if (wasAnimating && !winAnimator.mAnimating && mService.mWallpaperTarget == win) { mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; setPendingLayoutChanges(Display.DEFAULT_DISPLAY, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2", - mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY)); + getPendingLayoutChanges(Display.DEFAULT_DISPLAY)); } } @@ -382,7 +244,7 @@ public class WindowAnimator { WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 3", - mPendingLayoutChanges.get(displayId)); + getPendingLayoutChanges(displayId)); } mService.mFocusMayChange = true; } @@ -445,7 +307,7 @@ public class WindowAnimator { WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4", - mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY)); + getPendingLayoutChanges(Display.DEFAULT_DISPLAY)); } } } @@ -455,11 +317,11 @@ public class WindowAnimator { if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) { if (atoken == null || atoken.allDrawn) { if (winAnimator.performShowLocked()) { - mPendingLayoutChanges.put(displayId, + setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 5", - mPendingLayoutChanges.get(displayId)); + getPendingLayoutChanges(displayId)); } } } @@ -493,21 +355,21 @@ public class WindowAnimator { private void updateWallpaperLocked(int displayId) { final DisplayContentsAnimator displayAnimator = getDisplayContentsAnimatorLocked(displayId); - final WinAnimatorList winAnimatorList = displayAnimator.mWinAnimators; + final WindowList windows = mService.getWindowListLocked(displayId); WindowStateAnimator windowAnimationBackground = null; int windowAnimationBackgroundColor = 0; WindowState detachedWallpaper = null; final DimSurface windowAnimationBackgroundSurface = displayAnimator.mWindowAnimationBackgroundSurface; - for (int i = winAnimatorList.size() - 1; i >= 0; i--) { - WindowStateAnimator winAnimator = winAnimatorList.get(i); + for (int i = windows.size() - 1; i >= 0; i--) { + final WindowState win = windows.get(i); + WindowStateAnimator winAnimator = win.mWinAnimator; if (winAnimator.mSurface == null) { continue; } final int flags = winAnimator.mAttrFlags; - final WindowState win = winAnimator.mWin; // If this window is animating, make a note that we have // an animating window and take care of a request to run @@ -566,11 +428,11 @@ public class WindowAnimator { // don't cause the wallpaper to suddenly disappear. int animLayer = windowAnimationBackground.mAnimLayer; WindowState win = windowAnimationBackground.mWin; - if (mWallpaperTarget == win - || mLowerWallpaperTarget == win || mUpperWallpaperTarget == win) { - final int N = winAnimatorList.size(); + if (mService.mWallpaperTarget == win || mService.mLowerWallpaperTarget == win + || mService.mUpperWallpaperTarget == win) { + final int N = windows.size(); for (int i = 0; i < N; i++) { - WindowStateAnimator winAnimator = winAnimatorList.get(i); + WindowStateAnimator winAnimator = windows.get(i).mWinAnimator; if (winAnimator.mIsWallpaper) { animLayer = winAnimator.mAnimLayer; break; @@ -579,7 +441,7 @@ public class WindowAnimator { } if (windowAnimationBackgroundSurface != null) { - windowAnimationBackgroundSurface.show(mDw, mDh, + windowAnimationBackgroundSurface.show( animLayer - WindowManagerService.LAYER_OFFSET_DIM, windowAnimationBackgroundColor); } @@ -590,13 +452,16 @@ public class WindowAnimator { } } + /** See if any windows have been drawn, so they (and others associated with them) can now be + * shown. */ private void testTokenMayBeDrawnLocked() { // See if any windows have been drawn, so they (and others // associated with them) can now be shown. - final int NT = mAppAnimators.size(); + final ArrayList<AppWindowToken> appTokens = mService.mAnimatingAppTokens; + final int NT = appTokens.size(); for (int i=0; i<NT; i++) { - AppWindowAnimator appAnimator = mAppAnimators.get(i); - AppWindowToken wtoken = appAnimator.mAppToken; + AppWindowToken wtoken = appTokens.get(i); + AppWindowAnimator appAnimator = wtoken.mAppAnimator; final boolean allDrawn = wtoken.allDrawn; if (allDrawn != appAnimator.allDrawn) { appAnimator.allDrawn = allDrawn; @@ -618,7 +483,7 @@ public class WindowAnimator { setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, "testTokenMayBeDrawnLocked"); - + // We can now show all of the drawn windows! if (!mService.mOpeningApps.contains(wtoken)) { mAnimating |= appAnimator.showAllWindowsLocked(); @@ -641,7 +506,6 @@ public class WindowAnimator { return; } - mPendingLayoutChanges.clear(); mCurrentTime = SystemClock.uptimeMillis(); mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE; boolean wasAnimating = mAnimating; @@ -678,10 +542,10 @@ public class WindowAnimator { // associated with exiting/removed apps performAnimationsLocked(displayId); - final WinAnimatorList winAnimatorList = displayAnimator.mWinAnimators; - final int N = winAnimatorList.size(); + final WindowList windows = mService.getWindowListLocked(displayId); + final int N = windows.size(); for (int j = 0; j < N; j++) { - winAnimatorList.get(j).prepareSurfaceLocked(true); + windows.get(j).mWinAnimator.prepareSurfaceLocked(true); } } @@ -706,6 +570,10 @@ public class WindowAnimator { mAnimating |= dimAnimator.updateSurface(isDimmingLocked(displayId), mCurrentTime, !mService.okToDisplay()); } + //TODO (multidisplay): Magnification is supported only for the default display. + if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) { + mService.mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(); + } } if (mService.mWatermark != null) { @@ -719,21 +587,30 @@ public class WindowAnimator { TAG, "<<< CLOSE TRANSACTION animateLocked"); } - for (int i = mPendingLayoutChanges.size() - 1; i >= 0; i--) { - if ((mPendingLayoutChanges.valueAt(i) - & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { - mPendingActions |= WALLPAPER_ACTION_PENDING; + boolean hasPendingLayoutChanges = false; + DisplayContentsIterator iterator = mService.new DisplayContentsIterator(); + while (iterator.hasNext()) { + final DisplayContent displayContent = iterator.next(); + final int pendingChanges = getPendingLayoutChanges(displayContent.getDisplayId()); + if ((pendingChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { + mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING; + } + if (pendingChanges != 0) { + hasPendingLayoutChanges = true; } } - if (mBulkUpdateParams != 0 || mPendingLayoutChanges.size() > 0) { - updateAnimToLayoutLocked(); + boolean doRequest = false; + if (mBulkUpdateParams != 0) { + doRequest = mService.copyAnimToLayoutParamsLocked(); + } + + if (hasPendingLayoutChanges || doRequest) { + mService.requestTraversalLocked(); } if (mAnimating) { - synchronized (mService.mLayoutToAnim) { - mService.scheduleAnimationLocked(); - } + mService.scheduleAnimationLocked(); } else if (wasAnimating) { mService.requestTraversalLocked(); } @@ -741,7 +618,7 @@ public class WindowAnimator { Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams) + " mPendingLayoutChanges(DEFAULT_DISPLAY)=" - + Integer.toHexString(mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY))); + + Integer.toHexString(getPendingLayoutChanges(Display.DEFAULT_DISPLAY))); } } @@ -750,14 +627,6 @@ public class WindowAnimator { mCurrentFocus = currentFocus; } - void setDisplayDimensions(final int curWidth, final int curHeight, - final int appWidth, final int appHeight) { - mDw = curWidth; - mDh = curHeight; - mInnerDw = appWidth; - mInnerDh = appHeight; - } - boolean isDimmingLocked(int displayId) { return getDisplayContentsAnimatorLocked(displayId).mDimParams != null; } @@ -792,51 +661,16 @@ public class WindowAnimator { final String subPrefix = " " + prefix; final String subSubPrefix = " " + subPrefix; - boolean needSep = false; - if (mAppAnimators.size() > 0) { - needSep = true; - pw.println(" App Animators:"); - for (int i=mAppAnimators.size()-1; i>=0; i--) { - AppWindowAnimator anim = mAppAnimators.get(i); - pw.print(prefix); pw.print("App Animator #"); pw.print(i); - pw.print(' '); pw.print(anim); - if (dumpAll) { - pw.println(':'); - anim.dump(pw, subPrefix, dumpAll); - } else { - pw.println(); - } - } - } - if (mWallpaperTokens.size() > 0) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.print(prefix); pw.println("Wallpaper tokens:"); - for (int i=mWallpaperTokens.size()-1; i>=0; i--) { - WindowToken token = mWallpaperTokens.get(i); - pw.print(prefix); pw.print("Wallpaper #"); pw.print(i); - pw.print(' '); pw.print(token); - if (dumpAll) { - pw.println(':'); - token.dump(pw, subPrefix); - } else { - pw.println(); - } - } - } - - if (needSep) { - pw.println(); - } for (int i = 0; i < mDisplayContentsAnimators.size(); i++) { pw.print(prefix); pw.print("DisplayContentsAnimator #"); pw.print(mDisplayContentsAnimators.keyAt(i)); pw.println(":"); DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); - for (int j=0; j<displayAnimator.mWinAnimators.size(); j++) { - WindowStateAnimator wanim = displayAnimator.mWinAnimators.get(j); + final WindowList windows = + mService.getWindowListLocked(mDisplayContentsAnimators.keyAt(i)); + final int N = windows.size(); + for (int j = 0; j < N; j++) { + WindowStateAnimator wanim = windows.get(j).mWinAnimator; pw.print(subPrefix); pw.print("Window #"); pw.print(j); pw.print(": "); pw.println(wanim); } @@ -876,58 +710,40 @@ public class WindowAnimator { pw.print(" mForceHiding="); pw.println(forceHidingToString()); pw.print(prefix); pw.print("mCurrentTime="); pw.println(TimeUtils.formatUptime(mCurrentTime)); - pw.print(prefix); pw.print("mDw="); - pw.print(mDw); pw.print(" mDh="); pw.print(mDh); - pw.print(" mInnerDw="); pw.print(mInnerDw); - pw.print(" mInnerDh="); pw.println(mInnerDh); } if (mBulkUpdateParams != 0) { pw.print(prefix); pw.print("mBulkUpdateParams=0x"); pw.print(Integer.toHexString(mBulkUpdateParams)); pw.println(bulkUpdateParamsToString(mBulkUpdateParams)); } - if (mPendingActions != 0) { - pw.print(prefix); pw.print("mPendingActions=0x"); - pw.println(Integer.toHexString(mPendingActions)); - } if (mWindowDetachedWallpaper != null) { pw.print(prefix); pw.print("mWindowDetachedWallpaper="); pw.println(mWindowDetachedWallpaper); } - pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget); - pw.print(prefix); pw.print("mWpAppAnimator="); pw.println(mWpAppAnimator); - if (mLowerWallpaperTarget != null || mUpperWallpaperTarget != null) { - pw.print(prefix); pw.print("mLowerWallpaperTarget="); - pw.println(mLowerWallpaperTarget); - pw.print(prefix); pw.print("mUpperWallpaperTarget="); - pw.println(mUpperWallpaperTarget); - } if (mUniverseBackground != null) { pw.print(prefix); pw.print("mUniverseBackground="); pw.print(mUniverseBackground); pw.print(" mAboveUniverseLayer="); pw.println(mAboveUniverseLayer); } } - void clearPendingActions() { - synchronized (this) { - mPendingActions = 0; - } + int getPendingLayoutChanges(final int displayId) { + return mService.getDisplayContentLocked(displayId).pendingLayoutChanges; } void setPendingLayoutChanges(final int displayId, final int changes) { - mPendingLayoutChanges.put(displayId, mPendingLayoutChanges.get(displayId) | changes); + mService.getDisplayContentLocked(displayId).pendingLayoutChanges |= changes; } void setAppLayoutChanges(final AppWindowAnimator appAnimator, final int changes, String s) { // Used to track which displays layout changes have been done. SparseIntArray displays = new SparseIntArray(); - for (int i = appAnimator.mAllAppWinAnimators.size() - 1; i >= 0; i--) { - WindowStateAnimator winAnimator = appAnimator.mAllAppWinAnimators.get(i); - final int displayId = winAnimator.mWin.mDisplayContent.getDisplayId(); + WindowList windows = appAnimator.mAppToken.allAppWindows; + for (int i = windows.size() - 1; i >= 0; i--) { + final int displayId = windows.get(i).getDisplayId(); if (displays.indexOfKey(displayId) < 0) { setPendingLayoutChanges(displayId, changes); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats(s, mPendingLayoutChanges.get(displayId)); + mService.debugLayoutRepeats(s, getPendingLayoutChanges(displayId)); } // Keep from processing this display again. displays.put(displayId, changes); @@ -935,6 +751,27 @@ public class WindowAnimator { } } + void setDimParamsLocked(int displayId, DimAnimator.Parameters dimParams) { + DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId); + if (dimParams == null) { + displayAnimator.mDimParams = null; + } else { + final WindowStateAnimator newWinAnimator = dimParams.mDimWinAnimator; + + // Only set dim params on the highest dimmed layer. + final WindowStateAnimator existingDimWinAnimator = + displayAnimator.mDimParams == null ? + null : displayAnimator.mDimParams.mDimWinAnimator; + // Don't turn on for an unshown surface, or for any layer but the highest + // dimmed layer. + if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null + || !existingDimWinAnimator.mSurfaceShown + || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) { + displayAnimator.mDimParams = new DimAnimator.Parameters(dimParams); + } + } + } + private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) { DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId); if (displayAnimator == null) { @@ -953,7 +790,6 @@ public class WindowAnimator { } private class DisplayContentsAnimator { - WinAnimatorList mWinAnimators = new WinAnimatorList(); DimAnimator mDimAnimator = null; DimAnimator.Parameters mDimParams = null; DimSurface mWindowAnimationBackgroundSurface = null; @@ -961,8 +797,8 @@ public class WindowAnimator { public DisplayContentsAnimator(int displayId) { mDimAnimator = new DimAnimator(mService.mFxSession, displayId); - mWindowAnimationBackgroundSurface = - new DimSurface(mService.mFxSession, displayId); + mWindowAnimationBackgroundSurface = new DimSurface(mService.mFxSession, + mService.getDisplayContentLocked(displayId)); } } } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 0466c15..1640cac 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -40,6 +40,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + import com.android.internal.app.IBatteryStats; import com.android.internal.policy.PolicyManager; import com.android.internal.policy.impl.PhoneWindowManager; @@ -58,7 +60,6 @@ import com.android.server.power.ShutdownThread; import android.Manifest; import android.app.ActivityManagerNative; -import android.app.ActivityOptions; import android.app.IActivityManager; import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; @@ -92,7 +93,6 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; -import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; @@ -115,8 +115,8 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IApplicationToken; -import android.view.IDisplayContentChangeListener; import android.view.IInputFilter; +import android.view.IMagnificationCallbacks; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindow; @@ -127,24 +127,19 @@ import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.KeyEvent; +import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceSession; import android.view.View; import android.view.ViewTreeObserver; -import android.view.WindowInfo; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerPolicy.FakeWindow; -import android.view.animation.AlphaAnimation; import android.view.animation.Animation; -import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; -import android.view.animation.ScaleAnimation; import android.view.animation.Transformation; import java.io.BufferedWriter; @@ -268,9 +263,6 @@ public class WindowManagerService extends IWindowManager.Stub /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */ static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000; - /** Fraction of animation at which the recents thumbnail becomes completely transparent */ - static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f; - /** * If true, the window manager will do its own custom freezing and general * management of the screen during rotation. @@ -296,8 +288,6 @@ public class WindowManagerService extends IWindowManager.Stub private final boolean mHeadless; - private static final float THUMBNAIL_ANIMATION_DECELERATE_FACTOR = 1.5f; - final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -435,6 +425,8 @@ public class WindowManagerService extends IWindowManager.Stub IInputMethodManager mInputMethodManager; + DisplayMagnifier mDisplayMagnifier; + final SurfaceSession mFxSession; Watermark mWatermark; StrictModeFlash mStrictModeFlash; @@ -486,29 +478,10 @@ public class WindowManagerService extends IWindowManager.Stub // changes the orientation. PowerManager.WakeLock mScreenFrozenLock; - // State management of app transitions. When we are preparing for a - // transition, mNextAppTransition will be the kind of transition to - // perform or TRANSIT_NONE if we are not waiting. If we are waiting, - // mOpeningApps and mClosingApps are the lists of tokens that will be - // made visible or hidden at the next transition. - int mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; - int mNextAppTransitionType = ActivityOptions.ANIM_NONE; - String mNextAppTransitionPackage; - Bitmap mNextAppTransitionThumbnail; - // Used for thumbnail transitions. True if we're scaling up, false if scaling down - boolean mNextAppTransitionScaleUp; - IRemoteCallback mNextAppTransitionCallback; - int mNextAppTransitionEnter; - int mNextAppTransitionExit; - int mNextAppTransitionStartX; - int mNextAppTransitionStartY; - int mNextAppTransitionStartWidth; - int mNextAppTransitionStartHeight; - boolean mAppTransitionReady = false; - boolean mAppTransitionRunning = false; - boolean mAppTransitionTimeout = false; + final AppTransition mAppTransition; boolean mStartingIconInTransition = false; boolean mSkipAppTransitionAnimation = false; + final ArrayList<AppWindowToken> mOpeningApps = new ArrayList<AppWindowToken>(); final ArrayList<AppWindowToken> mClosingApps = new ArrayList<AppWindowToken>(); @@ -551,7 +524,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState mLowerWallpaperTarget = null; // If non-null, we are in the middle of animating from one wallpaper target // to another, and this is the higher one in Z-order. - private WindowState mUpperWallpaperTarget = null; + WindowState mUpperWallpaperTarget = null; int mWallpaperAnimLayerAdjustment; float mLastWallpaperX = -1; float mLastWallpaperY = -1; @@ -595,11 +568,11 @@ public class WindowManagerService extends IWindowManager.Stub static final int SET_FORCE_HIDING_CHANGED = 1 << 2; static final int SET_ORIENTATION_CHANGE_COMPLETE = 1 << 3; static final int SET_TURN_ON_SCREEN = 1 << 4; + static final int SET_WALLPAPER_ACTION_PENDING = 1 << 5; boolean mWallpaperForceHidingChanged = false; boolean mWallpaperMayChange = false; boolean mOrientationChangeComplete = true; - int mAdjResult = 0; private Session mHoldScreen = null; private boolean mObscured = false; boolean mDimming = false; @@ -608,6 +581,7 @@ public class WindowManagerService extends IWindowManager.Stub private float mButtonBrightness = -1; private long mUserActivityTimeout = -1; private boolean mUpdateRotation = false; + boolean mWallpaperActionPending = false; private static final int DISPLAY_CONTENT_UNKNOWN = 0; private static final int DISPLAY_CONTENT_MIRROR = 1; @@ -632,27 +606,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - static class LayoutToAnimatorParams { - boolean mParamsModified; - - static final long WALLPAPER_TOKENS_CHANGED = 1 << 0; - long mChanges; - - boolean mAnimationScheduled; - SparseArray<WinAnimatorList> mWinAnimatorLists = new SparseArray<WinAnimatorList>(); - WindowState mWallpaperTarget; - WindowState mLowerWallpaperTarget; - WindowState mUpperWallpaperTarget; - SparseArray<DimAnimator.Parameters> mDimParams = new SparseArray<DimAnimator.Parameters>(); - ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); - ArrayList<AppWindowAnimParams> mAppWindowAnimParams = new ArrayList<AppWindowAnimParams>(); - } - /** Params from WindowManagerService to WindowAnimator. Do not modify or read without first - * locking on either mWindowMap or mAnimator and then on mLayoutToAnim */ - final LayoutToAnimatorParams mLayoutToAnim = new LayoutToAnimatorParams(); - - /** The lowest wallpaper target with a detached wallpaper animation on it. */ - WindowState mWindowDetachedWallpaper = null; + boolean mAnimationScheduled; /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */ @@ -732,9 +686,6 @@ public class WindowManagerService extends IWindowManager.Stub */ boolean mInTouchMode = true; - // Temp regions for intermediary calculations. - private final Region mTempRegion = new Region(); - private ViewServer mViewServer; private ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<WindowChangeListener>(); @@ -815,6 +766,8 @@ public class WindowManagerService extends IWindowManager.Stub "SCREEN_FROZEN"); mScreenFrozenLock.setReferenceCounted(false); + mAppTransition = new AppTransition(context, mH); + mActivityManager = ActivityManagerNative.getDefault(); mBatteryStats = BatteryStatsService.getService(); @@ -1278,13 +1231,12 @@ public class WindowManagerService extends IWindowManager.Stub } if (highestTarget != null) { - if (DEBUG_INPUT_METHOD) Slog.v(TAG, "mNextAppTransition=" - + mNextAppTransition + " " + highestTarget + if (DEBUG_INPUT_METHOD) Slog.v(TAG, mAppTransition + " " + highestTarget + " animating=" + highestTarget.mWinAnimator.isAnimating() + " layer=" + highestTarget.mWinAnimator.mAnimLayer + " new layer=" + w.mWinAnimator.mAnimLayer); - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + if (mAppTransition.isTransitionSet()) { // If we are currently setting up for an animation, // hold everything until we can find out what will happen. mInputMethodTargetWaitingAnim = true; @@ -1590,7 +1542,6 @@ public class WindowManagerService extends IWindowManager.Stub int adjustWallpaperWindowsLocked() { mInnerFields.mWallpaperMayChange = false; - int changed = 0; boolean targetChanged = false; // TODO(multidisplay): Wallpapers on main screen only. @@ -1620,7 +1571,7 @@ public class WindowManagerService extends IWindowManager.Stub continue; } topCurW = null; - if (w != mWindowDetachedWallpaper && w.mAppToken != null) { + if (w != mAnimator.mWindowDetachedWallpaper && w.mAppToken != null) { // If this window's app token is hidden and not animating, // it is of no interest to us. if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) { @@ -1629,10 +1580,10 @@ public class WindowManagerService extends IWindowManager.Stub continue; } } - if (DEBUG_WALLPAPER) Slog.v(TAG, "Win #" + i + " " + w + ": readyfordisplay=" - + w.isReadyForDisplay() + " mDrawState=" + w.mWinAnimator.mDrawState); - if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isReadyForDisplay() - && (mWallpaperTarget == w || w.isDrawnLw())) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Win #" + i + " " + w + ": isOnScreen=" + + w.isOnScreen() + " mDrawState=" + w.mWinAnimator.mDrawState); + if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isOnScreen() + && (mWallpaperTarget == w || w.isDrawFinishedLw())) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: #" + i + "=" + w); foundW = w; @@ -1646,7 +1597,7 @@ public class WindowManagerService extends IWindowManager.Stub continue; } break; - } else if (w == mWindowDetachedWallpaper) { + } else if (w == mAnimator.mWindowDetachedWallpaper) { windowDetachedI = i; } } @@ -1658,27 +1609,6 @@ public class WindowManagerService extends IWindowManager.Stub foundI = windowDetachedI; } - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { - // If we are currently waiting for an app transition, and either - // the current target or the next target are involved with it, - // then hold off on doing anything with the wallpaper. - // Note that we are checking here for just whether the target - // is part of an app token... which is potentially overly aggressive - // (the app token may not be involved in the transition), but good - // enough (we'll just wait until whatever transition is pending - // executes). - if (mWallpaperTarget != null && mWallpaperTarget.mAppToken != null) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Wallpaper not changing: waiting for app anim in current target"); - return 0; - } - if (foundW != null && foundW.mAppToken != null) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Wallpaper not changing: waiting for app anim in found target"); - return 0; - } - } - if (mWallpaperTarget != foundW && (mLowerWallpaperTarget == null || mLowerWallpaperTarget != foundW)) { if (DEBUG_WALLPAPER_LIGHT) { @@ -1696,12 +1626,8 @@ public class WindowManagerService extends IWindowManager.Stub // Now what is happening... if the current and new targets are // animating, then we are in our super special mode! if (foundW != null && oldW != null) { - boolean oldAnim = oldW.mWinAnimator.mAnimation != null - || (oldW.mAppToken != null - && oldW.mAppToken.mAppAnimator.animation != null); - boolean foundAnim = foundW.mWinAnimator.mAnimation != null - || (foundW.mAppToken != null && - foundW.mAppToken.mAppAnimator.animation != null); + boolean oldAnim = oldW.isAnimatingLw(); + boolean foundAnim = foundW.isAnimatingLw(); if (DEBUG_WALLPAPER_LIGHT) { Slog.v(TAG, "New animation: " + foundAnim + " old animation: " + oldAnim); @@ -1753,13 +1679,7 @@ public class WindowManagerService extends IWindowManager.Stub } else if (mLowerWallpaperTarget != null) { // Is it time to stop animating? - boolean lowerAnimating = mLowerWallpaperTarget.mWinAnimator.mAnimation != null - || (mLowerWallpaperTarget.mAppToken != null - && mLowerWallpaperTarget.mAppToken.mAppAnimator.animation != null); - boolean upperAnimating = mUpperWallpaperTarget.mWinAnimator.mAnimation != null - || (mUpperWallpaperTarget.mAppToken != null - && mUpperWallpaperTarget.mAppToken.mAppAnimator.animation != null); - if (!lowerAnimating || !upperAnimating) { + if (!mLowerWallpaperTarget.isAnimatingLw() || !mUpperWallpaperTarget.isAnimatingLw()) { if (DEBUG_WALLPAPER_LIGHT) { Slog.v(TAG, "No longer animating wallpaper targets!"); } @@ -1835,6 +1755,7 @@ public class WindowManagerService extends IWindowManager.Stub // Start stepping backwards from here, ensuring that our wallpaper windows // are correctly placed. + int changed = 0; int curTokenIndex = mWallpaperTokens.size(); while (curTokenIndex > 0) { curTokenIndex--; @@ -2279,9 +2200,9 @@ public class WindowManagerService extends IWindowManager.Stub addWindowToListInOrderLocked(win, true); if (type == TYPE_WALLPAPER) { mLastWallpaperTimeoutTime = 0; - adjustWallpaperWindowsLocked(); + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) { - adjustWallpaperWindowsLocked(); + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if (mWallpaperTarget != null && mWallpaperTarget.mLayer >= win.mBaseLayer) { // If there is currently a wallpaper being shown, and @@ -2289,7 +2210,7 @@ public class WindowManagerService extends IWindowManager.Stub // layer of the target window, then adjust the wallpaper. // This is to avoid a new window being placed between the // wallpaper and its target. - adjustWallpaperWindowsLocked(); + displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } } @@ -2403,7 +2324,11 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mWinAnimator.applyAnimationLocked(transit, false)) { win.mExiting = true; } - scheduleNotifyWindowTranstionIfNeededLocked(win, transit); + //TODO (multidisplay): Magnification is supported only for the default display. + if (mDisplayMagnifier != null + && win.getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onWindowTransitionLocked(win, transit); + } } if (win.mExiting || win.mWinAnimator.isAnimating()) { // The exit animation is running... wait for it! @@ -2521,9 +2446,11 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mAttrs.type == TYPE_WALLPAPER) { mLastWallpaperTimeoutTime = 0; - adjustWallpaperWindowsLocked(); + getDefaultDisplayContentLocked().pendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } else if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) { - adjustWallpaperWindowsLocked(); + getDefaultDisplayContentLocked().pendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } if (!mInLayout) { @@ -2695,49 +2622,13 @@ public class WindowManagerService extends IWindowManager.Stub public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) { synchronized (mWindowMap) { - WindowState window = mWindowMap.get(token); - if (window != null) { - scheduleNotifyRectangleOnScreenRequestedIfNeededLocked(window, rectangle, - immediate); - } - } - } - - private void scheduleNotifyRectangleOnScreenRequestedIfNeededLocked(WindowState window, - Rect rectangle, boolean immediate) { - DisplayContent displayContent = window.mDisplayContent; - if (displayContent.mDisplayContentChangeListeners != null - && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) { - mH.obtainMessage(H.NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED, displayContent.getDisplayId(), - immediate? 1 : 0, new Rect(rectangle)).sendToTarget(); - } - } - - private void handleNotifyRectangleOnScreenRequested(int displayId, Rect rectangle, - boolean immediate) { - RemoteCallbackList<IDisplayContentChangeListener> callbacks = null; - synchronized (mWindowMap) { - DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent == null) { - return; - } - callbacks = displayContent.mDisplayContentChangeListeners; - if (callbacks == null) { - return; - } - } - final int callbackCount = callbacks.beginBroadcast(); - try { - for (int i = 0; i < callbackCount; i++) { - try { - callbacks.getBroadcastItem(i).onRectangleOnScreenRequested(displayId, - rectangle, immediate); - } catch (RemoteException re) { - /* ignore */ + if (mDisplayMagnifier != null) { + WindowState window = mWindowMap.get(token); + //TODO (multidisplay): Magnification is supported only for the default display. + if (window != null && window.getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle, immediate); } } - } finally { - callbacks.finishBroadcast(); } } @@ -2879,7 +2770,7 @@ public class WindowManagerService extends IWindowManager.Stub } if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) { // To change the format, we need to re-build the surface. - winAnimator.destroySurfaceLocked(false); + winAnimator.destroySurfaceLocked(); toBeDisplayed = true; surfaceChanged = true; } @@ -2960,9 +2851,13 @@ public class WindowManagerService extends IWindowManager.Stub if (mInputMethodWindow == win) { mInputMethodWindow = null; } - winAnimator.destroySurfaceLocked(false); + winAnimator.destroySurfaceLocked(); + } + //TODO (multidisplay): Magnification is supported only for the default + if (mDisplayMagnifier != null + && win.getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onWindowTransitionLocked(win, transit); } - scheduleNotifyWindowTranstionIfNeededLocked(win, transit); } } @@ -2994,16 +2889,12 @@ public class WindowManagerService extends IWindowManager.Stub } } if (wallpaperMayMove) { - if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { - assignLayers = true; - } + getDefaultDisplayContentLocked().pendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } win.mDisplayContent.layoutNeeded = true; win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0; - if (assignLayers) { - assignLayersLocked(win.getWindowList()); - } configChanged = updateOrientationFromAppTokensLocked(false); performLayoutAndPlaceSurfacesLocked(); if (toBeDisplayed && win.mIsWallpaper) { @@ -3057,12 +2948,12 @@ public class WindowManagerService extends IWindowManager.Stub long origId = Binder.clearCallingIdentity(); try { - synchronized(mWindowMap) { + synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); if (win == null) { return; } - win.mWinAnimator.destroyDeferredSurfaceLocked(false); + win.mWinAnimator.destroyDeferredSurfaceLocked(); } } finally { Binder.restoreCallingIdentity(origId); @@ -3073,7 +2964,7 @@ public class WindowManagerService extends IWindowManager.Stub long origId = Binder.clearCallingIdentity(); try { - synchronized(mWindowMap) { + synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); if (win == null) { return false; @@ -3087,11 +2978,12 @@ public class WindowManagerService extends IWindowManager.Stub public void finishDrawingWindow(Session session, IWindow client) { final long origId = Binder.clearCallingIdentity(); - synchronized(mWindowMap) { + synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); if (win != null && win.mWinAnimator.finishDrawingLocked()) { - if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) { - adjustWallpaperWindowsLocked(); + if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { + getDefaultDisplayContentLocked().pendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } win.mDisplayContent.layoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); @@ -3101,391 +2993,80 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public float getWindowCompatibilityScale(IBinder windowToken) { - if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, - "getWindowCompatibilityScale()")) { - throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); - } - synchronized (mWindowMap) { - WindowState windowState = mWindowMap.get(windowToken); - return (windowState != null) ? windowState.mGlobalScale : 1.0f; - } - } - - @Override - public WindowInfo getWindowInfo(IBinder token) { + public void getWindowFrame(IBinder token, Rect outBounds) { if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, "getWindowInfo()")) { throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); } synchronized (mWindowMap) { - WindowState window = mWindowMap.get(token); - if (window != null) { - return getWindowInfoForWindowStateLocked(window); + WindowState windowState = mWindowMap.get(token); + if (windowState != null) { + outBounds.set(windowState.mFrame); + } else { + outBounds.setEmpty(); } - return null; } } @Override - public void getVisibleWindowsForDisplay(int displayId, List<WindowInfo> outInfos) { - if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, - "getWindowInfos()")) { - throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); + public void setMagnificationSpec(MagnificationSpec spec) { + if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY, + "setMagnificationSpec()")) { + throw new SecurityException("Requires MAGNIFY_DISPLAY permission."); } synchronized (mWindowMap) { - DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent == null) { - return; - } - WindowList windows = displayContent.getWindowList(); - final int windowCount = windows.size(); - for (int i = 0; i < windowCount; i++) { - WindowState window = windows.get(i); - if (window.isVisibleLw() || window.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) { - WindowInfo info = getWindowInfoForWindowStateLocked(window); - outInfos.add(info); - } + if (mDisplayMagnifier != null) { + mDisplayMagnifier.setMagnificationSpecLocked(spec); + } else { + throw new IllegalStateException("Magnification callbacks not set!"); } } + if (Binder.getCallingPid() != android.os.Process.myPid()) { + spec.recycle(); + } } @Override - public void magnifyDisplay(int displayId, float scale, float offsetX, float offsetY) { - if (!checkCallingPermission( - android.Manifest.permission.MAGNIFY_DISPLAY, "magnifyDisplay()")) { - throw new SecurityException("Requires MAGNIFY_DISPLAY permission"); + public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) { + if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY, + "getCompatibleMagnificationSpecForWindow()")) { + throw new SecurityException("Requires MAGNIFY_DISPLAY permission."); } synchronized (mWindowMap) { - MagnificationSpec spec = getDisplayMagnificationSpecLocked(displayId); - if (spec != null) { - final boolean scaleChanged = spec.mScale != scale; - final boolean offsetChanged = spec.mOffsetX != offsetX || spec.mOffsetY != offsetY; - if (!scaleChanged && !offsetChanged) { - return; - } - spec.initialize(scale, offsetX, offsetY); - // If the offset has changed we need to re-add the input windows - // since the offsets have to be propagated to the input system. - if (offsetChanged) { - // TODO(multidisplay): Input only occurs on the default display. - if (displayId == Display.DEFAULT_DISPLAY) { - mInputMonitor.updateInputWindowsLw(true); - } - } - scheduleAnimationLocked(); - } - } - } - - MagnificationSpec getDisplayMagnificationSpecLocked(int displayId) { - DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { - if (displayContent.mMagnificationSpec == null) { - displayContent.mMagnificationSpec = new MagnificationSpec(); + WindowState windowState = mWindowMap.get(windowToken); + if (windowState == null) { + return null; } - return displayContent.mMagnificationSpec; - } - return null; - } - - private WindowInfo getWindowInfoForWindowStateLocked(WindowState window) { - WindowInfo info = WindowInfo.obtain(); - info.token = window.mToken.token; - info.frame.set(window.mFrame); - info.type = window.mAttrs.type; - info.displayId = window.getDisplayId(); - info.compatibilityScale = window.mGlobalScale; - info.visible = window.isVisibleLw() || info.type == TYPE_UNIVERSE_BACKGROUND; - info.layer = window.mLayer; - window.getTouchableRegion(mTempRegion); - mTempRegion.getBounds(info.touchableRegion); - return info; - } - - private AttributeCache.Entry getCachedAnimations(int userId, WindowManager.LayoutParams lp) { - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" - + (lp != null ? lp.packageName : null) - + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null)); - if (lp != null && lp.windowAnimations != 0) { - // If this is a system resource, don't try to load it from the - // application resources. It is nice to avoid loading application - // resources if we can. - String packageName = lp.packageName != null ? lp.packageName : "android"; - int resId = lp.windowAnimations; - if ((resId&0xFF000000) == 0x01000000) { - packageName = "android"; - } - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package=" - + packageName); - return AttributeCache.instance().get(userId, packageName, resId, - com.android.internal.R.styleable.WindowAnimation); - } - return null; - } - - private AttributeCache.Entry getCachedAnimations(int userId, String packageName, int resId) { - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package=" - + packageName + " resId=0x" + Integer.toHexString(resId)); - if (packageName != null) { - if ((resId&0xFF000000) == 0x01000000) { - packageName = "android"; + MagnificationSpec spec = null; + if (mDisplayMagnifier != null) { + spec = mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState); } - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package=" - + packageName); - return AttributeCache.instance().get(userId, packageName, resId, - com.android.internal.R.styleable.WindowAnimation); - } - return null; - } - - Animation loadAnimation(int userId, WindowManager.LayoutParams lp, int animAttr) { - int anim = 0; - Context context = mContext; - if (animAttr >= 0) { - AttributeCache.Entry ent = getCachedAnimations(userId, lp); - if (ent != null) { - context = ent.context; - anim = ent.array.getResourceId(animAttr, 0); - } - } - if (anim != 0) { - return AnimationUtils.loadAnimation(context, anim); - } - return null; - } - - private Animation loadAnimation(int userId, String packageName, int resId) { - int anim = 0; - Context context = mContext; - if (resId >= 0) { - AttributeCache.Entry ent = getCachedAnimations(userId, packageName, resId); - if (ent != null) { - context = ent.context; - anim = resId; + if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) { + return null; } + spec = (spec == null) ? MagnificationSpec.obtain() : MagnificationSpec.obtain(spec); + spec.scale *= windowState.mGlobalScale; + return spec; } - if (anim != 0) { - return AnimationUtils.loadAnimation(context, anim); - } - return null; } - private Animation createExitAnimationLocked(int transit, int duration) { - if (transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN || - transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE) { - // If we are on top of the wallpaper, we need an animation that - // correctly handles the wallpaper staying static behind all of - // the animated elements. To do this, will just have the existing - // element fade out. - Animation a = new AlphaAnimation(1, 0); - a.setDetachWallpaper(true); - a.setDuration(duration); - return a; - } - // For normal animations, the exiting element just holds in place. - Animation a = new AlphaAnimation(1, 1); - a.setDuration(duration); - return a; - } - - /** - * Compute the pivot point for an animation that is scaling from a small - * rect on screen to a larger rect. The pivot point varies depending on - * the distance between the inner and outer edges on both sides. This - * function computes the pivot point for one dimension. - * @param startPos Offset from left/top edge of outer rectangle to - * left/top edge of inner rectangle. - * @param finalScale The scaling factor between the size of the outer - * and inner rectangles. - */ - private static float computePivot(int startPos, float finalScale) { - final float denom = finalScale-1; - if (Math.abs(denom) < .0001f) { - return startPos; - } - return -startPos / denom; - } - - private Animation createScaleUpAnimationLocked(int transit, boolean enter) { - Animation a; - // Pick the desired duration. If this is an inter-activity transition, - // it is the standard duration for that. Otherwise we use the longer - // task transition duration. - int duration; - switch (transit) { - case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: - case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: - duration = mContext.getResources().getInteger( - com.android.internal.R.integer.config_shortAnimTime); - break; - default: - duration = 300; - break; - } - // TODO(multidisplay): For now assume all app animation is on main display. - final DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); - if (enter) { - // Entering app zooms out from the center of the initial rect. - float scaleW = mNextAppTransitionStartWidth / (float) displayInfo.appWidth; - float scaleH = mNextAppTransitionStartHeight / (float) displayInfo.appHeight; - Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mNextAppTransitionStartX, scaleW), - computePivot(mNextAppTransitionStartY, scaleH)); - scale.setDuration(duration); - AnimationSet set = new AnimationSet(true); - Animation alpha = new AlphaAnimation(0, 1); - scale.setDuration(duration); - set.addAnimation(scale); - alpha.setDuration(duration); - set.addAnimation(alpha); - set.setDetachWallpaper(true); - a = set; - } else { - a = createExitAnimationLocked(transit, duration); - } - a.setFillAfter(true); - final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext, - com.android.internal.R.interpolator.decelerate_cubic); - a.setInterpolator(interpolator); - a.initialize(displayInfo.appWidth, displayInfo.appHeight, - displayInfo.appWidth, displayInfo.appHeight); - return a; - } - - private Animation createThumbnailAnimationLocked(int transit, - boolean enter, boolean thumb, boolean scaleUp) { - Animation a; - final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); - final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; - final int thumbHeightI = mNextAppTransitionThumbnail.getHeight(); - final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; - // Pick the desired duration. If this is an inter-activity transition, - // it is the standard duration for that. Otherwise we use the longer - // task transition duration. - int duration; - switch (transit) { - case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: - case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: - duration = mContext.getResources().getInteger( - com.android.internal.R.integer.config_shortAnimTime); - break; - default: - duration = 250; - break; + @Override + public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) { + if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY, + "setMagnificationCallbacks()")) { + throw new SecurityException("Requires MAGNIFY_DISPLAY permission."); } - // TOOD(multidisplay): For now assume all app animation is on the main screen. - DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); - if (thumb) { - // Animation for zooming thumbnail from its initial size to - // filling the screen. - if (scaleUp) { - float scaleW = displayInfo.appWidth / thumbWidth; - float scaleH = displayInfo.appHeight / thumbHeight; - - Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, - computePivot(mNextAppTransitionStartX, 1 / scaleW), - computePivot(mNextAppTransitionStartY, 1 / scaleH)); - AnimationSet set = new AnimationSet(false); - Animation alpha = new AlphaAnimation(1, 0); - scale.setDuration(duration); - scale.setInterpolator(AnimationUtils.loadInterpolator(mContext, - com.android.internal.R.interpolator.decelerate_quad)); - set.addAnimation(scale); - alpha.setDuration(duration); - alpha.setInterpolator(new Interpolator() { - @Override - public float getInterpolation(float input) { - if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) { - // linear response - return input / RECENTS_THUMBNAIL_FADEOUT_FRACTION; - } - // complete - return 1; - } - }); - set.addAnimation(alpha); - set.setFillBefore(true); - a = set; - } else { - float scaleW = displayInfo.appWidth / thumbWidth; - float scaleH = displayInfo.appHeight / thumbHeight; - - Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mNextAppTransitionStartX, 1 / scaleW), - computePivot(mNextAppTransitionStartY, 1 / scaleH)); - AnimationSet set = new AnimationSet(true); - Animation alpha = new AlphaAnimation(1, 1); - scale.setDuration(duration); - scale.setInterpolator( - new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); - set.addAnimation(scale); - alpha.setDuration(duration); - set.addAnimation(alpha); - set.setFillBefore(true); - - a = set; - } - } else if (enter) { - // Entering app zooms out from the center of the thumbnail. - if (scaleUp) { - float scaleW = thumbWidth / displayInfo.appWidth; - float scaleH = thumbHeight / displayInfo.appHeight; - Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mNextAppTransitionStartX, scaleW), - computePivot(mNextAppTransitionStartY, scaleH)); - scale.setDuration(duration); - scale.setInterpolator( - new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); - scale.setFillBefore(true); - a = scale; + synchronized (mWindowMap) { + if (mDisplayMagnifier == null) { + mDisplayMagnifier = new DisplayMagnifier(this, callbacks); } else { - // noop animation - a = new AlphaAnimation(1, 1); - a.setDuration(duration); - } - } else { - // Exiting app - if (scaleUp) { - if (transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN) { - // Fade out while bringing up selected activity. This keeps the - // current activity from showing through a launching wallpaper - // activity. - a = new AlphaAnimation(1, 0); + if (callbacks == null) { + mDisplayMagnifier = null; } else { - // noop animation - a = new AlphaAnimation(1, 1); + throw new IllegalStateException("Magnification callbacks already set!"); } - a.setDuration(duration); - } else { - float scaleW = thumbWidth / displayInfo.appWidth; - float scaleH = thumbHeight / displayInfo.appHeight; - Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, - computePivot(mNextAppTransitionStartX, scaleW), - computePivot(mNextAppTransitionStartY, scaleH)); - scale.setDuration(duration); - scale.setInterpolator( - new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); - scale.setFillBefore(true); - AnimationSet set = new AnimationSet(true); - Animation alpha = new AlphaAnimation(1, 0); - set.addAnimation(scale); - alpha.setDuration(duration); - alpha.setInterpolator(new DecelerateInterpolator( - THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); - set.addAnimation(alpha); - set.setFillBefore(true); - set.setZAdjustment(Animation.ZORDER_TOP); - a = set; - } - } - a.setFillAfter(true); - final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext, - com.android.internal.R.interpolator.decelerate_quad); - a.setInterpolator(interpolator); - a.initialize(displayInfo.appWidth, displayInfo.appHeight, - displayInfo.appWidth, displayInfo.appHeight); - return a; + } + } } private boolean applyAnimationLocked(AppWindowToken atoken, @@ -3495,98 +3076,12 @@ public class WindowManagerService extends IWindowManager.Stub // artifacts when we unfreeze the display if some different animation // is running. if (okToDisplay()) { - Animation a; - boolean initialized = false; - if (mNextAppTransitionType == ActivityOptions.ANIM_CUSTOM) { - a = loadAnimation(atoken.userId, mNextAppTransitionPackage, enter ? - mNextAppTransitionEnter : mNextAppTransitionExit); - if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, - "applyAnimation: atoken=" + atoken - + " anim=" + a + " nextAppTransition=ANIM_CUSTOM" - + " transit=" + transit + " isEntrance=" + enter - + " Callers=" + Debug.getCallers(3)); - } else if (mNextAppTransitionType == ActivityOptions.ANIM_SCALE_UP) { - a = createScaleUpAnimationLocked(transit, enter); - initialized = true; - if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, - "applyAnimation: atoken=" + atoken - + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP" - + " transit=" + transit + " isEntrance=" + enter - + " Callers=" + Debug.getCallers(3)); - } else if (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP || - mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN) { - boolean scaleUp = (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP); - a = createThumbnailAnimationLocked(transit, enter, false, scaleUp); - initialized = true; - if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { - String animName = scaleUp ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN"; - Slog.v(TAG, "applyAnimation: atoken=" + atoken - + " anim=" + a + " nextAppTransition=" + animName - + " transit=" + transit + " isEntrance=" + enter - + " Callers=" + Debug.getCallers(3)); - } - } else { - int animAttr = 0; - switch (transit) { - case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_TASK_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_TASK_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation; - break; - } - a = animAttr != 0 ? loadAnimation(atoken.userId, lp, animAttr) : null; - if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, - "applyAnimation: atoken=" + atoken - + " anim=" + a - + " animAttr=0x" + Integer.toHexString(animAttr) - + " transit=" + transit + " isEntrance=" + enter - + " Callers=" + Debug.getCallers(3)); - } + DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); + final int width = displayInfo.appWidth; + final int height = displayInfo.appHeight; + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation: atoken=" + + atoken); + Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height); if (a != null) { if (DEBUG_ANIM) { RuntimeException e = null; @@ -3596,7 +3091,7 @@ public class WindowManagerService extends IWindowManager.Stub } Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e); } - atoken.mAppAnimator.setAnimation(a, initialized); + atoken.mAppAnimator.setAnimation(a, width, height); } } else { atoken.mAppAnimator.clearAnimation(); @@ -3685,7 +3180,6 @@ public class WindowManagerService extends IWindowManager.Stub mTokenMap.put(token, wtoken); if (type == TYPE_WALLPAPER) { mWallpaperTokens.add(wtoken); - updateLayoutToAnimWallpaperTokens(); } } } @@ -3716,8 +3210,12 @@ public class WindowManagerService extends IWindowManager.Stub if (win.isVisibleNow()) { win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false); - scheduleNotifyWindowTranstionIfNeededLocked(win, - WindowManagerPolicy.TRANSIT_EXIT); + //TODO (multidisplay): Magnification is supported only for the default + if (mDisplayMagnifier != null + && win.getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onWindowTransitionLocked(win, + WindowManagerPolicy.TRANSIT_EXIT); + } changed = true; win.mDisplayContent.layoutNeeded = true; } @@ -3735,7 +3233,6 @@ public class WindowManagerService extends IWindowManager.Stub mExitingTokens.add(wtoken); } else if (wtoken.windowType == TYPE_WALLPAPER) { mWallpaperTokens.remove(wtoken); - updateLayoutToAnimWallpaperTokens(); } } @@ -3766,7 +3263,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void addAppToken(int addPos, int userId, IApplicationToken token, + public void addAppToken(int addPos, IApplicationToken token, int groupId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addAppToken()")) { @@ -3793,7 +3290,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Attempted to add existing app token: " + token); return; } - atoken = new AppWindowToken(this, userId, token); + atoken = new AppWindowToken(this, token); atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; atoken.groupId = groupId; atoken.appFullscreen = fullscreen; @@ -4130,26 +3627,26 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Prepare app transition: transit=" + transit - + " mNextAppTransition=" + mNextAppTransition + + " " + mAppTransition + " alwaysKeepCurrent=" + alwaysKeepCurrent + " Callers=" + Debug.getCallers(3)); if (okToDisplay()) { - if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET - || mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) { - mNextAppTransition = transit; + if (!mAppTransition.isTransitionSet() || mAppTransition.isTransitionNone()) { + mAppTransition.setAppTransition(transit); } else if (!alwaysKeepCurrent) { if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN - && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) { + && mAppTransition.isTransitionEqual( + WindowManagerPolicy.TRANSIT_TASK_CLOSE)) { // Opening a new task always supersedes a close for the anim. - mNextAppTransition = transit; + mAppTransition.setAppTransition(transit); } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) { + && mAppTransition.isTransitionEqual( + WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE)) { // Opening a new activity always supersedes a close for the anim. - mNextAppTransition = transit; + mAppTransition.setAppTransition(transit); } } - mAppTransitionReady = false; - mAppTransitionTimeout = false; + mAppTransition.prepare(); mStartingIconInTransition = false; mSkipAppTransitionAnimation = false; mH.removeMessages(H.APP_TRANSITION_TIMEOUT); @@ -4160,30 +3657,15 @@ public class WindowManagerService extends IWindowManager.Stub @Override public int getPendingAppTransition() { - return mNextAppTransition; - } - - private void scheduleAnimationCallback(IRemoteCallback cb) { - if (cb != null) { - mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, cb)); - } + return mAppTransition.getAppTransition(); } @Override public void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, IRemoteCallback startedCallback) { synchronized(mWindowMap) { - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { - mNextAppTransitionType = ActivityOptions.ANIM_CUSTOM; - mNextAppTransitionPackage = packageName; - mNextAppTransitionThumbnail = null; - mNextAppTransitionEnter = enterAnim; - mNextAppTransitionExit = exitAnim; - scheduleAnimationCallback(mNextAppTransitionCallback); - mNextAppTransitionCallback = startedCallback; - } else { - scheduleAnimationCallback(startedCallback); - } + mAppTransition.overridePendingAppTransition(packageName, enterAnim, exitAnim, + startedCallback); } } @@ -4191,17 +3673,8 @@ public class WindowManagerService extends IWindowManager.Stub public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth, int startHeight) { synchronized(mWindowMap) { - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { - mNextAppTransitionType = ActivityOptions.ANIM_SCALE_UP; - mNextAppTransitionPackage = null; - mNextAppTransitionThumbnail = null; - mNextAppTransitionStartX = startX; - mNextAppTransitionStartY = startY; - mNextAppTransitionStartWidth = startWidth; - mNextAppTransitionStartHeight = startHeight; - scheduleAnimationCallback(mNextAppTransitionCallback); - mNextAppTransitionCallback = null; - } + mAppTransition.overridePendingAppTransitionScaleUp(startX, startY, startWidth, + startHeight); } } @@ -4209,19 +3682,8 @@ public class WindowManagerService extends IWindowManager.Stub public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY, IRemoteCallback startedCallback, boolean scaleUp) { synchronized(mWindowMap) { - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { - mNextAppTransitionType = scaleUp - ? ActivityOptions.ANIM_THUMBNAIL_SCALE_UP : ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; - mNextAppTransitionPackage = null; - mNextAppTransitionThumbnail = srcThumb; - mNextAppTransitionScaleUp = scaleUp; - mNextAppTransitionStartX = startX; - mNextAppTransitionStartY = startY; - scheduleAnimationCallback(mNextAppTransitionCallback); - mNextAppTransitionCallback = startedCallback; - } else { - scheduleAnimationCallback(startedCallback); - } + mAppTransition.overridePendingAppTransitionThumb(srcThumb, startX, startY, + startedCallback, scaleUp); } } @@ -4236,11 +3698,10 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS) { RuntimeException e = new RuntimeException("here"); e.fillInStackTrace(); - Slog.w(TAG, "Execute app transition: mNextAppTransition=" - + mNextAppTransition, e); + Slog.w(TAG, "Execute app transition: " + mAppTransition, e); } - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { - mAppTransitionReady = true; + if (mAppTransition.isTransitionSet()) { + mAppTransition.setReady(); final long origId = Binder.clearCallingIdentity(); performLayoutAndPlaceSurfacesLocked(); Binder.restoreCallingIdentity(origId); @@ -4331,6 +3792,7 @@ public class WindowManagerService extends IWindowManager.Stub // the new one. if (ttoken.allDrawn) { wtoken.allDrawn = true; + wtoken.deferClearAllDrawn = ttoken.deferClearAllDrawn; } if (ttoken.firstWindowDrawn) { wtoken.firstWindowDrawn = true; @@ -4409,8 +3871,8 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Checking theme of starting window: 0x" + Integer.toHexString(theme)); if (theme != 0) { - AttributeCache.Entry ent = AttributeCache.instance().get(wtoken.userId, - pkg, theme, com.android.internal.R.styleable.Window); + AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, + com.android.internal.R.styleable.Window); if (ent == null) { // Whoops! App doesn't exist. Um. Okay. We'll just // pretend like we didn't see that. @@ -4461,6 +3923,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void setAppWillBeHidden(IBinder token) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppWillBeHidden()")) { @@ -4505,8 +3968,10 @@ public class WindowManagerService extends IWindowManager.Stub delayed = runningAppAnimation = true; } WindowState window = wtoken.findMainWindow(); - if (window != null) { - scheduleNotifyWindowTranstionIfNeededLocked(window, transit); + //TODO (multidisplay): Magnification is supported only for the default display. + if (window != null && mDisplayMagnifier != null + && window.getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onWindowTransitionLocked(window, transit); } changed = true; } @@ -4525,8 +3990,12 @@ public class WindowManagerService extends IWindowManager.Stub if (!runningAppAnimation) { win.mWinAnimator.applyAnimationLocked( WindowManagerPolicy.TRANSIT_ENTER, true); - scheduleNotifyWindowTranstionIfNeededLocked(win, - WindowManagerPolicy.TRANSIT_ENTER); + //TODO (multidisplay): Magnification is supported only for the default + if (mDisplayMagnifier != null + && win.getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onWindowTransitionLocked(win, + WindowManagerPolicy.TRANSIT_ENTER); + } } changed = true; win.mDisplayContent.layoutNeeded = true; @@ -4535,8 +4004,12 @@ public class WindowManagerService extends IWindowManager.Stub if (!runningAppAnimation) { win.mWinAnimator.applyAnimationLocked( WindowManagerPolicy.TRANSIT_EXIT, false); - scheduleNotifyWindowTranstionIfNeededLocked(win, - WindowManagerPolicy.TRANSIT_EXIT); + //TODO (multidisplay): Magnification is supported only for the default + if (mDisplayMagnifier != null + && win.getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onWindowTransitionLocked(win, + WindowManagerPolicy.TRANSIT_EXIT); + } } changed = true; win.mDisplayContent.layoutNeeded = true; @@ -4584,6 +4057,7 @@ public class WindowManagerService extends IWindowManager.Stub return delayed; } + @Override public void setAppVisibility(IBinder token, boolean visible) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppVisibility()")) { @@ -4606,14 +4080,14 @@ public class WindowManagerService extends IWindowManager.Stub e.fillInStackTrace(); } Slog.v(TAG, "setAppVisibility(" + token + ", visible=" + visible - + "): mNextAppTransition=" + mNextAppTransition + + "): " + mAppTransition + " hidden=" + wtoken.hidden + " hiddenRequested=" + wtoken.hiddenRequested, e); } // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. - if (okToDisplay() && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + if (okToDisplay() && mAppTransition.isTransitionSet()) { // Already in requested state, don't do anything more. if (wtoken.hiddenRequested != visible) { return; @@ -4638,6 +4112,7 @@ public class WindowManagerService extends IWindowManager.Stub // its windows to be ready. if (wtoken.hidden) { wtoken.allDrawn = false; + wtoken.deferClearAllDrawn = false; wtoken.waitingToShow = true; if (wtoken.clientHidden) { @@ -4735,6 +4210,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void startAppFreezingScreen(IBinder token, int configChanges) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppFreezingScreen()")) { @@ -4758,6 +4234,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void stopAppFreezingScreen(IBinder token, boolean force) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppFreezingScreen()")) { @@ -4777,6 +4254,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void removeAppToken(IBinder token) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "removeAppToken()")) { @@ -4799,7 +4277,7 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.waitingToShow = false; if (mClosingApps.contains(wtoken)) { delayed = true; - } else if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + } else if (mAppTransition.isTransitionSet()) { mClosingApps.add(wtoken); wtoken.waitingToHide = true; delayed = true; @@ -4995,6 +4473,7 @@ public class WindowManagerService extends IWindowManager.Stub return index; } + @Override public void moveAppToken(int index, IBinder token) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "moveAppToken()")) { @@ -5009,8 +4488,8 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, "Start moving token " + wtoken + " initially at " + oldIndex); - if (oldIndex > index && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET - && !mAppTransitionRunning) { + if (oldIndex > index && mAppTransition.isTransitionSet() + && !mAppTransition.isRunning()) { // animation towards back has not started, copy old list for duration of animation. mAnimatingAppTokens.clear(); mAnimatingAppTokens.addAll(mAppTokens); @@ -5024,7 +4503,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_REORDER) Slog.v(TAG, "Moved " + token + " to " + index + ":"); else if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "Moved " + token + " to " + index); if (DEBUG_REORDER) dumpAppTokensLocked(); - if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET && !mAppTransitionRunning) { + if (!mAppTransition.isTransitionSet() && !mAppTransition.isRunning()) { // Not animating, bring animating app list in line with mAppTokens. mAnimatingAppTokens.clear(); mAnimatingAppTokens.addAll(mAppTokens); @@ -5172,13 +4651,13 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, "Adding next to top: " + wt); mAppTokens.add(wt); - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + if (mAppTransition.isTransitionSet()) { wt.sendingToBottom = false; } } } - if (!mAppTransitionRunning) { + if (!mAppTransition.isRunning()) { mAnimatingAppTokens.clear(); mAnimatingAppTokens.addAll(mAppTokens); moveAppWindowsLocked(tokens, mAppTokens.size()); @@ -5197,7 +4676,7 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { final int N = tokens.size(); - if (N > 0 && !mAppTransitionRunning) { + if (N > 0 && !mAppTransition.isRunning()) { // animating towards back, hang onto old list for duration of animation. mAnimatingAppTokens.clear(); mAnimatingAppTokens.addAll(mAppTokens); @@ -5210,14 +4689,14 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "Adding next to bottom: " + wt + " at " + pos); mAppTokens.add(pos, wt); - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + if (mAppTransition.isTransitionSet()) { wt.sendingToBottom = true; } pos++; } } - if (!mAppTransitionRunning) { + if (!mAppTransition.isRunning()) { mAnimatingAppTokens.clear(); mAnimatingAppTokens.addAll(mAppTokens); moveAppWindowsLocked(tokens, 0); @@ -5616,7 +5095,7 @@ public class WindowManagerService extends IWindowManager.Stub if (!mSystemBooted && !haveBootMsg) { return; } - + // If we are turning on the screen after the boot is completed // normally, don't do so until we have the application and // wallpaper. @@ -5766,7 +5245,7 @@ public class WindowManagerService extends IWindowManager.Stub * Takes a snapshot of the screen. In landscape mode this grabs the whole screen. * In portrait mode, it grabs the upper region of the screen based on the vertical dimension * of the target image. - * + * * @param displayId the Display to take a screenshot of. * @param width the width of the target bitmap * @param height the height of the target bitmap @@ -5810,64 +5289,50 @@ public class WindowManagerService extends IWindowManager.Stub // Figure out the part of the screen that is actually the app. boolean including = false; final WindowList windows = displayContent.getWindowList(); - try { - Surface.openTransaction(); - for (int i = windows.size() - 1; i >= 0; i--) { - WindowState ws = windows.get(i); - if (!ws.mHasSurface) { - continue; - } - if (ws.mLayer >= aboveAppLayer) { - continue; - } - // When we will skip windows: when we are not including - // ones behind a window we didn't skip, and we are actually - // taking a screenshot of a specific app. - if (!including && appToken != null) { - // Also, we can possibly skip this window if it is not - // an IME target or the application for the screenshot - // is not the current IME target. - if (!ws.mIsImWindow || !isImeTarget) { - // And finally, this window is of no interest if it - // is not associated with the screenshot app. - if (ws.mAppToken == null || ws.mAppToken.token != appToken) { - continue; - } + for (int i = windows.size() - 1; i >= 0; i--) { + WindowState ws = windows.get(i); + if (!ws.mHasSurface) { + continue; + } + if (ws.mLayer >= aboveAppLayer) { + continue; + } + // When we will skip windows: when we are not including + // ones behind a window we didn't skip, and we are actually + // taking a screenshot of a specific app. + if (!including && appToken != null) { + // Also, we can possibly skip this window if it is not + // an IME target or the application for the screenshot + // is not the current IME target. + if (!ws.mIsImWindow || !isImeTarget) { + // And finally, this window is of no interest if it + // is not associated with the screenshot app. + if (ws.mAppToken == null || ws.mAppToken.token != appToken) { + continue; } } + } - // We keep on including windows until we go past a full-screen - // window. - including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh); - - final WindowStateAnimator winAnimator = ws.mWinAnimator; - - // The setSize() method causes all previous Surface transactions to sync to - // the SurfaceFlinger. This will force any outstanding setLayer calls to be - // synced as well for screen capture. Without this we can get black bitmaps. - Surface surface = winAnimator.mSurface; - surface.setSize(surface.getWidth(), surface.getHeight()); - + // We keep on including windows until we go past a full-screen + // window. + including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh); - if (maxLayer < winAnimator.mSurfaceLayer) { - maxLayer = winAnimator.mSurfaceLayer; - } + if (maxLayer < ws.mWinAnimator.mSurfaceLayer) { + maxLayer = ws.mWinAnimator.mSurfaceLayer; + } - // Don't include wallpaper in bounds calculation - if (!ws.mIsWallpaper) { - final Rect wf = ws.mFrame; - final Rect cr = ws.mContentInsets; - int left = wf.left + cr.left; - int top = wf.top + cr.top; - int right = wf.right - cr.right; - int bottom = wf.bottom - cr.bottom; - frame.union(left, top, right, bottom); - } + // Don't include wallpaper in bounds calculation + if (!ws.mIsWallpaper) { + final Rect wf = ws.mFrame; + final Rect cr = ws.mContentInsets; + int left = wf.left + cr.left; + int top = wf.top + cr.top; + int right = wf.right - cr.right; + int bottom = wf.bottom - cr.bottom; + frame.union(left, top, right, bottom); } - } finally { - Surface.closeTransaction(); - Binder.restoreCallingIdentity(ident); } + Binder.restoreCallingIdentity(ident); // Constrain frame to the screen size. frame.intersect(0, 0, dw, dh); @@ -6137,7 +5602,7 @@ public class WindowManagerService extends IWindowManager.Stub rotation, mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, displayInfo.logicalWidth, displayInfo.logicalHeight)) { - updateLayoutToAnimationLocked(); + scheduleAnimationLocked(); } } @@ -6168,7 +5633,11 @@ public class WindowManagerService extends IWindowManager.Stub } } - scheduleNotifyRotationChangedIfNeededLocked(displayContent, rotation); + //TODO (multidisplay): Magnification is supported only for the default display. + if (mDisplayMagnifier != null + && displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onRotationChangedLocked(getDefaultDisplayContentLocked(), rotation); + } return true; } @@ -6550,146 +6019,6 @@ public class WindowManagerService extends IWindowManager.Stub return success; } - @Override - public void addDisplayContentChangeListener(int displayId, - IDisplayContentChangeListener listener) { - if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, - "addDisplayContentChangeListener()")) { - throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission"); - } - synchronized(mWindowMap) { - DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { - if (displayContent.mDisplayContentChangeListeners == null) { - displayContent.mDisplayContentChangeListeners = - new RemoteCallbackList<IDisplayContentChangeListener>(); - displayContent.mDisplayContentChangeListeners.register(listener); - } - } - } - } - - @Override - public void removeDisplayContentChangeListener(int displayId, - IDisplayContentChangeListener listener) { - if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, - "removeDisplayContentChangeListener()")) { - throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission"); - } - synchronized(mWindowMap) { - DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { - if (displayContent.mDisplayContentChangeListeners != null) { - displayContent.mDisplayContentChangeListeners.unregister(listener); - if (displayContent.mDisplayContentChangeListeners - .getRegisteredCallbackCount() == 0) { - displayContent.mDisplayContentChangeListeners = null; - } - } - } - } - } - - void scheduleNotifyWindowTranstionIfNeededLocked(WindowState window, int transition) { - DisplayContent displayContent = window.mDisplayContent; - if (displayContent.mDisplayContentChangeListeners != null) { - WindowInfo info = getWindowInfoForWindowStateLocked(window); - mH.obtainMessage(H.NOTIFY_WINDOW_TRANSITION, transition, 0, info).sendToTarget(); - } - } - - private void handleNotifyWindowTranstion(int transition, WindowInfo info) { - RemoteCallbackList<IDisplayContentChangeListener> callbacks = null; - synchronized (mWindowMap) { - DisplayContent displayContent = getDisplayContentLocked(info.displayId); - if (displayContent == null) { - return; - } - callbacks = displayContent.mDisplayContentChangeListeners; - if (callbacks == null) { - return; - } - } - final int callbackCount = callbacks.beginBroadcast(); - try { - for (int i = 0; i < callbackCount; i++) { - try { - callbacks.getBroadcastItem(i).onWindowTransition(info.displayId, - transition, info); - } catch (RemoteException re) { - /* ignore */ - } - } - } finally { - callbacks.finishBroadcast(); - } - } - - private void scheduleNotifyRotationChangedIfNeededLocked(DisplayContent displayContent, - int rotation) { - if (displayContent.mDisplayContentChangeListeners != null - && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) { - mH.obtainMessage(H.NOTIFY_ROTATION_CHANGED, displayContent.getDisplayId(), - rotation).sendToTarget(); - } - } - - private void handleNotifyRotationChanged(int displayId, int rotation) { - RemoteCallbackList<IDisplayContentChangeListener> callbacks = null; - synchronized (mWindowMap) { - DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent == null) { - return; - } - callbacks = displayContent.mDisplayContentChangeListeners; - if (callbacks == null) { - return; - } - } - try { - final int watcherCount = callbacks.beginBroadcast(); - for (int i = 0; i < watcherCount; i++) { - try { - callbacks.getBroadcastItem(i).onRotationChanged(rotation); - } catch (RemoteException re) { - /* ignore */ - } - } - } finally { - callbacks.finishBroadcast(); - } - } - - private void scheduleNotifyWindowLayersChangedIfNeededLocked(DisplayContent displayContent) { - if (displayContent.mDisplayContentChangeListeners != null - && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) { - mH.obtainMessage(H.NOTIFY_WINDOW_LAYERS_CHANGED, displayContent) .sendToTarget(); - } - } - - private void handleNotifyWindowLayersChanged(DisplayContent displayContent) { - RemoteCallbackList<IDisplayContentChangeListener> callbacks = null; - synchronized (mWindowMap) { - callbacks = displayContent.mDisplayContentChangeListeners; - if (callbacks == null) { - return; - } - } - try { - final int watcherCount = callbacks.beginBroadcast(); - for (int i = 0; i < watcherCount; i++) { - try { - callbacks.getBroadcastItem(i).onWindowLayersChanged( - displayContent.getDisplayId()); - } catch (RemoteException re) { - /* ignore */ - } - } - } finally { - callbacks.finishBroadcast(); - } - } - public void addWindowChangeListener(WindowChangeListener listener) { synchronized(mWindowMap) { mWindowChangeListeners.add(listener); @@ -6940,8 +6269,6 @@ public class WindowManagerService extends IWindowManager.Stub displayInfo.getAppMetrics(mDisplayMetrics, null); mDisplayManagerService.setDisplayInfoOverrideFromWindowManager( displayContent.getDisplayId(), displayInfo); - - mAnimator.setDisplayDimensions(dw, dh, appWidth, appHeight); } if (false) { Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight); @@ -7246,11 +6573,6 @@ public class WindowManagerService extends IWindowManager.Stub mIsTouchDevice = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN); - final DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); - mAnimator.setDisplayDimensions( - displayInfo.logicalWidth, displayInfo.logicalHeight, - displayInfo.appWidth, displayInfo.appHeight); - mPolicy.setInitialDisplaySize(displayContent.getDisplay(), displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight, @@ -7330,23 +6652,17 @@ public class WindowManagerService extends IWindowManager.Stub public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22; public static final int BOOT_TIMEOUT = 23; public static final int WAITING_FOR_DRAWN_TIMEOUT = 24; - public static final int UPDATE_ANIM_PARAMETERS = 25; - public static final int SHOW_STRICT_MODE_VIOLATION = 26; - public static final int DO_ANIMATION_CALLBACK = 27; - public static final int NOTIFY_ROTATION_CHANGED = 28; - public static final int NOTIFY_WINDOW_TRANSITION = 29; - public static final int NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 30; - public static final int NOTIFY_WINDOW_LAYERS_CHANGED = 31; + public static final int SHOW_STRICT_MODE_VIOLATION = 25; + public static final int DO_ANIMATION_CALLBACK = 26; - public static final int DO_DISPLAY_ADDED = 32; - public static final int DO_DISPLAY_REMOVED = 33; - public static final int DO_DISPLAY_CHANGED = 34; + public static final int DO_DISPLAY_ADDED = 27; + public static final int DO_DISPLAY_REMOVED = 28; + public static final int DO_DISPLAY_CHANGED = 29; - public static final int CLIENT_FREEZE_TIMEOUT = 35; + public static final int CLIENT_FREEZE_TIMEOUT = 30; public static final int ANIMATOR_WHAT_OFFSET = 100000; public static final int SET_TRANSPARENT_REGION = ANIMATOR_WHAT_OFFSET + 1; - public static final int CLEAR_PENDING_ACTIONS = ANIMATOR_WHAT_OFFSET + 2; public H() { } @@ -7601,11 +6917,11 @@ public class WindowManagerService extends IWindowManager.Stub case APP_TRANSITION_TIMEOUT: { synchronized (mWindowMap) { - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + if (mAppTransition.isTransitionSet()) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** APP TRANSITION TIMEOUT"); - mAppTransitionReady = true; - mAppTransitionTimeout = true; + mAppTransition.setReady(); + mAppTransition.setTimeout(true); mAnimatingAppTokens.clear(); mAnimatingAppTokens.addAll(mAppTokens); performLayoutAndPlaceSurfacesLocked(); @@ -7626,20 +6942,18 @@ public class WindowManagerService extends IWindowManager.Stub case FORCE_GC: { synchronized (mWindowMap) { - synchronized (mAnimator) { - // Since we're holding both mWindowMap and mAnimator we don't need to - // hold mAnimator.mLayoutToAnim. - if (mAnimator.mAnimating || mLayoutToAnim.mAnimationScheduled) { - // If we are animating, don't do the gc now but - // delay a bit so we don't interrupt the animation. - sendEmptyMessageDelayed(H.FORCE_GC, 2000); - return; - } - // If we are currently rotating the display, it will - // schedule a new message when done. - if (mDisplayFrozen) { - return; - } + // Since we're holding both mWindowMap and mAnimator we don't need to + // hold mAnimator.mLayoutToAnim. + if (mAnimator.mAnimating || mAnimationScheduled) { + // If we are animating, don't do the gc now but + // delay a bit so we don't interrupt the animation. + sendEmptyMessageDelayed(H.FORCE_GC, 2000); + return; + } + // If we are currently rotating the display, it will + // schedule a new message when done. + if (mDisplayFrozen) { + return; } } Runtime.getRuntime().gc(); @@ -7653,16 +6967,14 @@ public class WindowManagerService extends IWindowManager.Stub case APP_FREEZE_TIMEOUT: { synchronized (mWindowMap) { - synchronized (mAnimator) { - Slog.w(TAG, "App freeze timeout expired."); - int i = mAppTokens.size(); - while (i > 0) { - i--; - AppWindowToken tok = mAppTokens.get(i); - if (tok.mAppAnimator.freezingScreen) { - Slog.w(TAG, "Force clearing freeze: " + tok); - unsetAppFreezingScreenLocked(tok, true, true); - } + Slog.w(TAG, "App freeze timeout expired."); + int i = mAppTokens.size(); + while (i > 0) { + i--; + AppWindowToken tok = mAppTokens.get(i); + if (tok.mAppAnimator.freezingScreen) { + Slog.w(TAG, "Force clearing freeze: " + tok); + unsetAppFreezingScreenLocked(tok, true, true); } } } @@ -7753,17 +7065,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case UPDATE_ANIM_PARAMETERS: { - // Used to send multiple changes from the animation side to the layout side. - synchronized (mWindowMap) { - if (copyAnimToLayoutParamsLocked()) { - sendEmptyMessage(CLEAR_PENDING_ACTIONS); - performLayoutAndPlaceSurfacesLocked(); - } - } - break; - } - case SHOW_STRICT_MODE_VIOLATION: { showStrictModeViolation(msg.arg1, msg.arg2); break; @@ -7778,11 +7079,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case CLEAR_PENDING_ACTIONS: { - mAnimator.clearPendingActions(); - break; - } - case DO_ANIMATION_CALLBACK: { try { ((IRemoteCallback)msg.obj).sendResult(null); @@ -7791,34 +7087,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case NOTIFY_ROTATION_CHANGED: { - final int displayId = msg.arg1; - final int rotation = msg.arg2; - handleNotifyRotationChanged(displayId, rotation); - break; - } - - case NOTIFY_WINDOW_TRANSITION: { - final int transition = msg.arg1; - WindowInfo info = (WindowInfo) msg.obj; - handleNotifyWindowTranstion(transition, info); - break; - } - - case NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: { - final int displayId = msg.arg1; - final boolean immediate = (msg.arg2 == 1); - Rect rectangle = (Rect) msg.obj; - handleNotifyRectangleOnScreenRequested(displayId, rectangle, immediate); - break; - } - - case NOTIFY_WINDOW_LAYERS_CHANGED: { - DisplayContent displayContent = (DisplayContent) msg.obj; - handleNotifyWindowLayersChanged(displayContent); - break; - } - case DO_DISPLAY_ADDED: synchronized (mWindowMap) { handleDisplayAddedLocked(msg.arg1); @@ -8189,7 +7457,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.flush(); Slog.w(TAG, "This window was lost: " + ws); Slog.w(TAG, sw.toString()); - ws.mWinAnimator.destroySurfaceLocked(false); + ws.mWinAnimator.destroySurfaceLocked(); } } Slog.w(TAG, "Current app token list:"); @@ -8251,7 +7519,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (layerChanged && mAnimator.isDimmingLocked(winAnimator)) { // Force an animation pass just to update the mDimAnimator layer. - updateLayoutToAnimationLocked(); + scheduleAnimationLocked(); } if (DEBUG_LAYERS) Slog.v(TAG, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer @@ -8263,8 +7531,10 @@ public class WindowManagerService extends IWindowManager.Stub // "Assigned layer " + curLayer + " to " + w.mClient.asBinder()); } - if (anyLayerChanged) { - scheduleNotifyWindowLayersChangedIfNeededLocked(getDefaultDisplayContentLocked()); + //TODO (multidisplay): Magnification is supported only for the default display. + if (mDisplayMagnifier != null && anyLayerChanged + && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) { + mDisplayMagnifier.onWindowLayersChangedLocked(); } } @@ -8276,6 +7546,7 @@ public class WindowManagerService extends IWindowManager.Stub mH.removeMessages(H.DO_TRAVERSAL); loopCount--; } while (mTraversalScheduled && loopCount > 0); + mInnerFields.mWallpaperActionPending = false; } private boolean mInLayout = false; @@ -8436,8 +7707,7 @@ public class WindowManagerService extends IWindowManager.Stub // windows, since that means "perform layout as normal, // just don't display"). if (!gone || !win.mHaveFrame || win.mLayoutNeeded - || ((win.mAttrs.type == TYPE_KEYGUARD || win.mAttrs.type == TYPE_WALLPAPER) && - win.isConfigChanged()) + || (win.mAttrs.type == TYPE_KEYGUARD && win.isConfigChanged()) || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) { if (!win.mLayoutAttached) { if (initial) { @@ -8561,8 +7831,8 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Checking " + NN + " opening apps (frozen=" + mDisplayFrozen + " timeout=" - + mAppTransitionTimeout + ")..."); - if (!mDisplayFrozen && !mAppTransitionTimeout) { + + mAppTransition.isTimeout() + ")..."); + if (!mDisplayFrozen && !mAppTransition.isTimeout()) { // If the display isn't frozen, wait to do anything until // all of the apps are ready. Otherwise just go because // we'll unfreeze the display when everyone is ready. @@ -8581,14 +7851,11 @@ public class WindowManagerService extends IWindowManager.Stub } if (goodToGo) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO"); - int transit = mNextAppTransition; + int transit = mAppTransition.getAppTransition(); if (mSkipAppTransitionAnimation) { transit = WindowManagerPolicy.TRANSIT_UNSET; } - mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; - mAppTransitionReady = false; - mAppTransitionRunning = true; - mAppTransitionTimeout = false; + mAppTransition.goodToGo(); mStartingIconInTransition = false; mSkipAppTransitionAnimation = false; @@ -8602,7 +7869,6 @@ public class WindowManagerService extends IWindowManager.Stub && !mWallpaperTarget.mWinAnimator.isDummyAnimation() ? null : mWallpaperTarget; - adjustWallpaperWindowsLocked(); mInnerFields.mWallpaperMayChange = false; // The top-most window will supply the layout params, @@ -8616,7 +7882,18 @@ public class WindowManagerService extends IWindowManager.Stub + ", oldWallpaper=" + oldWallpaper + ", lower target=" + mLowerWallpaperTarget + ", upper target=" + mUpperWallpaperTarget); - int foundWallpapers = 0; + + boolean openingAppHasWallpaper = false; + boolean closingAppHasWallpaper = false; + final AppWindowToken lowerWallpaperAppToken; + final AppWindowToken upperWallpaperAppToken; + if (mLowerWallpaperTarget == null) { + lowerWallpaperAppToken = upperWallpaperAppToken = null; + } else { + lowerWallpaperAppToken = mLowerWallpaperTarget.mAppToken; + upperWallpaperAppToken = mUpperWallpaperTarget.mAppToken; + } + // Do a first pass through the tokens for two // things: // (1) Determine if both the closing and opening @@ -8630,21 +7907,19 @@ public class WindowManagerService extends IWindowManager.Stub final int NC = mClosingApps.size(); NN = NC + mOpeningApps.size(); for (i=0; i<NN; i++) { - AppWindowToken wtoken; - int mode; + final AppWindowToken wtoken; if (i < NC) { wtoken = mClosingApps.get(i); - mode = 1; + if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) { + closingAppHasWallpaper = true; + } } else { - wtoken = mOpeningApps.get(i-NC); - mode = 2; - } - if (mLowerWallpaperTarget != null) { - if (mLowerWallpaperTarget.mAppToken == wtoken - || mUpperWallpaperTarget.mAppToken == wtoken) { - foundWallpapers |= mode; + wtoken = mOpeningApps.get(i - NC); + if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) { + openingAppHasWallpaper = true; } } + if (wtoken.appFullscreen) { WindowState ws = wtoken.findMainWindow(); if (ws != null) { @@ -8663,9 +7938,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (foundWallpapers == 3) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Wallpaper animation!"); + if (closingAppHasWallpaper && openingAppHasWallpaper) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!"); switch (transit) { case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: case WindowManagerPolicy.TRANSIT_TASK_OPEN: @@ -8678,8 +7952,7 @@ public class WindowManagerService extends IWindowManager.Stub transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE; break; } - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New transit: " + transit); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit: " + transit); } else if ((oldWallpaper != null) && !mOpeningApps.contains(oldWallpaper.mAppToken)) { // We are transitioning from an activity with // a wallpaper to one without. @@ -8755,14 +8028,18 @@ public class WindowManagerService extends IWindowManager.Stub // this guy's animations regardless of whether it's // gotten drawn. wtoken.allDrawn = true; + wtoken.deferClearAllDrawn = false; } - if (mNextAppTransitionThumbnail != null && topOpeningApp != null - && topOpeningApp.mAppAnimator.animation != null) { + AppWindowAnimator appAnimator = + topOpeningApp == null ? null : topOpeningApp.mAppAnimator; + Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail(); + if (nextAppTransitionThumbnail != null && appAnimator != null + && appAnimator.animation != null) { // This thumbnail animation is very special, we need to have // an extra surface with the thumbnail included with the animation. - Rect dirty = new Rect(0, 0, mNextAppTransitionThumbnail.getWidth(), - mNextAppTransitionThumbnail.getHeight()); + Rect dirty = new Rect(0, 0, nextAppTransitionThumbnail.getWidth(), + nextAppTransitionThumbnail.getHeight()); try { // TODO(multi-display): support other displays final DisplayContent displayContent = getDefaultDisplayContentLocked(); @@ -8772,35 +8049,34 @@ public class WindowManagerService extends IWindowManager.Stub dirty.width(), dirty.height(), PixelFormat.TRANSLUCENT, Surface.HIDDEN); surface.setLayerStack(display.getLayerStack()); - topOpeningApp.mAppAnimator.thumbnail = surface; - if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL " - + surface + ": CREATE"); + appAnimator.thumbnail = surface; + if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL " + surface + ": CREATE"); Surface drawSurface = new Surface(); drawSurface.copyFrom(surface); Canvas c = drawSurface.lockCanvas(dirty); - c.drawBitmap(mNextAppTransitionThumbnail, 0, 0, null); + c.drawBitmap(nextAppTransitionThumbnail, 0, 0, null); drawSurface.unlockCanvasAndPost(c); drawSurface.release(); - topOpeningApp.mAppAnimator.thumbnailLayer = topOpeningLayer; - Animation anim = createThumbnailAnimationLocked( - transit, true, true, mNextAppTransitionScaleUp); - topOpeningApp.mAppAnimator.thumbnailAnimation = anim; + appAnimator.thumbnailLayer = topOpeningLayer; + DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); + Animation anim = mAppTransition.createThumbnailAnimationLocked( + transit, true, true, displayInfo.appWidth, displayInfo.appHeight); + appAnimator.thumbnailAnimation = anim; anim.restrictDuration(MAX_ANIMATION_DURATION); anim.scaleCurrentDuration(mTransitionAnimationScale); - topOpeningApp.mAppAnimator.thumbnailX = mNextAppTransitionStartX; - topOpeningApp.mAppAnimator.thumbnailY = mNextAppTransitionStartY; + Point p = new Point(); + mAppTransition.getStartingPoint(p); + appAnimator.thumbnailX = p.x; + appAnimator.thumbnailY = p.y; } catch (Surface.OutOfResourcesException e) { Slog.e(TAG, "Can't allocate thumbnail surface w=" + dirty.width() + " h=" + dirty.height(), e); - topOpeningApp.mAppAnimator.clearThumbnail(); + appAnimator.clearThumbnail(); } } - mNextAppTransitionType = ActivityOptions.ANIM_NONE; - mNextAppTransitionPackage = null; - mNextAppTransitionThumbnail = null; - scheduleAnimationCallback(mNextAppTransitionCallback); - mNextAppTransitionCallback = null; + mAppTransition.postAnimationCallback(); + mAppTransition.clear(); mOpeningApps.clear(); mClosingApps.clear(); @@ -8830,7 +8106,7 @@ public class WindowManagerService extends IWindowManager.Stub private int handleAnimatingStoppedAndTransitionLocked() { int changes = 0; - mAppTransitionRunning = false; + mAppTransition.setRunning(false); // Restore window app tokens to the ActivityManager views for (int i = mAnimatingAppTokens.size() - 1; i >= 0; i--) { mAnimatingAppTokens.get(i).sendingToBottom = false; @@ -8840,7 +8116,8 @@ public class WindowManagerService extends IWindowManager.Stub rebuildAppWindowListLocked(); changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - mInnerFields.mAdjResult |= ADJUST_WALLPAPER_LAYERS_CHANGED; + if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, + "Wallpaper layer changed: assigning layers + relayout"); moveInputMethodWindowsIfNeededLocked(true); mInnerFields.mWallpaperMayChange = true; // Since the window list has been rebuilt, focus might @@ -8851,37 +8128,6 @@ public class WindowManagerService extends IWindowManager.Stub return changes; } - /** - * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. - * - * @return bitmap indicating if another pass through layout must be made. - */ - private int animateAwayWallpaperLocked() { - int changes = 0; - WindowState oldWallpaper = mWallpaperTarget; - if (mLowerWallpaperTarget != null - && mLowerWallpaperTarget.mAppToken != null) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "wallpaperForceHiding changed with lower=" - + mLowerWallpaperTarget); - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "hidden=" + mLowerWallpaperTarget.mAppToken.hidden + - " hiddenRequested=" + mLowerWallpaperTarget.mAppToken.hiddenRequested); - if (mLowerWallpaperTarget.mAppToken.hidden) { - // The lower target has become hidden before we - // actually started the animation... let's completely - // re-evaluate everything. - mLowerWallpaperTarget = mUpperWallpaperTarget = null; - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; - } - } - mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked(); - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "****** OLD: " + oldWallpaper - + " NEW: " + mWallpaperTarget - + " LOWER: " + mLowerWallpaperTarget); - return changes; - } - private void updateResizingWindows(final WindowState w) { final WindowStateAnimator winAnimator = w.mWinAnimator; if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq) { @@ -8905,7 +8151,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_RESIZE || DEBUG_ORIENTATION) { Slog.v(TAG, "Resize reasons: " + " contentInsetsChanged=" + w.mContentInsetsChanged + + " " + w.mContentInsets.toShortString() + " visibleInsetsChanged=" + w.mVisibleInsetsChanged + + " " + w.mVisibleInsets.toShortString() + " surfaceResized=" + winAnimator.mSurfaceResized + " configChanged=" + configChanged); } @@ -8925,6 +8173,7 @@ public class WindowManagerService extends IWindowManager.Stub winAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING; if (w.mAppToken != null) { w.mAppToken.allDrawn = false; + w.mAppToken.deferClearAllDrawn = false; } } if (!mResizingWindows.contains(w)) { @@ -9000,27 +8249,29 @@ public class WindowManagerService extends IWindowManager.Stub // so we want to leave all of them as undimmed (for // performance reasons). mInnerFields.mObscured = true; - } else if (canBeSeen && (attrFlags & FLAG_DIM_BEHIND) != 0 - && !(w.mAppToken != null && w.mAppToken.hiddenRequested) + } + } + + private void handleFlagDimBehind(WindowState w, int innerDw, int innerDh) { + final WindowManager.LayoutParams attrs = w.mAttrs; + if ((attrs.flags & FLAG_DIM_BEHIND) != 0 + && w.isDisplayedLw() && !w.mExiting) { - if (localLOGV) Slog.v(TAG, "Win " + w + " obscured=" + mInnerFields.mObscured); - if (!mInnerFields.mDimming) { - //Slog.i(TAG, "DIM BEHIND: " + w); - mInnerFields.mDimming = true; - final WindowStateAnimator winAnimator = w.mWinAnimator; - if (!mAnimator.isDimmingLocked(winAnimator)) { - final int width, height; - if (attrs.type == TYPE_BOOT_PROGRESS) { - final DisplayInfo displayInfo = w.mDisplayContent.getDisplayInfo(); - width = displayInfo.logicalWidth; - height = displayInfo.logicalHeight; - } else { - width = innerDw; - height = innerDh; - } - startDimmingLocked( - winAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount, width, height); + mInnerFields.mDimming = true; + final WindowStateAnimator winAnimator = w.mWinAnimator; + if (!mAnimator.isDimmingLocked(winAnimator)) { + final int width, height; + if (attrs.type == TYPE_BOOT_PROGRESS) { + final DisplayInfo displayInfo = w.mDisplayContent.getDisplayInfo(); + width = displayInfo.logicalWidth; + height = displayInfo.logicalHeight; + } else { + width = innerDw; + height = innerDh; } + if (localLOGV) Slog.v(TAG, "Win " + w + " start dimming."); + startDimmingLocked( + winAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount, width, height); } } } @@ -9129,10 +8380,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner", displayContent.pendingLayoutChanges); - if (isDefaultDisplay && ((displayContent.pendingLayoutChanges - & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) - && ((adjustWallpaperWindowsLocked() - & ADJUST_WALLPAPER_LAYERS_CHANGED) != 0)) { + if ((adjustWallpaperWindowsLocked() & ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { assignLayersLocked(windows); displayContent.layoutNeeded = true; } @@ -9199,6 +8447,10 @@ public class WindowManagerService extends IWindowManager.Stub handleNotObscuredLocked(w, currentTime, innerDw, innerDh); } + if (!mInnerFields.mDimming) { + handleFlagDimBehind(w, innerDw, innerDh); + } + if (isDefaultDisplay && obscuredChanged && (mWallpaperTarget == w) && w.isVisibleLw()) { // This is the wallpaper target and its obscured state @@ -9368,15 +8620,13 @@ public class WindowManagerService extends IWindowManager.Stub // If we are ready to perform an app transition, check through // all of the app tokens to be shown and see if they are ready // to go. - if (mAppTransitionReady) { + if (mAppTransition.isReady()) { defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows); if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked", - defaultDisplay.pendingLayoutChanges); + defaultDisplay.pendingLayoutChanges); } - mInnerFields.mAdjResult = 0; - - if (!mAnimator.mAnimating && mAppTransitionRunning) { + if (!mAnimator.mAnimating && mAppTransition.isRunning()) { // We have finished the animation of an app transition. To do // this, we have delayed a lot of operations like showing and // hiding apps, moving apps in Z-order, etc. The app token list @@ -9389,14 +8639,14 @@ public class WindowManagerService extends IWindowManager.Stub } if (mInnerFields.mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0 - && !mAppTransitionReady) { + && !mAppTransition.isReady()) { // At this point, there was a window with a wallpaper that // was force hiding other windows behind it, but now it // is going away. This may be simple -- just animate // away the wallpaper and its window -- or it may be // hard -- the wallpaper now needs to be shown behind // something that was hidden. - defaultDisplay.pendingLayoutChanges |= animateAwayWallpaperLocked(); + defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animateAwayWallpaperLocked", defaultDisplay.pendingLayoutChanges); } @@ -9405,18 +8655,10 @@ public class WindowManagerService extends IWindowManager.Stub if (mInnerFields.mWallpaperMayChange) { if (WindowManagerService.DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting"); - mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked(); - } - - if ((mInnerFields.mAdjResult&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Wallpaper layer changed: assigning layers + relayout"); - defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; - assignLayersLocked(defaultWindows); - } else if ((mInnerFields.mAdjResult&ADJUST_WALLPAPER_VISIBILITY_CHANGED) != 0) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Wallpaper visibility changed: relayout"); - defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; + defaultDisplay.pendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("WallpaperMayChange", + defaultDisplay.pendingLayoutChanges); } if (mFocusMayChange) { @@ -9424,7 +8666,6 @@ public class WindowManagerService extends IWindowManager.Stub if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, false /*updateInputWindows*/)) { defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; - mInnerFields.mAdjResult = 0; } } @@ -9494,7 +8735,7 @@ public class WindowManagerService extends IWindowManager.Stub if (win == mWallpaperTarget) { wallpaperDestroyed = true; } - win.mWinAnimator.destroySurfaceLocked(false); + win.mWinAnimator.destroySurfaceLocked(); } while (i > 0); mDestroySurface.clear(); } @@ -9506,7 +8747,6 @@ public class WindowManagerService extends IWindowManager.Stub mExitingTokens.remove(i); if (token.windowType == TYPE_WALLPAPER) { mWallpaperTokens.remove(token); - updateLayoutToAnimWallpaperTokens(); } } } @@ -9538,8 +8778,10 @@ public class WindowManagerService extends IWindowManager.Stub mRelayoutWhileAnimating.clear(); } - if (wallpaperDestroyed && (adjustWallpaperWindowsLocked() != 0)) { - getDefaultDisplayContentLocked().layoutNeeded = true; + if (wallpaperDestroyed) { + defaultDisplay.pendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + defaultDisplay.layoutNeeded = true; } DisplayContentsIterator iterator = new DisplayContentsIterator(); @@ -9617,7 +8859,7 @@ public class WindowManagerService extends IWindowManager.Stub // be enabled, because the window obscured flags have changed. enableScreenIfNeededLocked(); - updateLayoutToAnimationLocked(); + scheduleAnimationLocked(); if (DEBUG_WINDOW_TRACE) { Slog.e(TAG, "performLayoutAndPlaceSurfacesLockedInner exit: animating=" @@ -9714,82 +8956,21 @@ public class WindowManagerService extends IWindowManager.Stub /** Note that Locked in this case is on mLayoutToAnim */ void scheduleAnimationLocked() { - final LayoutToAnimatorParams layoutToAnim = mLayoutToAnim; - if (!layoutToAnim.mAnimationScheduled) { - layoutToAnim.mAnimationScheduled = true; + if (!mAnimationScheduled) { + mAnimationScheduled = true; mChoreographer.postCallback( Choreographer.CALLBACK_ANIMATION, mAnimator.mAnimationRunnable, null); } } - void updateLayoutToAnimationLocked() { - final LayoutToAnimatorParams layoutToAnim = mLayoutToAnim; - synchronized (layoutToAnim) { - // Copy local params to transfer params. - SparseArray<WinAnimatorList> allWinAnimatorLists = layoutToAnim.mWinAnimatorLists; - allWinAnimatorLists.clear(); - DisplayContentsIterator iterator = new DisplayContentsIterator(); - while (iterator.hasNext()) { - final DisplayContent displayContent = iterator.next(); - WinAnimatorList winAnimatorList = new WinAnimatorList(); - final WindowList windows = displayContent.getWindowList(); - int N = windows.size(); - for (int i = 0; i < N; i++) { - final WindowStateAnimator winAnimator = windows.get(i).mWinAnimator; - if (winAnimator.mSurface != null) { - winAnimatorList.add(winAnimator); - } - } - allWinAnimatorLists.put(displayContent.getDisplayId(), winAnimatorList); - } - - if (WindowManagerService.DEBUG_WALLPAPER_LIGHT) { - if (mWallpaperTarget != layoutToAnim.mWallpaperTarget - || mLowerWallpaperTarget != layoutToAnim.mLowerWallpaperTarget - || mUpperWallpaperTarget != layoutToAnim.mUpperWallpaperTarget) { - Slog.d(TAG, "Pushing anim wallpaper: target=" + mWallpaperTarget - + " lower=" + mLowerWallpaperTarget + " upper=" - + mUpperWallpaperTarget + "\n" + Debug.getCallers(5, " ")); - } - } - layoutToAnim.mWallpaperTarget = mWallpaperTarget; - layoutToAnim.mLowerWallpaperTarget = mLowerWallpaperTarget; - layoutToAnim.mUpperWallpaperTarget = mUpperWallpaperTarget; - - final ArrayList<AppWindowAnimParams> paramList = layoutToAnim.mAppWindowAnimParams; - paramList.clear(); - int N = mAnimatingAppTokens.size(); - for (int i = 0; i < N; i++) { - paramList.add(new AppWindowAnimParams(mAnimatingAppTokens.get(i).mAppAnimator)); - } - - layoutToAnim.mParamsModified = true; - scheduleAnimationLocked(); - } - } - - void updateLayoutToAnimWallpaperTokens() { - synchronized(mLayoutToAnim) { - mLayoutToAnim.mWallpaperTokens = new ArrayList<WindowToken>(mWallpaperTokens); - mLayoutToAnim.mChanges |= LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED; - } - } - - void setAnimDimParams(int displayId, DimAnimator.Parameters params) { - synchronized (mLayoutToAnim) { - mLayoutToAnim.mDimParams.put(displayId, params); - scheduleAnimationLocked(); - } - } - void startDimmingLocked(final WindowStateAnimator winAnimator, final float target, final int width, final int height) { - setAnimDimParams(winAnimator.mWin.getDisplayId(), + mAnimator.setDimParamsLocked(winAnimator.mWin.getDisplayId(), new DimAnimator.Parameters(winAnimator, width, height, target)); } void stopDimmingLocked(int displayId) { - setAnimDimParams(displayId, null); + mAnimator.setDimParamsLocked(displayId, null); } private boolean needsLayout() { @@ -9802,53 +8983,39 @@ public class WindowManagerService extends IWindowManager.Stub return false; } - private boolean copyAnimToLayoutParamsLocked() { + boolean copyAnimToLayoutParamsLocked() { boolean doRequest = false; - final WindowAnimator.AnimatorToLayoutParams animToLayout = mAnimator.mAnimToLayout; - synchronized (animToLayout) { - animToLayout.mUpdateQueued = false; - final int bulkUpdateParams = animToLayout.mBulkUpdateParams; - // TODO(cmautner): As the number of bits grows, use masks of bit groups to - // eliminate unnecessary tests. - if ((bulkUpdateParams & LayoutFields.SET_UPDATE_ROTATION) != 0) { - mInnerFields.mUpdateRotation = true; - doRequest = true; - } - if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) { - mInnerFields.mWallpaperMayChange = true; - doRequest = true; - } - if ((bulkUpdateParams & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) { - mInnerFields.mWallpaperForceHidingChanged = true; - doRequest = true; - } - if ((bulkUpdateParams & LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE) == 0) { - mInnerFields.mOrientationChangeComplete = false; - } else { - mInnerFields.mOrientationChangeComplete = true; - if (mWindowsFreezingScreen) { - doRequest = true; - } - } - if ((bulkUpdateParams & LayoutFields.SET_TURN_ON_SCREEN) != 0) { - mTurnOnScreen = true; - } - SparseIntArray pendingLayouts = animToLayout.mPendingLayoutChanges; - final int count = pendingLayouts.size(); - if (count > 0) { + final int bulkUpdateParams = mAnimator.mBulkUpdateParams; + // TODO(cmautner): As the number of bits grows, use masks of bit groups to + // eliminate unnecessary tests. + if ((bulkUpdateParams & LayoutFields.SET_UPDATE_ROTATION) != 0) { + mInnerFields.mUpdateRotation = true; + doRequest = true; + } + if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) { + mInnerFields.mWallpaperMayChange = true; + doRequest = true; + } + if ((bulkUpdateParams & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) { + mInnerFields.mWallpaperForceHidingChanged = true; + doRequest = true; + } + if ((bulkUpdateParams & LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE) == 0) { + mInnerFields.mOrientationChangeComplete = false; + } else { + mInnerFields.mOrientationChangeComplete = true; + if (mWindowsFreezingScreen) { doRequest = true; } - for (int i = 0; i < count; ++i) { - final DisplayContent displayContent = - getDisplayContentLocked(pendingLayouts.keyAt(i)); - if (displayContent != null) { - displayContent.pendingLayoutChanges |= pendingLayouts.valueAt(i); - } - } - - mWindowDetachedWallpaper = animToLayout.mWindowDetachedWallpaper; } + if ((bulkUpdateParams & LayoutFields.SET_TURN_ON_SCREEN) != 0) { + mTurnOnScreen = true; + } + if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_ACTION_PENDING) != 0) { + mInnerFields.mWallpaperActionPending = true; + } + return doRequest; } @@ -10012,7 +9179,7 @@ public class WindowManagerService extends IWindowManager.Stub } return false; } - + private void finishUpdateFocusedWindowAfterAssignLayersLocked(boolean updateInputWindows) { mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows); } @@ -10119,12 +9286,10 @@ public class WindowManagerService extends IWindowManager.Stub // the screen then the whole world is changing behind the scenes. mPolicy.setLastInputMethodWindowLw(null, null); - if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { - mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; - mNextAppTransitionType = ActivityOptions.ANIM_NONE; - mNextAppTransitionPackage = null; - mNextAppTransitionThumbnail = null; - mAppTransitionReady = true; + if (mAppTransition.isTransitionSet()) { + mAppTransition.setAppTransition(WindowManagerPolicy.TRANSIT_UNSET); + mAppTransition.clear(); + mAppTransition.setReady(); } if (PROFILE_ORIENTATION) { @@ -10166,7 +9331,7 @@ public class WindowManagerService extends IWindowManager.Stub + ", mClientFreezingScreen=" + mClientFreezingScreen); return; } - + mDisplayFrozen = false; mH.removeMessages(H.APP_FREEZE_TIMEOUT); mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT); @@ -10188,7 +9353,7 @@ public class WindowManagerService extends IWindowManager.Stub if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, displayInfo.logicalWidth, displayInfo.logicalHeight)) { - updateLayoutToAnimationLocked(); + scheduleAnimationLocked(); } else { screenRotationAnimation.kill(); screenRotationAnimation = null; @@ -10384,14 +9549,17 @@ public class WindowManagerService extends IWindowManager.Stub return mPolicy.hasNavigationBar(); } + @Override public void lockNow(Bundle options) { mPolicy.lockNow(options); } + @Override public boolean isSafeModeEnabled() { return mSafeMode; } + @Override public void showAssistant() { // TODO: What permission? if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) @@ -10496,7 +9664,7 @@ public class WindowManagerService extends IWindowManager.Stub } } } - if (mAppTransitionRunning && mAnimatingAppTokens.size() > 0) { + if (mAppTransition.isRunning() && mAnimatingAppTokens.size() > 0) { pw.println(); pw.println(" Application tokens during animation:"); for (int i=mAnimatingAppTokens.size()-1; i>=0; i--) { @@ -10727,76 +9895,11 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); pw.print(" mTransitionWindowAnimationScale="); pw.print(mTransitionAnimationScale); pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale); - pw.print(" mTraversalScheduled="); pw.print(mTraversalScheduled); - pw.print(" mNextAppTransition=0x"); - pw.print(Integer.toHexString(mNextAppTransition)); - pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady); - pw.print(" mAppTransitionRunning="); pw.print(mAppTransitionRunning); - pw.print(" mAppTransitionTimeout="); pw.println(mAppTransitionTimeout); - if (mNextAppTransitionType != ActivityOptions.ANIM_NONE) { - pw.print(" mNextAppTransitionType="); pw.println(mNextAppTransitionType); - } - switch (mNextAppTransitionType) { - case ActivityOptions.ANIM_CUSTOM: - pw.print(" mNextAppTransitionPackage="); - pw.println(mNextAppTransitionPackage); - pw.print(" mNextAppTransitionEnter=0x"); - pw.print(Integer.toHexString(mNextAppTransitionEnter)); - pw.print(" mNextAppTransitionExit=0x"); - pw.println(Integer.toHexString(mNextAppTransitionExit)); - break; - case ActivityOptions.ANIM_SCALE_UP: - pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX); - pw.print(" mNextAppTransitionStartY="); - pw.println(mNextAppTransitionStartY); - pw.print(" mNextAppTransitionStartWidth="); - pw.print(mNextAppTransitionStartWidth); - pw.print(" mNextAppTransitionStartHeight="); - pw.println(mNextAppTransitionStartHeight); - break; - case ActivityOptions.ANIM_THUMBNAIL_SCALE_UP: - case ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN: - pw.print(" mNextAppTransitionThumbnail="); - pw.print(mNextAppTransitionThumbnail); - pw.print(" mNextAppTransitionStartX="); - pw.print(mNextAppTransitionStartX); - pw.print(" mNextAppTransitionStartY="); - pw.println(mNextAppTransitionStartY); - pw.print(" mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp); - break; - } - if (mNextAppTransitionCallback != null) { - pw.print(" mNextAppTransitionCallback="); - pw.println(mNextAppTransitionCallback); - } + pw.print(" mTraversalScheduled="); pw.println(mTraversalScheduled); pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition); pw.print(" mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation); pw.println(" mLayoutToAnim:"); - pw.print(" mParamsModified="); pw.print(mLayoutToAnim.mParamsModified); - pw.print(" mAnimationScheduled="); pw.print(mLayoutToAnim.mAnimationScheduled); - pw.print(" mChanges=0x"); - pw.println(Long.toHexString(mLayoutToAnim.mChanges)); - pw.print(" mWallpaperTarget="); pw.println(mLayoutToAnim.mWallpaperTarget); - if (mLayoutToAnim.mLowerWallpaperTarget != null - || mLayoutToAnim.mUpperWallpaperTarget != null) { - pw.print(" mLowerWallpaperTarget="); - pw.println(mLayoutToAnim.mLowerWallpaperTarget); - pw.print(" mUpperWallpaperTarget="); - pw.println(mLayoutToAnim.mUpperWallpaperTarget); - } - for (int i=0; i<mLayoutToAnim.mWinAnimatorLists.size(); i++) { - pw.print(" Win Animator List #"); - pw.print(mLayoutToAnim.mWinAnimatorLists.keyAt(i)); pw.println(":"); - WinAnimatorList wanim = mLayoutToAnim.mWinAnimatorLists.valueAt(i); - for (int wi=0; wi<wanim.size(); wi++) { - pw.print(" "); pw.println(wanim.get(wi)); - } - } - for (int i=0; i<mLayoutToAnim.mWallpaperTokens.size(); i++) { - pw.print(" Wallpaper Token #"); pw.print(i); pw.print(": "); - pw.println(mLayoutToAnim.mWallpaperTokens.get(i)); - } - // XXX also need to print mDimParams and mAppWindowAnimParams. I am lazy. + mAppTransition.dump(pw); } } @@ -11006,6 +10109,7 @@ public class WindowManagerService extends IWindowManager.Stub } // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection). + @Override public void monitor() { synchronized (mWindowMap) { } } @@ -11147,7 +10251,16 @@ public class WindowManagerService extends IWindowManager.Stub * @return The list of WindowStates on the screen, or null if the there is no screen. */ public WindowList getWindowListLocked(final Display display) { - final DisplayContent displayContent = getDisplayContentLocked(display.getDisplayId()); + return getWindowListLocked(display.getDisplayId()); + } + + /** + * Return the list of WindowStates associated on the passed display. + * @param displayId The screen to return windows from. + * @return The list of WindowStates on the screen, or null if the there is no screen. + */ + public WindowList getWindowListLocked(final int displayId) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); return displayContent != null ? displayContent.getWindowList() : null; } diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 81eac20..e0dad01b 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -58,7 +58,7 @@ class WindowList extends ArrayList<WindowState> { */ final class WindowState implements WindowManagerPolicy.WindowState { static final String TAG = "WindowState"; - + static final boolean DEBUG_VISIBILITY = WindowManagerService.DEBUG_VISIBILITY; static final boolean SHOW_TRANSACTIONS = WindowManagerService.SHOW_TRANSACTIONS; static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS; @@ -250,7 +250,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { // Input channel and input window handle used by the input dispatcher. final InputWindowHandle mInputWindowHandle; InputChannel mInputChannel; - + // Used to improve performance of toString() String mStringNameCache; CharSequence mLastTitle; @@ -517,21 +517,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } - MagnificationSpec getWindowMagnificationSpecLocked() { - MagnificationSpec spec = mDisplayContent.mMagnificationSpec; - if (spec != null && !spec.isNop()) { - if (mAttachedWindow != null) { - if (!mPolicy.canMagnifyWindowLw(mAttachedWindow.mAttrs)) { - return null; - } - } - if (!mPolicy.canMagnifyWindowLw(mAttrs)) { - return null; - } - } - return spec; - } - @Override public Rect getFrameLw() { return mFrame; @@ -677,7 +662,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { @Override public boolean isVisibleOrBehindKeyguardLw() { if (mRootToken.waitingToShow && - mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mService.mAppTransition.isTransitionSet()) { return false; } final AppWindowToken atoken = mAppToken; @@ -745,7 +730,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { final AppWindowToken atoken = mAppToken; if (atoken != null) { return ((!mAttachedHidden && !atoken.hiddenRequested) - || mWinAnimator.mAnimation != null || atoken.mAppAnimator.animation != null); + || mWinAnimator.mAnimation != null || atoken.mAppAnimator.animation != null); } return !mAttachedHidden || mWinAnimator.mAnimation != null; } @@ -756,7 +741,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ boolean isReadyForDisplay() { if (mRootToken.waitingToShow && - mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mService.mAppTransition.isTransitionSet()) { return false; } return mHasSurface && mPolicyVisibility && !mDestroying @@ -771,8 +756,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { * to the keyguard. */ boolean isReadyForDisplayIgnoringKeyguard() { - if (mRootToken.waitingToShow && - mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + if (mRootToken.waitingToShow && mService.mAppTransition.isTransitionSet()) { return false; } final AppWindowToken atoken = mAppToken; @@ -804,12 +788,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { } /** - * Return true if this window (or a window it is attached to, but not - * considering its app token) is currently animating. + * Return true if this window or its app token is currently animating. */ @Override public boolean isAnimatingLw() { - return mWinAnimator.mAnimation != null; + return mWinAnimator.mAnimation != null + || (mAppToken != null && mAppToken.mAppAnimator.animation != null); } @Override @@ -827,6 +811,17 @@ final class WindowState implements WindowManagerPolicy.WindowState { * Returns true if the window has a surface that it has drawn a * complete UI in to. */ + public boolean isDrawFinishedLw() { + return mHasSurface && !mDestroying && + (mWinAnimator.mDrawState == WindowStateAnimator.COMMIT_DRAW_PENDING + || mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW + || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN); + } + + /** + * Returns true if the window has a surface that it has drawn a + * complete UI in to. + */ public boolean isDrawnLw() { return mHasSurface && !mDestroying && (mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW @@ -883,13 +878,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { void removeLocked() { disposeInputChannel(); - + if (mAttachedWindow != null) { if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + this + " from " + mAttachedWindow); mAttachedWindow.mChildWindows.remove(this); } - mWinAnimator.destroyDeferredSurfaceLocked(false); - mWinAnimator.destroySurfaceLocked(false); + mWinAnimator.destroyDeferredSurfaceLocked(); + mWinAnimator.destroySurfaceLocked(); mSession.windowRemovedLocked(); try { mClient.asBinder().unlinkToDeath(mDeathRecipient, 0); @@ -916,7 +911,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { void disposeInputChannel() { if (mInputChannel != null) { mService.mInputManager.unregisterInputChannel(mInputChannel); - + mInputChannel.dispose(); mInputChannel = null; } @@ -990,7 +985,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true); } if (requestAnim) { - mService.updateLayoutToAnimationLocked(); + mService.scheduleAnimationLocked(); } return true; } @@ -1033,7 +1028,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } if (requestAnim) { - mService.updateLayoutToAnimationLocked(); + mService.scheduleAnimationLocked(); } return true; } @@ -1252,7 +1247,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); } } - + String makeInputChannelName() { return Integer.toHexString(System.identityHashCode(this)) + " " + mAttrs.getTitle(); diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index 10784fe..9acdd49 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -14,9 +14,10 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.os.Debug; -import android.os.UserHandle; import android.util.Slog; +import android.view.Display; import android.view.DisplayInfo; +import android.view.MagnificationSpec; import android.view.Surface; import android.view.SurfaceSession; import android.view.WindowManager; @@ -226,7 +227,7 @@ class WindowStateAnimator { mAnimation.cancel(); mAnimation = null; mLocalAnimating = false; - destroySurfaceLocked(true); + destroySurfaceLocked(); } } @@ -369,7 +370,7 @@ class WindowStateAnimator { final int displayId = mWin.mDisplayContent.getDisplayId(); mAnimator.setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) mService.debugLayoutRepeats( - "WindowStateAnimator", mAnimator.mPendingLayoutChanges.get(displayId)); + "WindowStateAnimator", mAnimator.getPendingLayoutChanges(displayId)); if (mWin.mAppToken != null) { mWin.mAppToken.updateReportedVisibilityLocked(); @@ -413,7 +414,7 @@ class WindowStateAnimator { mService.mPendingRemove.add(mWin); mWin.mRemoveOnExit = false; } - mAnimator.hideWallpapersLocked(mWin, true); + mAnimator.hideWallpapersLocked(mWin); } void hide() { @@ -627,7 +628,14 @@ class WindowStateAnimator { "createSurface " + this + ": mDrawState=DRAW_PENDING"); mDrawState = DRAW_PENDING; if (mWin.mAppToken != null) { - mWin.mAppToken.allDrawn = false; + if (mWin.mAppToken.mAppAnimator.animation == null) { + mWin.mAppToken.allDrawn = false; + mWin.mAppToken.deferClearAllDrawn = false; + } else { + // Currently animating, persist current state of allDrawn until animation + // is complete. + mWin.mAppToken.deferClearAllDrawn = true; + } } mService.makeWindowFreezingScreenIfNeededLocked(mWin); @@ -745,7 +753,7 @@ class WindowStateAnimator { return mSurface; } - void destroySurfaceLocked(boolean fromAnimator) { + void destroySurfaceLocked() { if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) { mWin.mAppToken.startingDisplayed = false; } @@ -795,7 +803,7 @@ class WindowStateAnimator { } mSurface.destroy(); } - mAnimator.hideWallpapersLocked(mWin, fromAnimator); + mAnimator.hideWallpapersLocked(mWin); } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying Window " + this + " surface " + mSurface + " session " + mSession @@ -809,7 +817,7 @@ class WindowStateAnimator { } } - void destroyDeferredSurfaceLocked(boolean fromAnimator) { + void destroyDeferredSurfaceLocked() { try { if (mPendingDestroySurface != null) { if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { @@ -821,7 +829,7 @@ class WindowStateAnimator { WindowManagerService.logSurface(mWin, "DESTROY PENDING", e); } mPendingDestroySurface.destroy(); - mAnimator.hideWallpapersLocked(mWin, fromAnimator); + mAnimator.hideWallpapersLocked(mWin); } } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying Window " @@ -842,9 +850,9 @@ class WindowStateAnimator { // Wallpapers are animated based on the "real" window they // are currently targeting. - if (mIsWallpaper && mAnimator.mLowerWallpaperTarget == null - && mAnimator.mWallpaperTarget != null) { - final WindowStateAnimator wallpaperAnimator = mAnimator.mWallpaperTarget.mWinAnimator; + if (mIsWallpaper && mService.mLowerWallpaperTarget == null + && mService.mWallpaperTarget != null) { + final WindowStateAnimator wallpaperAnimator = mService.mWallpaperTarget.mWinAnimator; if (wallpaperAnimator.mHasLocalTransformation && wallpaperAnimator.mAnimation != null && !wallpaperAnimator.mAnimation.getDetachWallpaper()) { @@ -853,7 +861,7 @@ class WindowStateAnimator { Slog.v(TAG, "WP target attached xform: " + attachedTransformation); } } - final AppWindowAnimator wpAppAnimator = mAnimator.mWpAppAnimator; + final AppWindowAnimator wpAppAnimator = mAnimator.getWallpaperAppAnimator(); if (wpAppAnimator != null && wpAppAnimator.hasTransformation && wpAppAnimator.animation != null && !wpAppAnimator.animation.getDetachWallpaper()) { @@ -912,10 +920,15 @@ class WindowStateAnimator { if (screenAnimation) { tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix()); } - MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked(); - if (spec != null && !spec.isNop()) { - tmpMatrix.postScale(spec.mScale, spec.mScale); - tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY); + //TODO (multidisplay): Magnification is supported only for the default display. + if (mService.mDisplayMagnifier != null + && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) { + MagnificationSpec spec = mService.mDisplayMagnifier + .getMagnificationSpecForWindowLocked(mWin); + if (spec != null && !spec.isNop()) { + tmpMatrix.postScale(spec.scale, spec.scale); + tmpMatrix.postTranslate(spec.offsetX, spec.offsetY); + } } // "convert" it into SurfaceFlinger's format @@ -976,8 +989,7 @@ class WindowStateAnimator { + " screen=" + (screenAnimation ? screenRotationAnimation.getEnterTransformation().getAlpha() : "null")); return; - } else if (mIsWallpaper && - (mAnimator.mPendingActions & WindowAnimator.WALLPAPER_ACTION_PENDING) != 0) { + } else if (mIsWallpaper && mService.mInnerFields.mWallpaperActionPending) { return; } @@ -988,7 +1000,11 @@ class WindowStateAnimator { final boolean applyUniverseTransformation = (mAnimator.mUniverseBackground != null && mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer); - MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked(); + MagnificationSpec spec = null; + //TODO (multidisplay): Magnification is supported only for the default display. + if (mService.mDisplayMagnifier != null && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) { + spec = mService.mDisplayMagnifier.getMagnificationSpecForWindowLocked(mWin); + } if (applyUniverseTransformation || spec != null) { final Rect frame = mWin.mFrame; final float tmpFloats[] = mService.mTmpFloats; @@ -1002,8 +1018,8 @@ class WindowStateAnimator { } if (spec != null && !spec.isNop()) { - tmpMatrix.postScale(spec.mScale, spec.mScale); - tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY); + tmpMatrix.postScale(spec.scale, spec.scale); + tmpMatrix.postTranslate(spec.offsetX, spec.offsetY); } tmpMatrix.getValues(tmpFloats); @@ -1209,7 +1225,7 @@ class WindowStateAnimator { hide(); } else if (w.mAttachedHidden || !w.isReadyForDisplay()) { hide(); - mAnimator.hideWallpapersLocked(w, true); + mAnimator.hideWallpapersLocked(w); // If we are waiting for this window to handle an // orientation change, well, it is hidden, so @@ -1403,7 +1419,7 @@ class WindowStateAnimator { if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this); mDrawState = HAS_DRAWN; - mService.updateLayoutToAnimationLocked(); + mService.scheduleAnimationLocked(); int i = mWin.mChildWindows.size(); while (i > 0) { @@ -1488,7 +1504,11 @@ class WindowStateAnimator { transit = WindowManagerPolicy.TRANSIT_SHOW; } applyAnimationLocked(transit, true); - mService.scheduleNotifyWindowTranstionIfNeededLocked(mWin, transit); + //TODO (multidisplay): Magnification is supported only for the default display. + if (mService.mDisplayMagnifier != null + && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) { + mService.mDisplayMagnifier.onWindowTransitionLocked(mWin, transit); + } } // TODO(cmautner): Move back to WindowState? @@ -1534,8 +1554,7 @@ class WindowStateAnimator { break; } if (attr >= 0) { - a = mService.loadAnimation(UserHandle.getUserId(mWin.mOwnerUid), - mWin.mAttrs, attr); + a = mService.mAppTransition.loadAnimation(mWin.mAttrs, attr); } } if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, |