summaryrefslogtreecommitdiffstats
path: root/services/java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java')
-rw-r--r--services/java/com/android/server/AttributeCache.java51
-rw-r--r--services/java/com/android/server/ConnectivityService.java36
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java4
-rw-r--r--services/java/com/android/server/LockSettingsService.java408
-rw-r--r--services/java/com/android/server/NetworkManagementService.java6
-rw-r--r--services/java/com/android/server/NetworkTimeUpdateService.java51
-rw-r--r--services/java/com/android/server/SerialService.java7
-rw-r--r--services/java/com/android/server/SystemServer.java9
-rw-r--r--services/java/com/android/server/WifiService.java83
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityInputFilter.java121
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java240
-rw-r--r--services/java/com/android/server/accessibility/ScreenMagnifier.java1317
-rw-r--r--services/java/com/android/server/accounts/AccountAuthenticatorCache.java94
-rw-r--r--services/java/com/android/server/accounts/AccountManagerService.java2558
-rw-r--r--services/java/com/android/server/accounts/IAccountAuthenticatorCache.java67
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java8
-rw-r--r--services/java/com/android/server/am/ActivityRecord.java2
-rw-r--r--services/java/com/android/server/am/ActivityStack.java24
-rw-r--r--services/java/com/android/server/content/ContentService.java848
-rw-r--r--services/java/com/android/server/content/SyncManager.java2786
-rw-r--r--services/java/com/android/server/content/SyncOperation.java224
-rw-r--r--services/java/com/android/server/content/SyncQueue.java225
-rw-r--r--services/java/com/android/server/content/SyncStorageEngine.java2303
-rw-r--r--services/java/com/android/server/display/LocalDisplayAdapter.java57
-rw-r--r--services/java/com/android/server/location/GpsLocationProvider.java13
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java7
-rw-r--r--services/java/com/android/server/os/SchedulingPolicyService.java64
-rw-r--r--services/java/com/android/server/pm/Installer.java39
-rw-r--r--services/java/com/android/server/pm/PackageManagerService.java19
-rw-r--r--services/java/com/android/server/pm/Settings.java6
-rw-r--r--services/java/com/android/server/power/PowerManagerService.java99
-rw-r--r--services/java/com/android/server/search/SearchManagerService.java299
-rw-r--r--services/java/com/android/server/search/Searchables.java464
-rw-r--r--services/java/com/android/server/wm/AppTransition.java629
-rw-r--r--services/java/com/android/server/wm/AppWindowAnimator.java29
-rw-r--r--services/java/com/android/server/wm/AppWindowToken.java14
-rw-r--r--services/java/com/android/server/wm/DimSurface.java11
-rw-r--r--services/java/com/android/server/wm/DisplayContent.java11
-rw-r--r--services/java/com/android/server/wm/DisplayMagnifier.java732
-rw-r--r--services/java/com/android/server/wm/MagnificationSpec.java46
-rw-r--r--services/java/com/android/server/wm/WindowAnimator.java382
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java1709
-rw-r--r--services/java/com/android/server/wm/WindowState.java59
-rw-r--r--services/java/com/android/server/wm/WindowStateAnimator.java73
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,