diff options
Diffstat (limited to 'core/java')
105 files changed, 3154 insertions, 1973 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index a463a62..9ebbe03 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -104,7 +104,7 @@ import com.android.internal.os.HandlerCaller; * </ul> * <h3>Retrieving window content</h3> * <p> - * An service can specify in its declaration that it can retrieve the active window + * A service can specify in its declaration that it can retrieve the active window * content which is represented as a tree of {@link AccessibilityNodeInfo}. Note that * declaring this capability requires that the service declares its configuration via * an XML resource referenced by {@link #SERVICE_META_DATA}. diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index eae0a4c..b55fda4 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -503,26 +503,38 @@ public class AccessibilityServiceInfo implements Parcelable { public static String feedbackTypeToString(int feedbackType) { StringBuilder builder = new StringBuilder(); builder.append("["); - while (feedbackType > 0) { + while (feedbackType != 0) { final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType); feedbackType &= ~feedbackTypeFlag; - if (builder.length() > 1) { - builder.append(", "); - } switch (feedbackTypeFlag) { case FEEDBACK_AUDIBLE: + if (builder.length() > 1) { + builder.append(", "); + } builder.append("FEEDBACK_AUDIBLE"); break; case FEEDBACK_HAPTIC: + if (builder.length() > 1) { + builder.append(", "); + } builder.append("FEEDBACK_HAPTIC"); break; case FEEDBACK_GENERIC: + if (builder.length() > 1) { + builder.append(", "); + } builder.append("FEEDBACK_GENERIC"); break; case FEEDBACK_SPOKEN: + if (builder.length() > 1) { + builder.append(", "); + } builder.append("FEEDBACK_SPOKEN"); break; case FEEDBACK_VISUAL: + if (builder.length() > 1) { + builder.append(", "); + } builder.append("FEEDBACK_VISUAL"); break; } diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java index 7183267..e9535ab 100644 --- a/core/java/android/accounts/AbstractAccountAuthenticator.java +++ b/core/java/android/accounts/AbstractAccountAuthenticator.java @@ -59,7 +59,7 @@ import java.util.Arrays; * "Account & Sync" settings page and one user of the android:smallIcon is the Contact Application's * tab panels. * <p> - * The preferences attribute points to an PreferenceScreen xml hierarchy that contains + * The preferences attribute points to a PreferenceScreen xml hierarchy that contains * a list of PreferenceScreens that can be invoked to manage the authenticator. An example is: * <pre> * <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 5fee4de..adc7d35 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -18,6 +18,7 @@ package android.accounts; import android.Manifest; import android.app.ActivityManager; +import android.app.AppGlobals; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -33,6 +34,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.RegisteredServicesCache; import android.content.pm.RegisteredServicesCacheListener; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.Cursor; import android.database.DatabaseUtils; @@ -48,11 +50,14 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import com.android.internal.R; +import com.android.internal.util.IndentingPrintWriter; import java.io.File; import java.io.FileDescriptor; @@ -62,6 +67,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -94,7 +100,6 @@ public class AccountManagerService private static final int MESSAGE_TIMED_OUT = 3; private final IAccountAuthenticatorCache mAuthenticatorCache; - private final DatabaseHelper mOpenHelper; private static final String TABLE_ACCOUNTS = "accounts"; private static final String ACCOUNTS_ID = "_id"; @@ -148,14 +153,36 @@ public class AccountManagerService private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>(); private final AtomicInteger mNotificationIds = new AtomicInteger(1); - private final HashMap<Pair<Pair<Account, String>, Integer>, Integer> - mCredentialsPermissionNotificationIds = - new HashMap<Pair<Pair<Account, String>, Integer>, Integer>(); - private final HashMap<Account, Integer> mSigninRequiredNotificationIds = - new HashMap<Account, Integer>(); + 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 HashMap<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 { @@ -163,15 +190,6 @@ public class AccountManagerService ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); } - private final Object mCacheLock = new Object(); - /** protected by the {@link #mCacheLock} */ - private final HashMap<String, Account[]> mAccountCache = new HashMap<String, Account[]>(); - /** protected by the {@link #mCacheLock} */ - private HashMap<Account, HashMap<String, String>> mUserDataCache = - new HashMap<Account, HashMap<String, String>>(); - /** protected by the {@link #mCacheLock} */ - private HashMap<Account, HashMap<String, String>> mAuthTokenCache = - new HashMap<Account, HashMap<String, String>>(); /** * This should only be called by system code. One should only call this after the service @@ -192,10 +210,6 @@ public class AccountManagerService mContext = context; mPackageManager = packageManager; - synchronized (mCacheLock) { - mOpenHelper = new DatabaseHelper(mContext); - } - mMessageThread = new HandlerThread("AccountManagerService"); mMessageThread.start(); mMessageHandler = new MessageHandler(mMessageThread.getLooper()); @@ -203,6 +217,8 @@ public class AccountManagerService mAuthenticatorCache = authenticatorCache; mAuthenticatorCache.setListener(this, null /* Handler */); + UserAccounts accounts = initUser(0); + sThis.set(this); IntentFilter intentFilter = new IntentFilter(); @@ -211,17 +227,36 @@ public class AccountManagerService mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context1, Intent intent) { - purgeOldGrants(); + purgeOldGrantsAll(); } }, intentFilter); - purgeOldGrants(); - validateAccountsAndPopulateCache(); } - private void purgeOldGrants() { - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + 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); + validateAccountsAndPopulateCache(accounts); + } + 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); @@ -243,15 +278,15 @@ public class AccountManagerService } } - private void validateAccountsAndPopulateCache() { - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + private void validateAccountsAndPopulateCache(UserAccounts accounts) { + 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 { - mAccountCache.clear(); + accounts.accountCache.clear(); final HashMap<String, ArrayList<String>> accountNamesByType = new HashMap<String, ArrayList<String>>(); while (cursor.moveToNext()) { @@ -265,8 +300,8 @@ public class AccountManagerService db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); accountDeleted = true; final Account account = new Account(accountName, accountType); - mUserDataCache.remove(account); - mAuthTokenCache.remove(account); + accounts.userDataCache.remove(account); + accounts.authTokenCache.remove(account); } else { ArrayList<String> accountNames = accountNamesByType.get(accountType); if (accountNames == null) { @@ -286,19 +321,51 @@ public class AccountManagerService accountsForType[i] = new Account(accountName, accountType); ++i; } - mAccountCache.put(accountType, accountsForType); + accounts.accountCache.put(accountType, accountsForType); } } finally { cursor.close(); if (accountDeleted) { - sendAccountsChangedBroadcast(); + sendAccountsChangedBroadcast(accounts.userId); } } } } + private UserAccounts getUserAccountsForCaller() { + return getUserAccounts(UserId.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 List<UserInfo> getAllUsers() { + try { + return AppGlobals.getPackageManager().getUsers(); + } catch (RemoteException re) { + // Local to system process, shouldn't happen + } + return null; + } + public void onServiceChanged(AuthenticatorDescription desc, boolean removed) { - validateAccountsAndPopulateCache(); + // Validate accounts for all users + List<UserInfo> users = getAllUsers(); + if (users == null) { + validateAccountsAndPopulateCache(getUserAccountsForCaller()); + } else { + for (UserInfo user : users) { + validateAccountsAndPopulateCache(getUserAccounts(user.id)); + } + } } public String getPassword(Account account) { @@ -310,21 +377,22 @@ public class AccountManagerService if (account == null) throw new IllegalArgumentException("account is null"); checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); long identityToken = clearCallingIdentity(); try { - return readPasswordInternal(account); + return readPasswordInternal(accounts, account); } finally { restoreCallingIdentity(identityToken); } } - private String readPasswordInternal(Account account) { + private String readPasswordInternal(UserAccounts accounts, Account account) { if (account == null) { return null; } - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + 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); @@ -349,9 +417,10 @@ public class AccountManagerService 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(account, key); + return readUserDataInternal(accounts, account, key); } finally { restoreCallingIdentity(identityToken); } @@ -390,21 +459,23 @@ public class AccountManagerService 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(account, password, extras); + return addAccountInternal(accounts, account, password, extras); } finally { restoreCallingIdentity(identityToken); } } - private boolean addAccountInternal(Account account, String password, Bundle extras) { + private boolean addAccountInternal(UserAccounts accounts, Account account, String password, + Bundle extras) { if (account == null) { return false; } - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); try { long numMatches = DatabaseUtils.longForQuery(db, @@ -437,11 +508,11 @@ public class AccountManagerService } } db.setTransactionSuccessful(); - insertAccountIntoCacheLocked(account); + insertAccountIntoCacheLocked(accounts, account); } finally { db.endTransaction(); } - sendAccountsChangedBroadcast(); + sendAccountsChangedBroadcast(accounts.userId); return true; } } @@ -467,9 +538,10 @@ public class AccountManagerService 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(response, account, features).bind(); + new TestFeaturesSession(accounts, response, account, features).bind(); } finally { restoreCallingIdentity(identityToken); } @@ -479,9 +551,9 @@ public class AccountManagerService private final String[] mFeatures; private final Account mAccount; - public TestFeaturesSession(IAccountManagerResponse response, + public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response, Account account, String[] features) { - super(response, account.type, false /* expectActivityLaunch */, + super(accounts, response, account.type, false /* expectActivityLaunch */, true /* stripAuthTokenFromResult */); mFeatures = features; mAccount = account; @@ -537,21 +609,22 @@ public class AccountManagerService if (response == null) throw new IllegalArgumentException("response is null"); if (account == null) throw new IllegalArgumentException("account is null"); checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); long identityToken = clearCallingIdentity(); - cancelNotification(getSigninRequiredNotificationId(account)); - synchronized(mCredentialsPermissionNotificationIds) { + cancelNotification(getSigninRequiredNotificationId(accounts, account)); + synchronized(accounts.credentialsPermissionNotificationIds) { for (Pair<Pair<Account, String>, Integer> pair: - mCredentialsPermissionNotificationIds.keySet()) { + accounts.credentialsPermissionNotificationIds.keySet()) { if (account.equals(pair.first.first)) { - int id = mCredentialsPermissionNotificationIds.get(pair); + int id = accounts.credentialsPermissionNotificationIds.get(pair); cancelNotification(id); } } } try { - new RemoveAccountSession(response, account).bind(); + new RemoveAccountSession(accounts, response, account).bind(); } finally { restoreCallingIdentity(identityToken); } @@ -559,8 +632,9 @@ public class AccountManagerService private class RemoveAccountSession extends Session { final Account mAccount; - public RemoveAccountSession(IAccountManagerResponse response, Account account) { - super(response, account.type, false /* expectActivityLaunch */, + public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, + Account account) { + super(accounts, response, account.type, false /* expectActivityLaunch */, true /* stripAuthTokenFromResult */); mAccount = account; } @@ -579,7 +653,7 @@ public class AccountManagerService && !result.containsKey(AccountManager.KEY_INTENT)) { final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); if (removalAllowed) { - removeAccountInternal(mAccount); + removeAccountInternal(mAccounts, mAccount); } IAccountManagerResponse response = getResponseAndClose(); if (response != null) { @@ -600,13 +674,18 @@ public class AccountManagerService } } + /* For testing */ protected void removeAccountInternal(Account account) { - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + 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(account); - sendAccountsChangedBroadcast(); + removeAccountFromCacheLocked(accounts, account); + sendAccountsChangedBroadcast(accounts.userId); } } @@ -619,13 +698,14 @@ public class AccountManagerService 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 (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); try { - invalidateAuthTokenLocked(db, accountType, authToken); + invalidateAuthTokenLocked(accounts, db, accountType, authToken); db.setTransactionSuccessful(); } finally { db.endTransaction(); @@ -636,7 +716,8 @@ public class AccountManagerService } } - private void invalidateAuthTokenLocked(SQLiteDatabase db, String accountType, String authToken) { + private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db, + String accountType, String authToken) { if (authToken == null || accountType == null) { return; } @@ -657,7 +738,7 @@ public class AccountManagerService String accountName = cursor.getString(1); String authTokenType = cursor.getString(2); db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); - writeAuthTokenIntoCacheLocked(db, new Account(accountName, accountType), + writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType), authTokenType, null); } } finally { @@ -665,13 +746,14 @@ public class AccountManagerService } } - private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) { + private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type, + String authToken) { if (account == null || type == null) { return false; } - cancelNotification(getSigninRequiredNotificationId(account)); - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + cancelNotification(getSigninRequiredNotificationId(accounts, account)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); try { long accountId = getAccountIdLocked(db, account); @@ -687,7 +769,7 @@ public class AccountManagerService values.put(AUTHTOKENS_AUTHTOKEN, authToken); if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) { db.setTransactionSuccessful(); - writeAuthTokenIntoCacheLocked(db, account, type, authToken); + writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken); return true; } return false; @@ -707,9 +789,10 @@ public class AccountManagerService 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(account, authTokenType); + return readAuthTokenInternal(accounts, account, authTokenType); } finally { restoreCallingIdentity(identityToken); } @@ -725,9 +808,10 @@ public class AccountManagerService 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(account, authTokenType, authToken); + saveAuthTokenToDatabase(accounts, account, authTokenType, authToken); } finally { restoreCallingIdentity(identityToken); } @@ -741,20 +825,21 @@ public class AccountManagerService } if (account == null) throw new IllegalArgumentException("account is null"); checkAuthenticateAccountsPermission(account); + UserAccounts accounts = getUserAccountsForCaller(); long identityToken = clearCallingIdentity(); try { - setPasswordInternal(account, password); + setPasswordInternal(accounts, account, password); } finally { restoreCallingIdentity(identityToken); } } - private void setPasswordInternal(Account account, String password) { + private void setPasswordInternal(UserAccounts accounts, Account account, String password) { if (account == null) { return; } - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); try { final ContentValues values = new ContentValues(); @@ -764,20 +849,20 @@ public class AccountManagerService final String[] argsAccountId = {String.valueOf(accountId)}; db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId); - mAuthTokenCache.remove(account); + accounts.authTokenCache.remove(account); db.setTransactionSuccessful(); } } finally { db.endTransaction(); } - sendAccountsChangedBroadcast(); + sendAccountsChangedBroadcast(accounts.userId); } } - private void sendAccountsChangedBroadcast() { + private void sendAccountsChangedBroadcast(int userId) { Log.i(TAG, "the accounts changed, sending broadcast of " + ACCOUNTS_CHANGED_INTENT.getAction()); - mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); + mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT, userId); } public void clearPassword(Account account) { @@ -788,9 +873,10 @@ public class AccountManagerService } if (account == null) throw new IllegalArgumentException("account is null"); checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); long identityToken = clearCallingIdentity(); try { - setPasswordInternal(account, null); + setPasswordInternal(accounts, account, null); } finally { restoreCallingIdentity(identityToken); } @@ -806,20 +892,22 @@ public class AccountManagerService 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(account, key, value); + setUserdataInternal(accounts, account, key, value); } finally { restoreCallingIdentity(identityToken); } } - private void setUserdataInternal(Account account, String key, String value) { + private void setUserdataInternal(UserAccounts accounts, Account account, String key, + String value) { if (account == null || key == null) { return; } - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); try { long accountId = getAccountIdLocked(db, account); @@ -840,7 +928,7 @@ public class AccountManagerService } } - writeUserDataIntoCacheLocked(db, account, key, value); + writeUserDataIntoCacheLocked(accounts, db, account, key, value); db.setTransactionSuccessful(); } finally { db.endTransaction(); @@ -868,15 +956,16 @@ public class AccountManagerService } void getAuthTokenLabel(final IAccountManagerResponse response, - final Account account, final String authTokenType) { + final Account account, + final String authTokenType, int uid) { if (account == null) throw new IllegalArgumentException("account is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); checkBinderPermission(Manifest.permission.USE_CREDENTIALS); - + UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); long identityToken = clearCallingIdentity(); try { - new Session(response, account.type, false, + new Session(accounts, response, account.type, false, false /* stripAuthTokenFromResult */) { protected String toDebugString(long now) { return super.toDebugString(now) + ", getAuthTokenLabel" @@ -921,6 +1010,7 @@ public class AccountManagerService if (account == null) throw new IllegalArgumentException("account is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); checkBinderPermission(Manifest.permission.USE_CREDENTIALS); + UserAccounts accounts = getUserAccountsForCaller(); AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo = mAuthenticatorCache.getServiceInfo( AuthenticatorDescription.newKey(account.type)); @@ -946,7 +1036,7 @@ public class AccountManagerService // if the caller has permission, do the peek. otherwise go the more expensive // route of starting a Session if (!customTokens && permissionGranted) { - String authToken = readAuthTokenInternal(account, authTokenType); + String authToken = readAuthTokenInternal(accounts, account, authTokenType); if (authToken != null) { Bundle result = new Bundle(); result.putString(AccountManager.KEY_AUTHTOKEN, authToken); @@ -957,7 +1047,7 @@ public class AccountManagerService } } - new Session(response, account.type, expectActivityLaunch, + new Session(accounts, response, account.type, expectActivityLaunch, false /* stripAuthTokenFromResult */) { protected String toDebugString(long now) { if (loginOptions != null) loginOptions.keySet(); @@ -1000,14 +1090,14 @@ public class AccountManagerService return; } if (!customTokens) { - saveAuthTokenToDatabase(new Account(name, type), + saveAuthTokenToDatabase(mAccounts, new Account(name, type), authTokenType, authToken); } } Intent intent = result.getParcelable(AccountManager.KEY_INTENT); if (intent != null && notifyOnAuthFailure && !customTokens) { - doNotification( + doNotification(mAccounts, account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE), intent); } @@ -1090,26 +1180,27 @@ public class AccountManagerService private Integer getCredentialPermissionNotificationId(Account account, String authTokenType, int uid) { Integer id; - synchronized(mCredentialsPermissionNotificationIds) { + UserAccounts accounts = getUserAccounts(UserId.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 = mCredentialsPermissionNotificationIds.get(key); + id = accounts.credentialsPermissionNotificationIds.get(key); if (id == null) { id = mNotificationIds.incrementAndGet(); - mCredentialsPermissionNotificationIds.put(key, id); + accounts.credentialsPermissionNotificationIds.put(key, id); } } return id; } - private Integer getSigninRequiredNotificationId(Account account) { + private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) { Integer id; - synchronized(mSigninRequiredNotificationIds) { - id = mSigninRequiredNotificationIds.get(account); + synchronized (accounts.signinRequiredNotificationIds) { + id = accounts.signinRequiredNotificationIds.get(account); if (id == null) { id = mNotificationIds.incrementAndGet(); - mSigninRequiredNotificationIds.put(account, id); + accounts.signinRequiredNotificationIds.put(account, id); } } return id; @@ -1131,6 +1222,7 @@ public class AccountManagerService 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; @@ -1139,7 +1231,7 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { - new Session(response, accountType, expectActivityLaunch, + new Session(accounts, response, accountType, expectActivityLaunch, true /* stripAuthTokenFromResult */) { public void run() throws RemoteException { mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, @@ -1172,9 +1264,10 @@ public class AccountManagerService if (response == null) throw new IllegalArgumentException("response is null"); if (account == null) throw new IllegalArgumentException("account is null"); checkManageAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); long identityToken = clearCallingIdentity(); try { - new Session(response, account.type, expectActivityLaunch, + new Session(accounts, response, account.type, expectActivityLaunch, true /* stripAuthTokenFromResult */) { public void run() throws RemoteException { mAuthenticator.confirmCredentials(this, account, options); @@ -1204,9 +1297,10 @@ public class AccountManagerService 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(response, account.type, expectActivityLaunch, + new Session(accounts, response, account.type, expectActivityLaunch, true /* stripAuthTokenFromResult */) { public void run() throws RemoteException { mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); @@ -1236,9 +1330,10 @@ public class AccountManagerService 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(response, accountType, expectActivityLaunch, + new Session(accounts, response, accountType, expectActivityLaunch, true /* stripAuthTokenFromResult */) { public void run() throws RemoteException { mAuthenticator.editProperties(this, mAccountType); @@ -1259,16 +1354,16 @@ public class AccountManagerService private volatile ArrayList<Account> mAccountsWithFeatures = null; private volatile int mCurrentAccount = 0; - public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response, - String type, String[] features) { - super(response, type, false /* expectActivityLaunch */, + 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 (mCacheLock) { - mAccountsOfType = getAccountsFromCacheLocked(mAccountType); + synchronized (mAccounts.cacheLock) { + mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType); } // check whether each account matches the requested features mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length); @@ -1346,6 +1441,23 @@ public class AccountManagerService } } + /** + * 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); + } + } + public Account[] getAccounts(String type) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAccounts: accountType " + type @@ -1353,10 +1465,11 @@ public class AccountManagerService + ", pid " + Binder.getCallingPid()); } checkReadAccountsPermission(); + UserAccounts accounts = getUserAccountsForCaller(); long identityToken = clearCallingIdentity(); try { - synchronized (mCacheLock) { - return getAccountsFromCacheLocked(type); + synchronized (accounts.cacheLock) { + return getAccountsFromCacheLocked(accounts, type); } } finally { restoreCallingIdentity(identityToken); @@ -1375,19 +1488,20 @@ public class AccountManagerService 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 (mCacheLock) { - accounts = getAccountsFromCacheLocked(type); + synchronized (userAccounts.cacheLock) { + accounts = getAccountsFromCacheLocked(userAccounts, type); } Bundle result = new Bundle(); result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); onResult(response, result); return; } - new GetAccountsByTypeAndFeatureSession(response, type, features).bind(); + new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind(); } finally { restoreCallingIdentity(identityToken); } @@ -1435,12 +1549,14 @@ public class AccountManagerService IAccountAuthenticator mAuthenticator = null; private final boolean mStripAuthTokenFromResult; + protected final UserAccounts mAccounts; - public Session(IAccountManagerResponse response, String accountType, + 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; @@ -1578,7 +1694,7 @@ public class AccountManagerService String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { Account account = new Account(accountName, accountType); - cancelNotification(getSigninRequiredNotificationId(account)); + cancelNotification(getSigninRequiredNotificationId(mAccounts, account)); } } IAccountManagerResponse response; @@ -1694,20 +1810,23 @@ public class AccountManagerService } } - private static String getDatabaseName() { - if(Environment.isEncryptedFilesystemEnabled()) { - // Hard-coded path in case of encrypted file system - return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME; - } else { - // Regular path in case of non-encrypted file system - return DATABASE_NAME; + private static String getDatabaseName(int userId) { + File systemDir = Environment.getSystemSecureDirectory(); + File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME); + if (userId == 0) { + // Migrate old file, if it exists, to the new location + File oldFile = new File(systemDir, DATABASE_NAME); + if (oldFile.exists()) { + oldFile.renameTo(databaseFile); + } } + return databaseFile.getPath(); } - private class DatabaseHelper extends SQLiteOpenHelper { + static class DatabaseHelper extends SQLiteOpenHelper { - public DatabaseHelper(Context context) { - super(context, AccountManagerService.getDatabaseName(), null, DATABASE_VERSION); + public DatabaseHelper(Context context, int userId) { + super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION); } /** @@ -1799,15 +1918,6 @@ public class AccountManagerService } } - private void setMetaValue(String key, String value) { - ContentValues values = new ContentValues(); - values.put(META_KEY, key); - values.put(META_VALUE, value); - synchronized (mCacheLock) { - mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values); - } - } - public IBinder onBind(Intent intent) { return asBinder(); } @@ -1837,11 +1947,25 @@ public class AccountManagerService + " without permission " + android.Manifest.permission.DUMP); return; } + final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c"); - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + fout = new IndentingPrintWriter(fout, " "); + int size = mUsers.size(); + for (int i = 0; i < size; i++) { + fout.println("User " + mUsers.keyAt(i) + ":"); + ((IndentingPrintWriter) fout).increaseIndent(); + dumpUser(mUsers.valueAt(i), fd, fout, args, isCheckinRequest); + ((IndentingPrintWriter) fout).decreaseIndent(); + if (i < size - 1) { + fout.println(); + } + } + } - final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c"); + 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. @@ -1858,7 +1982,7 @@ public class AccountManagerService } } } else { - Account[] accounts = getAccountsFromCacheLocked(null /* type */); + Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */); fout.println("Accounts: " + accounts.length); for (Account account : accounts) { fout.println(" " + account); @@ -1879,7 +2003,8 @@ public class AccountManagerService } } - private void doNotification(Account account, CharSequence message, Intent intent) { + private void doNotification(UserAccounts accounts, Account account, CharSequence message, + Intent intent) { long identityToken = clearCallingIdentity(); try { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -1891,7 +2016,7 @@ public class AccountManagerService intent.getComponent().getClassName())) { createNoCredentialsPermissionNotification(account, intent); } else { - final Integer notificationId = getSigninRequiredNotificationId(account); + final Integer notificationId = getSigninRequiredNotificationId(accounts, account); intent.addCategory(String.valueOf(notificationId)); Notification n = new Notification(android.R.drawable.stat_sys_warning, null, 0 /* when */); @@ -1962,7 +2087,7 @@ public class AccountManagerService final boolean fromAuthenticator = account != null && hasAuthenticatorUid(account.type, callerUid); final boolean hasExplicitGrants = account != null - && hasExplicitlyGrantedPermission(account, authTokenType); + && hasExplicitlyGrantedPermission(account, authTokenType, callerUid); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid " + callerUid + ", " + account @@ -1984,13 +2109,15 @@ public class AccountManagerService return false; } - private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType) { - if (Binder.getCallingUid() == android.os.Process.SYSTEM_UID) { + private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType, + int callerUid) { + if (callerUid == android.os.Process.SYSTEM_UID) { return true; } - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - String[] args = {String.valueOf(Binder.getCallingUid()), authTokenType, + 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; @@ -1998,7 +2125,7 @@ public class AccountManagerService // 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 " + Binder.getCallingUid() + + authTokenType + " by uid " + callerUid + " but ignoring since device is in test harness."); return true; } @@ -2048,8 +2175,9 @@ public class AccountManagerService Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception()); return; } - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); try { long accountId = getAccountIdLocked(db, account); @@ -2081,8 +2209,9 @@ public class AccountManagerService Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception()); return; } - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); + synchronized (accounts.cacheLock) { + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); try { long accountId = getAccountIdLocked(db, account); @@ -2105,8 +2234,8 @@ public class AccountManagerService return value != null ? ("[" + TextUtils.join(",", value) + "]") : null; } - private void removeAccountFromCacheLocked(Account account) { - final Account[] oldAccountsForType = mAccountCache.get(account.type); + 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) { @@ -2115,34 +2244,34 @@ public class AccountManagerService } } if (newAccountsList.isEmpty()) { - mAccountCache.remove(account.type); + accounts.accountCache.remove(account.type); } else { Account[] newAccountsForType = new Account[newAccountsList.size()]; newAccountsForType = newAccountsList.toArray(newAccountsForType); - mAccountCache.put(account.type, newAccountsForType); + accounts.accountCache.put(account.type, newAccountsForType); } } - mUserDataCache.remove(account); - mAuthTokenCache.remove(account); + 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(Account account) { - Account[] accountsForType = mAccountCache.get(account.type); + 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; - mAccountCache.put(account.type, newAccountsForType); + accounts.accountCache.put(account.type, newAccountsForType); } - protected Account[] getAccountsFromCacheLocked(String accountType) { + protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) { if (accountType != null) { - final Account[] accounts = mAccountCache.get(accountType); + final Account[] accounts = userAccounts.accountCache.get(accountType); if (accounts == null) { return EMPTY_ACCOUNT_ARRAY; } else { @@ -2150,7 +2279,7 @@ public class AccountManagerService } } else { int totalLength = 0; - for (Account[] accounts : mAccountCache.values()) { + for (Account[] accounts : userAccounts.accountCache.values()) { totalLength += accounts.length; } if (totalLength == 0) { @@ -2158,7 +2287,7 @@ public class AccountManagerService } Account[] accounts = new Account[totalLength]; totalLength = 0; - for (Account[] accountsOfType : mAccountCache.values()) { + for (Account[] accountsOfType : userAccounts.accountCache.values()) { System.arraycopy(accountsOfType, 0, accounts, totalLength, accountsOfType.length); totalLength += accountsOfType.length; @@ -2167,12 +2296,12 @@ public class AccountManagerService } } - protected void writeUserDataIntoCacheLocked(final SQLiteDatabase db, Account account, - String key, String value) { - HashMap<String, String> userDataForAccount = mUserDataCache.get(account); + 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); - mUserDataCache.put(account, userDataForAccount); + accounts.userDataCache.put(account, userDataForAccount); } if (value == null) { userDataForAccount.remove(key); @@ -2181,12 +2310,12 @@ public class AccountManagerService } } - protected void writeAuthTokenIntoCacheLocked(final SQLiteDatabase db, Account account, - String key, String value) { - HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account); + 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); - mAuthTokenCache.put(account, authTokensForAccount); + accounts.authTokenCache.put(account, authTokensForAccount); } if (value == null) { authTokensForAccount.remove(key); @@ -2195,27 +2324,28 @@ public class AccountManagerService } } - protected String readAuthTokenInternal(Account account, String authTokenType) { - synchronized (mCacheLock) { - HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account); + 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 = mOpenHelper.getReadableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); - mAuthTokenCache.put(account, authTokensForAccount); + accounts.authTokenCache.put(account, authTokensForAccount); } return authTokensForAccount.get(authTokenType); } } - protected String readUserDataInternal(Account account, String key) { - synchronized (mCacheLock) { - HashMap<String, String> userDataForAccount = mUserDataCache.get(account); + 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 = mOpenHelper.getReadableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); - mUserDataCache.put(account, userDataForAccount); + accounts.userDataCache.put(account, userDataForAccount); } return userDataForAccount.get(key); } diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java index 0ee683c..4419c8c 100644 --- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java +++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java @@ -113,8 +113,7 @@ public class GrantCredentialsPermissionActivity extends Activity implements View } }; - accountManagerService.getAuthTokenLabel( - response, mAccount, mAuthTokenType); + accountManagerService.getAuthTokenLabel(response, mAccount, mAuthTokenType, mUid); findViewById(R.id.allow_button).setOnClickListener(this); findViewById(R.id.deny_button).setOnClickListener(this); diff --git a/core/java/android/accounts/OnAccountsUpdateListener.java b/core/java/android/accounts/OnAccountsUpdateListener.java index 38b371d..2b4ee50 100644 --- a/core/java/android/accounts/OnAccountsUpdateListener.java +++ b/core/java/android/accounts/OnAccountsUpdateListener.java @@ -17,11 +17,11 @@ package android.accounts; /** - * An interface that contains the callback used by the AccountMonitor + * An interface that contains the callback used by the AccountManager */ public interface OnAccountsUpdateListener { /** - * This invoked when the AccountMonitor starts up and whenever the account + * This invoked when the AccountManager starts up and whenever the account * set changes. * @param accounts the current accounts */ diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index e01fa1a..788765d 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -208,7 +208,7 @@ public abstract class Animator implements Cloneable { * this call to its child objects to tell them to set up the values. A * ObjectAnimator object will use the information it has about its target object * and PropertyValuesHolder objects to get the start values for its properties. - * An ValueAnimator object will ignore the request since it does not have enough + * A ValueAnimator object will ignore the request since it does not have enough * information (such as a target object) to gather these values. */ public void setupStartValues() { @@ -220,7 +220,7 @@ public abstract class Animator implements Cloneable { * this call to its child objects to tell them to set up the values. A * ObjectAnimator object will use the information it has about its target object * and PropertyValuesHolder objects to get the start values for its properties. - * An ValueAnimator object will ignore the request since it does not have enough + * A ValueAnimator object will ignore the request since it does not have enough * information (such as a target object) to gather these values. */ public void setupEndValues() { diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 6fbeee3..f69120a 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -400,7 +400,7 @@ public class ValueAnimator extends Animator { /** * Sets the values, per property, being animated between. This function is called internally - * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can + * by the constructors of ValueAnimator that take a list of values. But a ValueAnimator can * be constructed without values and this method can be called to set the values manually * instead. * @@ -645,7 +645,7 @@ public class ValueAnimator extends Animator { // onAnimate to process the next frame of the animations. if (!mAnimationScheduled && (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) { - mChoreographer.postAnimationCallback(this); + mChoreographer.postAnimationCallback(this, null); mAnimationScheduled = true; } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 8e8d37d..599487d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -203,8 +203,8 @@ import java.util.HashMap; * with the user. Between these two methods you can maintain resources that * are needed to show the activity to the user. For example, you can register * a {@link android.content.BroadcastReceiver} in onStart() to monitor for changes - * that impact your UI, and unregister it in onStop() when the user an no - * longer see what you are displaying. The onStart() and onStop() methods + * that impact your UI, and unregister it in onStop() when the user no + * longer sees what you are displaying. The onStart() and onStop() methods * can be called multiple times, as the activity becomes visible and hidden * to the user. * @@ -570,7 +570,18 @@ import java.util.HashMap; * tag. By doing so, other applications will need to declare a corresponding * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} * element in their own manifest to be able to start that activity. - * + * + * <p>When starting an Activity you can set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} on the Intent. This will grant the + * Activity access to the specific URIs in the Intent. Access will remain + * until the Activity has finished (it will remain across the hosting + * process being killed and other temporary destruction). As of + * {@link android.os.Build.VERSION_CODES#GINGERBREAD}, if the Activity + * was already created and a new Intent is being delivered to + * {@link #onNewIntent(Intent)}, any newly granted URI permissions will be added + * to the existing ones it holds. + * * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> * document for more information on permissions and security in general. * @@ -3549,7 +3560,16 @@ public class Activity extends ContextThemeWrapper /** * Call this to set the result that your activity will return to its * caller. - * + * + * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, the Intent + * you supply here can have {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} set. This will grant the + * Activity receiving the result access to the specific URIs in the Intent. + * Access will remain until the Activity has finished (it will remain across the hosting + * process being killed and other temporary destruction) and will be added + * to any existing set of URI permissions it already holds. + * * @param resultCode The result code to propagate back to the originating * activity, often RESULT_CANCELED or RESULT_OK * @param data The data to propagate back to the originating activity. diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d98d87b..59c803e 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1145,7 +1145,14 @@ public class ActivityManager { * @hide */ public int flags; - + + /** + * Last memory trim level reported to the process: corresponds to + * the values supplied to {@link android.content.ComponentCallbacks2#onTrimMemory(int) + * ComponentCallbacks2.onTrimMemory(int)}. + */ + public int lastTrimLevel; + /** * Constant for {@link #importance}: this process is running the * foreground UI. @@ -1212,7 +1219,7 @@ public class ActivityManager { * be maintained in the future. */ public int lru; - + /** * Constant for {@link #importanceReasonCode}: nothing special has * been specified for the reason for this level. @@ -1282,6 +1289,7 @@ public class ActivityManager { dest.writeInt(uid); dest.writeStringArray(pkgList); dest.writeInt(this.flags); + dest.writeInt(lastTrimLevel); dest.writeInt(importance); dest.writeInt(lru); dest.writeInt(importanceReasonCode); @@ -1296,6 +1304,7 @@ public class ActivityManager { uid = source.readInt(); pkgList = source.readStringArray(); flags = source.readInt(); + lastTrimLevel = source.readInt(); importance = source.readInt(); lru = source.readInt(); importanceReasonCode = source.readInt(); @@ -1349,7 +1358,25 @@ public class ActivityManager { return null; } } - + + /** + * Return global memory state information for the calling process. This + * does not fill in all fields of the {@link RunningAppProcessInfo}. The + * only fields that will be filled in are + * {@link RunningAppProcessInfo#pid}, + * {@link RunningAppProcessInfo#uid}, + * {@link RunningAppProcessInfo#lastTrimLevel}, + * {@link RunningAppProcessInfo#importance}, + * {@link RunningAppProcessInfo#lru}, and + * {@link RunningAppProcessInfo#importanceReasonCode}. + */ + static public void getMyMemoryState(RunningAppProcessInfo outState) { + try { + ActivityManagerNative.getDefault().getMyMemoryState(outState); + } catch (RemoteException e) { + } + } + /** * Return information about the memory usage of one or more processes. * diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 24079a5..b952649 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1126,7 +1126,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } - + + case GET_MY_MEMORY_STATE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ActivityManager.RunningAppProcessInfo info = + new ActivityManager.RunningAppProcessInfo(); + getMyMemoryState(info); + reply.writeNoException(); + info.writeToParcel(reply, 0); + return true; + } + case GET_DEVICE_CONFIGURATION_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); ConfigurationInfo config = getDeviceConfigurationInfo(); @@ -2973,6 +2983,19 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outInfo) + throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_MY_MEMORY_STATE_TRANSACTION, data, reply, 0); + reply.readException(); + outInfo.readFromParcel(reply); + reply.recycle(); + data.recycle(); + } + public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 588125d..ebf4261 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -98,7 +98,7 @@ public class ApplicationErrorReport implements Parcelable { /** * Package name of the application which installed the application this * report pertains to. - * This identifies which Market the application came from. + * This identifies which market the application came from. */ public String installerPackageName; diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index f1ce2bb..f04ff6a 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -591,7 +591,7 @@ public class Dialog implements DialogInterface, Window.Callback, } /** - * Called when an key shortcut event is not handled by any of the views in the Dialog. + * Called when a key shortcut event is not handled by any of the views in the Dialog. * Override this method to implement global key shortcuts for the Dialog. * Key shortcuts can also be implemented by setting the * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items. diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 53a71db..ea2545f 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -278,13 +278,16 @@ public interface IActivityManager extends IInterface { * SIGUSR1 is delivered. All others are ignored. */ public void signalPersistentProcesses(int signal) throws RemoteException; - // Retrieve info of applications installed on external media that are currently - // running. + // Retrieve running application processes in the system public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() throws RemoteException; - // Retrieve running application processes in the system + // Retrieve info of applications installed on external media that are currently + // running. public List<ApplicationInfo> getRunningExternalApplications() throws RemoteException; + // Get memory information about the calling process. + public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outInfo) + throws RemoteException; // Get device configuration public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException; @@ -606,4 +609,5 @@ public interface IActivityManager extends IInterface { int KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+139; int GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+140; int REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+141; + int GET_MY_MEMORY_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+142; } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index b0637a7..c95066c 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -351,7 +351,7 @@ public final class PendingIntent implements Parcelable { /** * Cancel a currently active PendingIntent. Only the original application - * owning an PendingIntent can cancel it. + * owning a PendingIntent can cancel it. */ public void cancel() { try { diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 35bd8c0..207ae76 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -163,7 +163,19 @@ import java.io.PrintWriter; * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} * element in their own manifest to be able to start, stop, or bind to * the service. - * + * + * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, when using + * {@link Context#startService(Intent) Context.startService(Intent)}, you can + * also set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION + * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION + * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} on the Intent. This will grant the + * Service temporary access to the specific URIs in the Intent. Access will + * remain until the Service has called {@link #stopSelf(int)} for that start + * command or a later one, or until the Service has been completely stopped. + * This works for granting access to the other apps that have not requested + * the permission protecting the Service, or even when the Service is not + * exported at all. + * * <p>In addition, a service can protect individual IPC calls into it with * permissions, by calling the * {@link #checkCallingPermission} @@ -441,7 +453,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac /** * Called by the system to notify a Service that it is no longer used and is being removed. The - * service should clean up an resources it holds (threads, registered + * service should clean up any resources it holds (threads, registered * receivers, etc) at this point. Upon return, there will be no more calls * in to this Service object and it is effectively dead. Do not call this method directly. */ diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 43cd330..30b65de 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -165,7 +165,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { = "android.app.action.ACTION_PASSWORD_EXPIRING"; /** - * Name under which an DevicePolicy component publishes information + * Name under which a DevicePolicy component publishes information * about itself. This meta-data must reference an XML resource containing * a device-admin tag. XXX TO DO: describe syntax. */ diff --git a/core/java/android/bluetooth/AtCommandResult.java b/core/java/android/bluetooth/AtCommandResult.java index 375a6dd..9675234 100644 --- a/core/java/android/bluetooth/AtCommandResult.java +++ b/core/java/android/bluetooth/AtCommandResult.java @@ -17,7 +17,7 @@ package android.bluetooth; /** - * The result of execution of an single AT command.<p> + * The result of execution of a single AT command.<p> * * * This class can represent the final response to an AT command line, and also diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 189e8fc..04af5f7 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -193,7 +193,7 @@ public final class BluetoothDevice implements Parcelable { public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI"; /** - * Used as an Parcelable {@link BluetoothClass} extra field in {@link + * Used as a Parcelable {@link BluetoothClass} extra field in {@link * #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents. */ public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS"; diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index 2683bef..639ae1a 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -60,7 +60,7 @@ public class BluetoothPbap { public static final String PBAP_PREVIOUS_STATE = "android.bluetooth.pbap.intent.PBAP_PREVIOUS_STATE"; - /** Indicates the state of an pbap connection state has changed. + /** Indicates the state of a pbap connection state has changed. * This intent will always contain PBAP_STATE, PBAP_PREVIOUS_STATE and * BluetoothIntent.ADDRESS extras. */ diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index a8b1bf4..a655dd4 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -153,7 +153,7 @@ public class ClipData implements Parcelable { final Bitmap mIcon; - final ArrayList<Item> mItems = new ArrayList<Item>(); + final ArrayList<Item> mItems; /** * Description of a single item in a ClippedData. @@ -321,6 +321,33 @@ public class ClipData implements Parcelable { return ""; } //END_INCLUDE(coerceToText) + + @Override + public String toString() { + StringBuilder b = new StringBuilder(128); + + b.append("ClipData.Item { "); + toShortString(b); + b.append(" }"); + + return b.toString(); + } + + /** @hide */ + public void toShortString(StringBuilder b) { + if (mText != null) { + b.append("T:"); + b.append(mText); + } else if (mUri != null) { + b.append("U:"); + b.append(mUri); + } else if (mIntent != null) { + b.append("I:"); + mIntent.toShortString(b, true, true, true, true); + } else { + b.append("NULL"); + } + } } /** @@ -336,6 +363,7 @@ public class ClipData implements Parcelable { throw new NullPointerException("item is null"); } mIcon = null; + mItems = new ArrayList<Item>(); mItems.add(item); } @@ -351,10 +379,23 @@ public class ClipData implements Parcelable { throw new NullPointerException("item is null"); } mIcon = null; + mItems = new ArrayList<Item>(); mItems.add(item); } /** + * Create a new clip that is a copy of another clip. This does a deep-copy + * of all items in the clip. + * + * @param other The existing ClipData that is to be copied. + */ + public ClipData(ClipData other) { + mClipDescription = other.mClipDescription; + mIcon = other.mIcon; + mItems = new ArrayList<Item>(other.mItems); + } + + /** * Create a new ClipData holding data of the type * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}. * @@ -475,6 +516,46 @@ public class ClipData implements Parcelable { } @Override + public String toString() { + StringBuilder b = new StringBuilder(128); + + b.append("ClipData { "); + toShortString(b); + b.append(" }"); + + return b.toString(); + } + + /** @hide */ + public void toShortString(StringBuilder b) { + boolean first; + if (mClipDescription != null) { + first = !mClipDescription.toShortString(b); + } else { + first = true; + } + if (mIcon != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("I:"); + b.append(mIcon.getWidth()); + b.append('x'); + b.append(mIcon.getHeight()); + } + for (int i=0; i<mItems.size(); i++) { + if (!first) { + b.append(' '); + } + first = false; + b.append('{'); + mItems.get(i).toShortString(b); + b.append('}'); + } + } + + @Override public int describeContents() { return 0; } @@ -515,6 +596,7 @@ public class ClipData implements Parcelable { } else { mIcon = null; } + mItems = new ArrayList<Item>(); final int N = in.readInt(); for (int i=0; i<N; i++) { CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java index b5fa20c..c6b51ef 100644 --- a/core/java/android/content/ClipDescription.java +++ b/core/java/android/content/ClipDescription.java @@ -184,6 +184,39 @@ public class ClipDescription implements Parcelable { } @Override + public String toString() { + StringBuilder b = new StringBuilder(128); + + b.append("ClipDescription { "); + toShortString(b); + b.append(" }"); + + return b.toString(); + } + + /** @hide */ + public boolean toShortString(StringBuilder b) { + boolean first = true; + for (int i=0; i<mMimeTypes.length; i++) { + if (!first) { + b.append(' '); + } + first = false; + b.append(mMimeTypes[i]); + } + if (mLabel != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append('"'); + b.append(mLabel); + b.append('"'); + } + return !first; + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/content/ComponentCallbacks2.java b/core/java/android/content/ComponentCallbacks2.java index 8b9f97c..85294dd 100644 --- a/core/java/android/content/ComponentCallbacks2.java +++ b/core/java/android/content/ComponentCallbacks2.java @@ -52,15 +52,49 @@ public interface ComponentCallbacks2 extends ComponentCallbacks { static final int TRIM_MEMORY_UI_HIDDEN = 20; /** + * Level for {@link #onTrimMemory(int)}: the process is not an expendable + * background process, but the device is running extremely low on memory + * and is about to not be able to keep any background processes running. + * Your running process should free up as many non-critical resources as it + * can to allow that memory to be used elsewhere. The next thing that + * will happen after this is {@link #onLowMemory()} called to report that + * nothing at all can be kept in the background, a situation that can start + * to notably impact the user. + */ + static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; + + /** + * Level for {@link #onTrimMemory(int)}: the process is not an expendable + * background process, but the device is running low on memory. + * Your running process should free up unneeded resources to allow that + * memory to be used elsewhere. + */ + static final int TRIM_MEMORY_RUNNING_LOW = 10; + + + /** + * Level for {@link #onTrimMemory(int)}: the process is not an expendable + * background process, but the device is running moderately low on memory. + * Your running process may want to release some unneeded resources for + * use elsewhere. + */ + static final int TRIM_MEMORY_RUNNING_MODERATE = 5; + + /** * Called when the operating system has determined that it is a good * time for a process to trim unneeded memory from its process. This will * happen for example when it goes in the background and there is not enough - * memory to keep as many background processes running as desired. + * memory to keep as many background processes running as desired. You + * should never compare to exact values of the level, since new intermediate + * values may be added -- you will typically want to compare if the value + * is greater or equal to a level you are interested in. * * @param level The context of the trim, giving a hint of the amount of * trimming the application may like to perform. May be * {@link #TRIM_MEMORY_COMPLETE}, {@link #TRIM_MEMORY_MODERATE}, - * {@link #TRIM_MEMORY_BACKGROUND}, or {@link #TRIM_MEMORY_UI_HIDDEN}. + * {@link #TRIM_MEMORY_BACKGROUND}, {@link #TRIM_MEMORY_UI_HIDDEN}, + * {@link #TRIM_MEMORY_RUNNING_CRITICAL}, {@link #TRIM_MEMORY_RUNNING_LOW}, + * or {@link #TRIM_MEMORY_RUNNING_MODERATE}. */ void onTrimMemory(int level); } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 7a612bc..2930998 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -917,7 +917,7 @@ public abstract class ContentResolver { } /** - * Call an provider-defined method. This can be used to implement + * Call a provider-defined method. This can be used to implement * read or write interfaces which are cheaper than using a Cursor and/or * do not fit into the traditional table model. * diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index fc4c262..f827c3d 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -26,6 +26,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserId; import android.util.Log; import android.util.SparseIntArray; import android.Manifest; @@ -163,6 +164,8 @@ public final class ContentService extends IContentService.Stub { Log.v(TAG, "Notifying update of " + uri + " from observer " + observer + ", syncToNetwork " + syncToNetwork); } + + int userId = UserId.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(); @@ -201,7 +204,8 @@ public final class ContentService extends IContentService.Stub { if (syncToNetwork) { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - syncManager.scheduleLocalSync(null /* all accounts */, uri.getAuthority()); + syncManager.scheduleLocalSync(null /* all accounts */, userId, + uri.getAuthority()); } } } finally { @@ -229,13 +233,15 @@ public final class ContentService extends IContentService.Stub { public void requestSync(Account account, String authority, Bundle extras) { ContentResolver.validateSyncExtrasBundle(extras); + int userId = UserId.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.scheduleSync(account, authority, extras, 0 /* no delay */, + syncManager.scheduleSync(account, userId, authority, extras, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } finally { @@ -250,14 +256,16 @@ public final class ContentService extends IContentService.Stub { * @param authority filter the pending and active syncs to cancel using this authority */ public void cancelSync(Account account, String authority) { + int userId = UserId.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, authority); - syncManager.cancelActiveSync(account, authority); + syncManager.clearScheduledSyncOperations(account, userId, authority); + syncManager.cancelActiveSync(account, userId, authority); } } finally { restoreCallingIdentity(identityToken); @@ -283,12 +291,14 @@ public final class ContentService extends IContentService.Stub { public boolean getSyncAutomatically(Account account, String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { return syncManager.getSyncStorageEngine().getSyncAutomatically( - account, providerName); + account, userId, providerName); } } finally { restoreCallingIdentity(identityToken); @@ -299,12 +309,14 @@ public final class ContentService extends IContentService.Stub { 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 = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.getSyncStorageEngine().setSyncAutomatically( - account, providerName, sync); + account, userId, providerName, sync); } } finally { restoreCallingIdentity(identityToken); @@ -315,10 +327,12 @@ public final class ContentService extends IContentService.Stub { long pollFrequency) { mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { getSyncManager().getSyncStorageEngine().addPeriodicSync( - account, authority, extras, pollFrequency); + account, userId, authority, extras, pollFrequency); } finally { restoreCallingIdentity(identityToken); } @@ -327,9 +341,12 @@ public final class ContentService extends IContentService.Stub { 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 = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { - getSyncManager().getSyncStorageEngine().removePeriodicSync(account, authority, extras); + getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority, + extras); } finally { restoreCallingIdentity(identityToken); } @@ -338,10 +355,12 @@ public final class ContentService extends IContentService.Stub { public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { return getSyncManager().getSyncStorageEngine().getPeriodicSyncs( - account, providerName); + account, userId, providerName); } finally { restoreCallingIdentity(identityToken); } @@ -350,12 +369,14 @@ public final class ContentService extends IContentService.Stub { public int getIsSyncable(Account account, String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { return syncManager.getSyncStorageEngine().getIsSyncable( - account, providerName); + account, userId, providerName); } } finally { restoreCallingIdentity(identityToken); @@ -366,12 +387,14 @@ public final class ContentService extends IContentService.Stub { 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 = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.getSyncStorageEngine().setIsSyncable( - account, providerName, syncable); + account, userId, providerName, syncable); } } finally { restoreCallingIdentity(identityToken); @@ -381,11 +404,13 @@ public final class ContentService extends IContentService.Stub { public boolean getMasterSyncAutomatically() { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(); + return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId); } } finally { restoreCallingIdentity(identityToken); @@ -396,11 +421,13 @@ public final class ContentService extends IContentService.Stub { public void setMasterSyncAutomatically(boolean flag) { mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag); + syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId); } } finally { restoreCallingIdentity(identityToken); @@ -410,12 +437,14 @@ public final class ContentService extends IContentService.Stub { public boolean isSyncActive(Account account, String authority) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, "no permission to read the sync stats"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { return syncManager.getSyncStorageEngine().isSyncActive( - account, authority); + account, userId, authority); } } finally { restoreCallingIdentity(identityToken); @@ -426,9 +455,11 @@ public final class ContentService extends IContentService.Stub { public List<SyncInfo> getCurrentSyncs() { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, "no permission to read the sync stats"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { - return getSyncManager().getSyncStorageEngine().getCurrentSyncs(); + return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId); } finally { restoreCallingIdentity(identityToken); } @@ -437,12 +468,14 @@ public final class ContentService extends IContentService.Stub { public SyncStatusInfo getSyncStatus(Account account, String authority) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, "no permission to read the sync stats"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority( - account, authority); + account, userId, authority); } } finally { restoreCallingIdentity(identityToken); @@ -453,11 +486,13 @@ public final class ContentService extends IContentService.Stub { public boolean isSyncPending(Account account, String authority) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, "no permission to read the sync stats"); + int userId = UserId.getCallingUserId(); + long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - return syncManager.getSyncStorageEngine().isSyncPending(account, authority); + return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority); } } finally { restoreCallingIdentity(identityToken); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 111f45e..0e9e256 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1270,7 +1270,7 @@ public abstract class Context { /** * Connect to an application service, creating it if needed. This defines * a dependency between your application and the service. The given - * <var>conn</var> will receive the service object when its created and be + * <var>conn</var> will receive the service object when it is created and be * told if it dies and restarts. The service will be considered required * by the system only for as long as the calling context exists. For * example, if this Context is an Activity that is stopped, the service will @@ -1279,15 +1279,15 @@ public abstract class Context { * <p>This function will throw {@link SecurityException} if you do not * have permission to bind to the given service. * - * <p class="note">Note: this method <em>can not be called from an + * <p class="note">Note: this method <em>can not be called from a * {@link BroadcastReceiver} component</em>. A pattern you can use to - * communicate from an BroadcastReceiver to a Service is to call + * communicate from a BroadcastReceiver to a Service is to call * {@link #startService} with the arguments containing the command to be * sent, with the service calling its * {@link android.app.Service#stopSelf(int)} method when done executing * that command. See the API demo App/Service/Service Start Arguments * Controller for an illustration of this. It is okay, however, to use - * this method from an BroadcastReceiver that has been registered with + * this method from a BroadcastReceiver that has been registered with * {@link #registerReceiver}, since the lifetime of this BroadcastReceiver * is tied to another object (the one that registered it).</p> * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ab62c44..6cf5b43 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -843,10 +843,10 @@ public class Intent implements Parcelable, Cloneable { * just say what kind of data is desired, not a URI of existing data from * which the user can pick. A ACTION_GET_CONTENT could allow the user to * create the data as it runs (for example taking a picture or recording a - * sound), let them browser over the web and download the desired data, + * sound), let them browse over the web and download the desired data, * etc. * <p> - * There are two main ways to use this action: if you want an specific kind + * There are two main ways to use this action: if you want a specific kind * of data, such as a person contact, you set the MIME type to the kind of * data you want and launch it with {@link Context#startActivity(Intent)}. * The system will then launch the best application to select that kind @@ -864,12 +864,12 @@ public class Intent implements Parcelable, Cloneable { * broad MIME type (such as image/* or {@literal *}/*), resulting in a * broad range of content types the user can select from. * <p> - * When using such a broad GET_CONTENT action, it is often desireable to + * When using such a broad GET_CONTENT action, it is often desirable to * only pick from data that can be represented as a stream. This is * accomplished by requiring the {@link #CATEGORY_OPENABLE} in the Intent. * <p> * Callers can optionally specify {@link #EXTRA_LOCAL_ONLY} to request that - * the launched content chooser only return results representing data that + * the launched content chooser only returns results representing data that * is locally available on the device. For example, if this extra is set * to true then an image picker should not show any pictures that are available * from a remote server but not already on the local device (thus requiring @@ -1137,7 +1137,7 @@ public class Intent implements Parcelable, Cloneable { /** * Activity Action: The user pressed the "Report" button in the crash/ANR dialog. * This intent is delivered to the package which installed the application, usually - * the Market. + * Google Play. * <p>Input: No data is specified. The bug report is passed in using * an {@link #EXTRA_BUG_REPORT} field. * <p>Output: Nothing. @@ -1908,14 +1908,14 @@ public class Intent implements Parcelable, Cloneable { // location; they are not general-purpose actions. /** - * Broadcast Action: An GTalk connection has been established. + * Broadcast Action: A GTalk connection has been established. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_GTALK_SERVICE_CONNECTED = "android.intent.action.GTALK_CONNECTED"; /** - * Broadcast Action: An GTalk connection has been disconnected. + * Broadcast Action: A GTalk connection has been disconnected. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_GTALK_SERVICE_DISCONNECTED = @@ -2200,7 +2200,7 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE"; /** - * Intended to be used as a tab inside of an containing TabActivity. + * Intended to be used as a tab inside of a containing TabActivity. */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_TAB = "android.intent.category.TAB"; @@ -2256,7 +2256,7 @@ public class Intent implements Parcelable, Cloneable { */ public static final String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST"; /** - * To be used as an sample code example (not part of the normal user + * To be used as a sample code example (not part of the normal user * experience). */ public static final String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE"; @@ -2506,7 +2506,7 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_KEY_CONFIRM = "android.intent.extra.KEY_CONFIRM"; /** - * Used as an boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} or + * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} or * {@link android.content.Intent#ACTION_PACKAGE_CHANGED} intents to override the default action * of restarting the application. */ @@ -2688,12 +2688,20 @@ public class Intent implements Parcelable, Cloneable { /** * If set, the recipient of this Intent will be granted permission to - * perform read operations on the Uri in the Intent's data. + * perform read operations on the Uri in the Intent's data and any URIs + * specified in its ClipData. When applying to an Intent's ClipData, + * all URIs as well as recursive traversals through data or other ClipData + * in Intent items will be granted; only the grant flags of the top-level + * Intent are used. */ public static final int FLAG_GRANT_READ_URI_PERMISSION = 0x00000001; /** * If set, the recipient of this Intent will be granted permission to - * perform write operations on the Uri in the Intent's data. + * perform write operations on the Uri in the Intent's data and any URIs + * specified in its ClipData. When applying to an Intent's ClipData, + * all URIs as well as recursive traversals through data or other ClipData + * in Intent items will be granted; only the grant flags of the top-level + * Intent are used. */ public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 0x00000002; /** @@ -3018,6 +3026,7 @@ public class Intent implements Parcelable, Cloneable { private Bundle mExtras; private Rect mSourceBounds; private Intent mSelector; + private ClipData mClipData; // --------------------------------------------------------------------- @@ -3049,6 +3058,9 @@ public class Intent implements Parcelable, Cloneable { if (o.mSelector != null) { this.mSelector = new Intent(o.mSelector); } + if (o.mClipData != null) { + this.mClipData = new ClipData(o.mClipData); + } } @Override @@ -3692,7 +3704,7 @@ public class Intent implements Parcelable, Cloneable { } /** - * Check if an category exists in the intent. + * Check if a category exists in the intent. * * @param category The category to check. * @@ -3729,6 +3741,16 @@ public class Intent implements Parcelable, Cloneable { } /** + * Return the {@link ClipData} associated with this Intent. If there is + * none, returns null. See {@link #setClipData} for more information. + * + * @see #setClipData; + */ + public ClipData getClipData() { + return mClipData; + } + + /** * Sets the ClassLoader that will be used when unmarshalling * any Parcelable values from the extras of this Intent. * @@ -4601,7 +4623,7 @@ public class Intent implements Parcelable, Cloneable { /** * Add a new category to the intent. Categories provide additional detail - * about the action the intent is perform. When resolving an intent, only + * about the action the intent performs. When resolving an intent, only * activities that provide <em>all</em> of the requested categories will be * used. * @@ -4624,7 +4646,7 @@ public class Intent implements Parcelable, Cloneable { } /** - * Remove an category from an intent. + * Remove a category from an intent. * * @param category The category to remove. * @@ -4683,6 +4705,37 @@ public class Intent implements Parcelable, Cloneable { } /** + * Set a {@link ClipData} associated with this Intent. This replaces any + * previously set ClipData. + * + * <p>The ClipData in an intent is not used for Intent matching or other + * such operations. Semantically it is like extras, used to transmit + * additional data with the Intent. The main feature of using this over + * the extras for data is that {@link #FLAG_GRANT_READ_URI_PERMISSION} + * and {@link #FLAG_GRANT_WRITE_URI_PERMISSION} will operate on any URI + * items included in the clip data. This is useful, in particular, if + * you want to transmit an Intent containing multiple <code>content:</code> + * URIs for which the recipient may not have global permission to access the + * content provider. + * + * <p>If the ClipData contains items that are themselves Intents, any + * grant flags in those Intents will be ignored. Only the top-level flags + * of the main Intent are respected, and will be applied to all Uri or + * Intent items in the clip (or sub-items of the clip). + * + * <p>The MIME type, label, and icon in the ClipData object are not + * directly used by Intent. Applications should generally rely on the + * MIME type of the Intent itself, not what it may find in the ClipData. + * A common practice is to construct a ClipData for use with an Intent + * with a MIME type of "*\/*". + * + * @param clip The new clip to set. May be null to clear the current clip. + */ + public void setClipData(ClipData clip) { + mClipData = clip; + } + + /** * Add extended data to the intent. The name must include a package * prefix, for example the app com.android.contacts would use names * like "com.android.contacts.ShowAll". @@ -5660,6 +5713,12 @@ public class Intent implements Parcelable, Cloneable { public static final int FILL_IN_SELECTOR = 1<<6; /** + * Use with {@link #fillIn} to allow the current ClipData to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_CLIP_DATA = 1<<7; + + /** * Copy the contents of <var>other</var> in to this object, but only * where fields are not defined by this object. For purposes of a field * being defined, the following pieces of data in the Intent are @@ -5673,19 +5732,22 @@ public class Intent implements Parcelable, Cloneable { * <li> package, as set by {@link #setPackage}. * <li> component, as set by {@link #setComponent(ComponentName)} or * related methods. - * <li> source bounds, as set by {@link #setSourceBounds} + * <li> source bounds, as set by {@link #setSourceBounds}. + * <li> selector, as set by {@link #setSelector(Intent)}. + * <li> clip data, as set by {@link #setClipData(ClipData)}. * <li> each top-level name in the associated extras. * </ul> * * <p>In addition, you can use the {@link #FILL_IN_ACTION}, * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE}, - * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS}, and - * {@link #FILL_IN_SELECTOR} to override the restriction where the - * corresponding field will not be replaced if it is already set. + * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS}, + * {@link #FILL_IN_SELECTOR}, and {@link #FILL_IN_CLIP_DATA} to override + * the restriction where the corresponding field will not be replaced if + * it is already set. * - * <p>Note: The component field will only be copied if {@link #FILL_IN_COMPONENT} is explicitly - * specified. The selector will only be copied if {@link #FILL_IN_SELECTOR} is - * explicitly specified. + * <p>Note: The component field will only be copied if {@link #FILL_IN_COMPONENT} + * is explicitly specified. The selector will only be copied if + * {@link #FILL_IN_SELECTOR} is explicitly specified. * * <p>For example, consider Intent A with {data="foo", categories="bar"} * and Intent B with {action="gotit", data-type="some/thing", @@ -5742,6 +5804,11 @@ public class Intent implements Parcelable, Cloneable { changes |= FILL_IN_SELECTOR; } } + if (other.mClipData != null + && (mClipData == null || (flags&FILL_IN_CLIP_DATA) != 0)) { + mClipData = other.mClipData; + changes |= FILL_IN_CLIP_DATA; + } // Component is special: it can -only- be set if explicitly allowed, // since otherwise the sender could force the intent somewhere the // originator didn't intend. @@ -5938,7 +6005,7 @@ public class Intent implements Parcelable, Cloneable { StringBuilder b = new StringBuilder(128); b.append("Intent { "); - toShortString(b, true, true, true); + toShortString(b, true, true, true, false); b.append(" }"); return b.toString(); @@ -5949,21 +6016,33 @@ public class Intent implements Parcelable, Cloneable { StringBuilder b = new StringBuilder(128); b.append("Intent { "); - toShortString(b, false, true, true); + toShortString(b, false, true, true, false); + b.append(" }"); + + return b.toString(); + } + + /** @hide */ + public String toInsecureStringWithClip() { + StringBuilder b = new StringBuilder(128); + + b.append("Intent { "); + toShortString(b, false, true, true, true); b.append(" }"); return b.toString(); } /** @hide */ - public String toShortString(boolean secure, boolean comp, boolean extras) { + public String toShortString(boolean secure, boolean comp, boolean extras, boolean clip) { StringBuilder b = new StringBuilder(128); - toShortString(b, secure, comp, extras); + toShortString(b, secure, comp, extras, clip); return b.toString(); } /** @hide */ - public void toShortString(StringBuilder b, boolean secure, boolean comp, boolean extras) { + public void toShortString(StringBuilder b, boolean secure, boolean comp, boolean extras, + boolean clip) { boolean first = true; if (mAction != null) { b.append("act=").append(mAction); @@ -6031,6 +6110,19 @@ public class Intent implements Parcelable, Cloneable { first = false; b.append("bnds=").append(mSourceBounds.toShortString()); } + if (mClipData != null) { + if (!first) { + b.append(' '); + } + first = false; + if (clip) { + b.append("clip={"); + mClipData.toShortString(b); + b.append('}'); + } else { + b.append("(has clip)"); + } + } if (extras && mExtras != null) { if (!first) { b.append(' '); @@ -6040,7 +6132,7 @@ public class Intent implements Parcelable, Cloneable { } if (mSelector != null) { b.append(" sel={"); - mSelector.toShortString(b, secure, comp, extras); + mSelector.toShortString(b, secure, comp, extras, clip); b.append("}"); } } @@ -6209,6 +6301,13 @@ public class Intent implements Parcelable, Cloneable { out.writeInt(0); } + if (mClipData != null) { + out.writeInt(1); + mClipData.writeToParcel(out, flags); + } else { + out.writeInt(0); + } + out.writeBundle(mExtras); } @@ -6254,6 +6353,10 @@ public class Intent implements Parcelable, Cloneable { mSelector = new Intent(in); } + if (in.readInt() != 0) { + mClipData = new ClipData(in); + } + mExtras = in.readBundle(); } diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index ba24036..b7dfe92 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -23,18 +23,23 @@ import com.google.android.collect.Maps; import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountManagerService; import android.accounts.OnAccountsUpdateListener; import android.app.ActivityManager; import android.app.AlarmManager; +import android.app.AppGlobals; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.DownloadManager.Request; +import android.content.SyncStorageEngine.OnSyncRequestListener; 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; @@ -48,6 +53,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserId; import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; @@ -132,7 +138,9 @@ public class SyncManager implements OnAccountsUpdateListener { private Context mContext; - private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY; + private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0]; + + private volatile AccountAndUser[] mAccounts = INITIAL_ACCOUNTS_ARRAY; volatile private PowerManager.WakeLock mHandleAlarmWakeLock; volatile private PowerManager.WakeLock mSyncManagerWakeLock; @@ -166,7 +174,8 @@ public class SyncManager implements OnAccountsUpdateListener { Log.v(TAG, "Internal storage is low."); } mStorageIsLow = true; - cancelActiveSync(null /* any account */, null /* any authority */); + cancelActiveSync(null /* any account */, UserId.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."); @@ -186,28 +195,73 @@ public class SyncManager implements OnAccountsUpdateListener { private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (getConnectivityManager().getBackgroundDataSetting()) { - scheduleSync(null /* account */, null /* authority */, new Bundle(), 0 /* delay */, + scheduleSync(null /* account */, UserId.USER_ALL, null /* authority */, + new Bundle(), 0 /* delay */, false /* onlyThoseWithUnknownSyncableState */); } } }; - private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0]; - private final PowerManager mPowerManager; 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() { + try { + return AppGlobals.getPackageManager().getUsers(); + } catch (RemoteException re) { + // Local to system process, shouldn't happen + } + return null; + } + + 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 onAccountsUpdated(Account[] accounts) { // remember if this was the first time this was called after an update final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY; - mAccounts = accounts; - // if a sync is in progress yet it is no longer in the accounts list, - // cancel it + List<UserInfo> users = getAllUsers(); + if (users == null) return; + + int count = 0; + + // For all known users on the system, get their accounts and add them to the list + // TODO: Limit this to active users, when such a concept exists. + for (UserInfo user : users) { + accounts = AccountManagerService.getSingleton().getAccounts(user.id); + count += accounts.length; + } + + AccountAndUser[] allAccounts = new AccountAndUser[count]; + int index = 0; + for (UserInfo user : users) { + accounts = AccountManagerService.getSingleton().getAccounts(user.id); + for (Account account : accounts) { + allAccounts[index++] = new AccountAndUser(account, user.id); + } + if (mBootCompleted) { + mSyncStorageEngine.doDatabaseCleanup(accounts, user.id); + } + } + + mAccounts = allAccounts; + for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { - if (!ArrayUtils.contains(accounts, currentSyncContext.mSyncOperation.account)) { + if (!containsAccountAndUser(allAccounts, + currentSyncContext.mSyncOperation.account, + currentSyncContext.mSyncOperation.userId)) { Log.d(TAG, "canceling sync since the account has been removed"); sendSyncFinishedOrCanceledMessage(currentSyncContext, null /* no result since this is a cancel */); @@ -218,11 +272,7 @@ public class SyncManager implements OnAccountsUpdateListener { // the accounts are not set yet sendCheckAlarmsMessage(); - if (mBootCompleted) { - mSyncStorageEngine.doDatabaseCleanup(accounts); - } - - if (accounts.length > 0) { + if (allAccounts.length > 0) { // If this is the first time this was called after a bootup then // the accounts haven't really changed, instead they were just loaded // from the AccountManager. Otherwise at least one of the accounts @@ -238,7 +288,8 @@ public class SyncManager implements OnAccountsUpdateListener { // a chance to set their syncable state. boolean onlyThoseWithUnkownSyncableState = justBootedUp; - scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState); + scheduleSync(null, UserId.USER_ALL, null, null, 0 /* no delay */, + onlyThoseWithUnkownSyncableState); } } @@ -277,10 +328,36 @@ public class SyncManager implements OnAccountsUpdateListener { private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; private final SyncHandler mSyncHandler; - private final Handler mMainHandler; private volatile boolean mBootCompleted = false; + static class AccountAndUser { + Account account; + int userId; + + AccountAndUser(Account account, int userId) { + this.account = account; + this.userId = userId; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AccountAndUser)) return false; + final AccountAndUser other = (AccountAndUser) o; + return this.account.equals(other.account) + && this.userId == other.userId; + } + + @Override + public int hashCode() { + return account.hashCode() + userId; + } + + public String toString() { + return account.toString() + " u" + userId; + } + } + private ConnectivityManager getConnectivityManager() { synchronized (this) { if (mConnManagerDoNotUseDirectly == null) { @@ -297,6 +374,13 @@ public class SyncManager implements OnAccountsUpdateListener { mContext = context; SyncStorageEngine.init(context); mSyncStorageEngine = SyncStorageEngine.getSingleton(); + mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { + public void onSyncRequest(Account account, int userId, String authority, + Bundle extras) { + scheduleSync(account, userId, authority, extras, 0, false); + } + }); + mSyncAdapters = new SyncAdaptersCache(mContext); mSyncQueue = new SyncQueue(mSyncStorageEngine, mSyncAdapters); @@ -304,12 +388,11 @@ public class SyncManager implements OnAccountsUpdateListener { Process.THREAD_PRIORITY_BACKGROUND); syncThread.start(); mSyncHandler = new SyncHandler(syncThread.getLooper()); - mMainHandler = new Handler(mContext.getMainLooper()); mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { public void onServiceChanged(SyncAdapterType type, boolean removed) { if (!removed) { - scheduleSync(null, type.authority, null, 0 /* no delay */, + scheduleSync(null, UserId.USER_ALL, type.authority, null, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } @@ -376,7 +459,7 @@ public class SyncManager implements OnAccountsUpdateListener { AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this, mSyncHandler, false /* updateImmediately */); // do this synchronously to ensure we have the accounts before this call returns - onAccountsUpdated(AccountManager.get(mContext).getAccounts()); + onAccountsUpdated(null); } } @@ -404,82 +487,6 @@ public class SyncManager implements OnAccountsUpdateListener { } } - private void initializeSyncAdapter(Account account, String authority) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "initializeSyncAdapter: " + account + ", authority " + authority); - } - SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type); - RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = - mSyncAdapters.getServiceInfo(syncAdapterType); - if (syncAdapterInfo == null) { - Log.w(TAG, "can't find a sync adapter for " + syncAdapterType + ", removing"); - mSyncStorageEngine.removeAuthority(account, authority); - return; - } - - Intent intent = new Intent(); - intent.setAction("android.content.SyncAdapter"); - intent.setComponent(syncAdapterInfo.componentName); - if (!mContext.bindService(intent, - new InitializerServiceConnection(account, authority, mContext, mMainHandler), - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND - | Context.BIND_ALLOW_OOM_MANAGEMENT)) { - Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent); - } - } - - private static class InitializerServiceConnection implements ServiceConnection { - private final Account mAccount; - private final String mAuthority; - private final Handler mHandler; - private volatile Context mContext; - private volatile boolean mInitialized; - - public InitializerServiceConnection(Account account, String authority, Context context, - Handler handler) { - mAccount = account; - mAuthority = authority; - mContext = context; - mHandler = handler; - mInitialized = false; - } - - public void onServiceConnected(ComponentName name, IBinder service) { - try { - if (!mInitialized) { - mInitialized = true; - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "calling initialize: " + mAccount + ", authority " + mAuthority); - } - ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority); - } - } catch (RemoteException e) { - // doesn't matter, we will retry again later - Log.d(TAG, "error while initializing: " + mAccount + ", authority " + mAuthority, - e); - } finally { - // give the sync adapter time to initialize before unbinding from it - // TODO: change this API to not rely on this timing, http://b/2500805 - mHandler.postDelayed(new Runnable() { - public void run() { - if (mContext != null) { - mContext.unbindService(InitializerServiceConnection.this); - mContext = null; - } - } - }, INITIALIZATION_UNBIND_DELAY_MS); - } - } - - public void onServiceDisconnected(ComponentName name) { - if (mContext != null) { - mContext.unbindService(InitializerServiceConnection.this); - mContext = null; - } - } - - } - /** * Initiate a sync. This can start a sync for all providers * (pass null to url, set onlyTicklable to false), only those @@ -500,6 +507,8 @@ public class SyncManager implements OnAccountsUpdateListener { * <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 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 @@ -507,7 +516,7 @@ public class SyncManager implements OnAccountsUpdateListener { * @param delay how many milliseconds in the future to wait before performing this * @param onlyThoseWithUnkownSyncableState */ - public void scheduleSync(Account requestedAccount, String requestedAuthority, + public void scheduleSync(Account requestedAccount, int userId, String requestedAuthority, Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) { boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); @@ -521,9 +530,9 @@ public class SyncManager implements OnAccountsUpdateListener { delay = -1; // this means schedule at the front of the queue } - Account[] accounts; - if (requestedAccount != null) { - accounts = new Account[]{requestedAccount}; + AccountAndUser[] accounts; + if (requestedAccount != null && userId != UserId.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 @@ -574,24 +583,23 @@ public class SyncManager implements OnAccountsUpdateListener { if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority); } - final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically(); - for (String authority : syncableAuthorities) { - for (Account account : accounts) { - int isSyncable = mSyncStorageEngine.getIsSyncable(account, authority); + for (AccountAndUser account : accounts) { + int isSyncable = mSyncStorageEngine.getIsSyncable(account.account, account.userId, + authority); if (isSyncable == 0) { continue; } final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo( - SyncAdapterType.newKey(authority, account.type)); + SyncAdapterType.newKey(authority, account.account.type)); if (syncAdapterInfo == null) { continue; } final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs(); final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable(); if (isSyncable < 0 && isAlwaysSyncable) { - mSyncStorageEngine.setIsSyncable(account, authority, 1); + mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1); isSyncable = 1; } if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { @@ -605,8 +613,10 @@ public class SyncManager implements OnAccountsUpdateListener { boolean syncAllowed = (isSyncable < 0) || ignoreSettings - || (backgroundDataUsageAllowed && masterSyncAutomatically - && mSyncStorageEngine.getSyncAutomatically(account, authority)); + || (backgroundDataUsageAllowed + && mSyncStorageEngine.getMasterSyncAutomatically(account.userId) + && mSyncStorageEngine.getSyncAutomatically(account.account, + account.userId, authority)); if (!syncAllowed) { if (isLoggable) { Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority @@ -615,8 +625,10 @@ public class SyncManager implements OnAccountsUpdateListener { continue; } - Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(account, authority); - long delayUntil = mSyncStorageEngine.getDelayUntilTime(account, authority); + 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(); @@ -630,9 +642,8 @@ public class SyncManager implements OnAccountsUpdateListener { + ", extras " + newExtras); } scheduleSyncOperation( - new SyncOperation(account, source, authority, newExtras, 0, - backoffTime, delayUntil, - allowParallelSyncs)); + new SyncOperation(account.account, account.userId, source, authority, + newExtras, 0, backoffTime, delayUntil, allowParallelSyncs)); } if (!onlyThoseWithUnkownSyncableState) { if (isLoggable) { @@ -644,18 +655,17 @@ public class SyncManager implements OnAccountsUpdateListener { + ", extras " + extras); } scheduleSyncOperation( - new SyncOperation(account, source, authority, extras, delay, - backoffTime, delayUntil, - allowParallelSyncs)); + new SyncOperation(account.account, account.userId, source, authority, + extras, delay, backoffTime, delayUntil, allowParallelSyncs)); } } } } - public void scheduleLocalSync(Account account, String authority) { + public void scheduleLocalSync(Account account, int userId, String authority) { final Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); - scheduleSync(account, authority, extras, LOCAL_SYNC_DELAY, + scheduleSync(account, userId, authority, extras, LOCAL_SYNC_DELAY, false /* onlyThoseWithUnkownSyncableState */); } @@ -691,11 +701,13 @@ public class SyncManager implements OnAccountsUpdateListener { mSyncHandler.sendMessage(msg); } - private void sendCancelSyncsMessage(final Account account, final String authority) { + 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); } @@ -717,10 +729,10 @@ public class SyncManager implements OnAccountsUpdateListener { } private void clearBackoffSetting(SyncOperation op) { - mSyncStorageEngine.setBackoff(op.account, op.authority, + 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.authority, 0); + mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0); } } @@ -728,7 +740,7 @@ public class SyncManager implements OnAccountsUpdateListener { final long now = SystemClock.elapsedRealtime(); final Pair<Long, Long> previousSettings = - mSyncStorageEngine.getBackoff(op.account, op.authority); + 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 @@ -759,14 +771,14 @@ public class SyncManager implements OnAccountsUpdateListener { final long backoff = now + newDelayInMs; - mSyncStorageEngine.setBackoff(op.account, op.authority, + mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority, backoff, newDelayInMs); op.backoff = backoff; op.updateEffectiveRunTime(); synchronized (mSyncQueue) { - mSyncQueue.onBackoffChanged(op.account, op.authority, backoff); + mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff); } } @@ -779,7 +791,8 @@ public class SyncManager implements OnAccountsUpdateListener { } else { newDelayUntilTime = 0; } - mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime); + mSyncStorageEngine + .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime); synchronized (mSyncQueue) { mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime); } @@ -790,8 +803,8 @@ public class SyncManager implements OnAccountsUpdateListener { * @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, String authority) { - sendCancelSyncsMessage(account, authority); + public void cancelActiveSync(Account account, int userId, String authority) { + sendCancelSyncsMessage(account, userId, authority); } /** @@ -823,11 +836,11 @@ public class SyncManager implements OnAccountsUpdateListener { * @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, String authority) { + public void clearScheduledSyncOperations(Account account, int userId, String authority) { synchronized (mSyncQueue) { - mSyncQueue.remove(account, authority); + mSyncQueue.remove(account, userId, authority); } - mSyncStorageEngine.setBackoff(account, authority, + mSyncStorageEngine.setBackoff(account, userId, authority, SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); } @@ -875,7 +888,8 @@ public class SyncManager implements OnAccountsUpdateListener { Log.d(TAG, "retrying sync operation that failed because there was already a " + "sync in progress: " + operation); } - scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource, + scheduleSyncOperation(new SyncOperation(operation.account, operation.userId, + operation.syncSource, operation.authority, operation.extras, DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, operation.backoff, operation.delayUntil, operation.allowParallelSyncs)); @@ -979,7 +993,8 @@ public class SyncManager implements OnAccountsUpdateListener { mBound = true; final boolean bindResult = mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND - | Context.BIND_ALLOW_OOM_MANAGEMENT); + | Context.BIND_ALLOW_OOM_MANAGEMENT, + mSyncOperation.userId); if (!bindResult) { mBound = false; } @@ -1034,10 +1049,19 @@ public class SyncManager implements OnAccountsUpdateListener { protected void dumpSyncState(PrintWriter pw) { pw.print("data connected: "); pw.println(mDataConnectionIsConnected); - pw.print("auto sync: "); pw.println(mSyncStorageEngine.getMasterSyncAutomatically()); + 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 Account[] accounts = mAccounts; + final AccountAndUser[] accounts = mAccounts; + pw.print("accounts: "); if (accounts != INITIAL_ACCOUNTS_ARRAY) { pw.println(accounts.length); @@ -1090,18 +1114,20 @@ public class SyncManager implements OnAccountsUpdateListener { // join the installed sync adapter with the accounts list and emit for everything pw.println(); pw.println("Sync Status"); - for (Account account : accounts) { - pw.print(" Account "); pw.print(account.name); - pw.print(" "); pw.print(account.type); + for (AccountAndUser account : accounts) { + pw.print(" Account "); pw.print(account.account.name); + pw.print(" u"); pw.print(account.userId); + pw.print(" "); pw.print(account.account.type); pw.println(":"); for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : mSyncAdapters.getAllServices()) { - if (!syncAdapterType.type.accountType.equals(account.type)) { + if (!syncAdapterType.type.accountType.equals(account.account.type)) { continue; } - SyncStorageEngine.AuthorityInfo settings = mSyncStorageEngine.getOrCreateAuthority( - account, syncAdapterType.type.authority); + SyncStorageEngine.AuthorityInfo settings = + mSyncStorageEngine.getOrCreateAuthority( + account.account, account.userId, syncAdapterType.type.authority); SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings); pw.print(" "); pw.print(settings.authority); pw.println(":"); @@ -1554,7 +1580,16 @@ public class SyncManager implements OnAccountsUpdateListener { private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1); public void onBootCompleted() { mBootCompleted = true; - mSyncStorageEngine.doDatabaseCleanup(AccountManager.get(mContext).getAccounts()); + // TODO: Handle bootcompleted event for specific user. Now let's just iterate through + // all the users. + List<UserInfo> users = getAllUsers(); + if (users != null) { + for (UserInfo user : users) { + mSyncStorageEngine.doDatabaseCleanup( + AccountManagerService.getSingleton().getAccounts(user.id), + user.id); + } + } if (mReadyToRunLatch != null) { mReadyToRunLatch.countDown(); } @@ -1635,7 +1670,7 @@ public class SyncManager implements OnAccountsUpdateListener { Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: " + payload.first + ", " + payload.second); } - cancelActiveSyncLocked(payload.first, payload.second); + cancelActiveSyncLocked(payload.first, msg.arg1, payload.second); nextPendingSyncTime = maybeStartNextSyncLocked(); break; } @@ -1740,22 +1775,28 @@ public class SyncManager implements OnAccountsUpdateListener { final boolean backgroundDataUsageAllowed = getConnectivityManager().getBackgroundDataSetting(); long earliestFuturePollTime = Long.MAX_VALUE; - if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) { + if (!backgroundDataUsageAllowed) { return earliestFuturePollTime; } + + AccountAndUser[] accounts = mAccounts; + final long nowAbsolute = System.currentTimeMillis(); ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities(); for (SyncStorageEngine.AuthorityInfo info : infos) { // skip the sync if the account of this operation no longer exists - if (!ArrayUtils.contains(mAccounts, info.account)) { + if (!containsAccountAndUser(accounts, info.account, info.userId)) { continue; } - if (!mSyncStorageEngine.getSyncAutomatically(info.account, info.authority)) { + if (!mSyncStorageEngine.getMasterSyncAutomatically(info.userId) + || !mSyncStorageEngine.getSyncAutomatically(info.account, info.userId, + info.authority)) { continue; } - if (mSyncStorageEngine.getIsSyncable(info.account, info.authority) == 0) { + if (mSyncStorageEngine.getIsSyncable(info.account, info.userId, info.authority) + == 0) { continue; } @@ -1772,8 +1813,8 @@ public class SyncManager implements OnAccountsUpdateListener { : lastPollTimeAbsolute + periodInSeconds * 1000; // if it is ready to run then schedule it and mark it as having been scheduled if (nextPollTimeAbsolute <= nowAbsolute) { - final Pair<Long, Long> backoff = - mSyncStorageEngine.getBackoff(info.account, info.authority); + final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff( + info.account, info.userId, info.authority); final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo( SyncAdapterType.newKey(info.authority, info.account.type)); @@ -1781,11 +1822,12 @@ public class SyncManager implements OnAccountsUpdateListener { continue; } scheduleSyncOperation( - new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC, + new SyncOperation(info.account, info.userId, + SyncStorageEngine.SOURCE_PERIODIC, info.authority, extras, 0 /* delay */, backoff != null ? backoff.first : 0, mSyncStorageEngine.getDelayUntilTime( - info.account, info.authority), + info.account, info.userId, info.authority), syncAdapterInfo.type.allowParallelSyncs())); status.setPeriodicSyncTime(i, nowAbsolute); } else { @@ -1830,7 +1872,7 @@ public class SyncManager implements OnAccountsUpdateListener { // 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. - Account[] accounts = mAccounts; + AccountAndUser[] accounts = mAccounts; if (accounts == INITIAL_ACCOUNTS_ARRAY) { if (isLoggable) { Log.v(TAG, "maybeStartNextSync: accounts not known, skipping"); @@ -1843,7 +1885,6 @@ public class SyncManager implements OnAccountsUpdateListener { // start it, otherwise just get out. final boolean backgroundDataUsageAllowed = getConnectivityManager().getBackgroundDataSetting(); - final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically(); final long now = SystemClock.elapsedRealtime(); @@ -1863,14 +1904,15 @@ public class SyncManager implements OnAccountsUpdateListener { final SyncOperation op = operationIterator.next(); // drop the sync if the account of this operation no longer exists - if (!ArrayUtils.contains(mAccounts, op.account)) { + 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.authority); + int syncableState = mSyncStorageEngine.getIsSyncable( + op.account, op.userId, op.authority); if (syncableState == 0) { operationIterator.remove(); mSyncStorageEngine.deleteFromPending(op.pendingOperation); @@ -1905,11 +1947,11 @@ public class SyncManager implements OnAccountsUpdateListener { // disconnected for the target UID. if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false) && (syncableState > 0) - && (!masterSyncAutomatically + && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId) || !backgroundDataUsageAllowed || !uidNetworkConnected || !mSyncStorageEngine.getSyncAutomatically( - op.account, op.authority))) { + op.account, op.userId, op.authority))) { operationIterator.remove(); mSyncStorageEngine.deleteFromPending(op.pendingOperation); continue; @@ -1946,6 +1988,7 @@ public class SyncManager implements OnAccountsUpdateListener { } 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; @@ -2033,7 +2076,7 @@ public class SyncManager implements OnAccountsUpdateListener { if (syncAdapterInfo == null) { Log.d(TAG, "can't find a sync adapter for " + syncAdapterType + ", removing settings for it"); - mSyncStorageEngine.removeAuthority(op.account, op.authority); + mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority); return false; } @@ -2074,7 +2117,7 @@ public class SyncManager implements OnAccountsUpdateListener { } } - private void cancelActiveSyncLocked(Account account, String authority) { + private void cancelActiveSyncLocked(Account account, int userId, String authority) { ArrayList<ActiveSyncContext> activeSyncs = new ArrayList<ActiveSyncContext>(mActiveSyncContexts); for (ActiveSyncContext activeSyncContext : activeSyncs) { @@ -2082,15 +2125,20 @@ public class SyncManager implements OnAccountsUpdateListener { // if an authority was specified then only cancel the sync if it matches if (account != null) { if (!account.equals(activeSyncContext.mSyncOperation.account)) { - return; + continue; } } // if an account was specified then only cancel the sync if it matches if (authority != null) { if (!authority.equals(activeSyncContext.mSyncOperation.authority)) { - return; + continue; } } + // check if the userid matches + if (userId != UserId.USER_ALL + && userId != activeSyncContext.mSyncOperation.userId) { + continue; + } runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */, activeSyncContext); } @@ -2169,7 +2217,7 @@ public class SyncManager implements OnAccountsUpdateListener { } if (syncResult != null && syncResult.fullSyncRequested) { - scheduleSyncOperation(new SyncOperation(syncOperation.account, + scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId, syncOperation.syncSource, syncOperation.authority, new Bundle(), 0, syncOperation.backoff, syncOperation.delayUntil, syncOperation.allowParallelSyncs)); @@ -2180,7 +2228,8 @@ public class SyncManager implements OnAccountsUpdateListener { private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) { activeSyncContext.close(); mActiveSyncContexts.remove(activeSyncContext); - mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo); + mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo, + activeSyncContext.mSyncOperation.userId); } /** @@ -2446,7 +2495,8 @@ public class SyncManager implements OnAccountsUpdateListener { syncOperation.account.name.hashCode()); return mSyncStorageEngine.insertStartSyncEvent( - syncOperation.account, syncOperation.authority, now, source); + syncOperation.account, syncOperation.userId, syncOperation.authority, + now, source); } public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage, diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java index 94c2247..4e86ef8 100644 --- a/core/java/android/content/SyncOperation.java +++ b/core/java/android/content/SyncOperation.java @@ -26,6 +26,7 @@ import android.os.SystemClock; */ public class SyncOperation implements Comparable { public final Account account; + public final int userId; public int syncSource; public String authority; public final boolean allowParallelSyncs; @@ -38,9 +39,10 @@ public class SyncOperation implements Comparable { public long delayUntil; public long effectiveRunTime; - public SyncOperation(Account account, int source, String authority, Bundle extras, + public SyncOperation(Account account, int userId, int source, String authority, Bundle extras, long delayInMs, long backoff, long delayUntil, boolean allowParallelSyncs) { this.account = account; + this.userId = userId; this.syncSource = source; this.authority = authority; this.allowParallelSyncs = allowParallelSyncs; @@ -75,6 +77,7 @@ public class SyncOperation implements Comparable { SyncOperation(SyncOperation other) { this.account = other.account; + this.userId = other.userId; this.syncSource = other.syncSource; this.authority = other.authority; this.extras = new Bundle(other.extras); @@ -120,7 +123,8 @@ public class SyncOperation implements Comparable { private String toKey() { StringBuilder sb = new StringBuilder(); sb.append("authority: ").append(authority); - sb.append(" account {name=" + account.name + ", type=" + account.type + "}"); + sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type + + "}"); sb.append(" extras: "); extrasToStringBuilder(extras, sb); return sb.toString(); diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java index bfdf4a1..06da6fa 100644 --- a/core/java/android/content/SyncQueue.java +++ b/core/java/android/content/SyncQueue.java @@ -49,7 +49,8 @@ public class SyncQueue { final int N = ops.size(); for (int i=0; i<N; i++) { SyncStorageEngine.PendingOperation op = ops.get(i); - final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority); + final Pair<Long, Long> backoff = + syncStorageEngine.getBackoff(op.account, op.userId, op.authority); final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = syncAdapters.getServiceInfo( SyncAdapterType.newKey(op.authority, op.account.type)); @@ -57,9 +58,9 @@ public class SyncQueue { continue; } SyncOperation syncOperation = new SyncOperation( - op.account, op.syncSource, op.authority, op.extras, 0 /* delay */, + op.account, op.userId, op.syncSource, op.authority, op.extras, 0 /* delay */, backoff != null ? backoff.first : 0, - syncStorageEngine.getDelayUntilTime(op.account, op.authority), + syncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority), syncAdapterInfo.type.allowParallelSyncs()); syncOperation.expedited = op.expedited; syncOperation.pendingOperation = op; @@ -102,8 +103,8 @@ public class SyncQueue { operation.pendingOperation = pop; if (operation.pendingOperation == null) { pop = new SyncStorageEngine.PendingOperation( - operation.account, operation.syncSource, - operation.authority, operation.extras, operation.expedited); + operation.account, operation.userId, operation.syncSource, + operation.authority, operation.extras, operation.expedited); pop = mSyncStorageEngine.insertIntoPending(pop); if (pop == null) { throw new IllegalStateException("error adding pending sync operation " @@ -131,11 +132,12 @@ public class SyncQueue { } } - public void onBackoffChanged(Account account, String providerName, long backoff) { + 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)) { + if (op.account.equals(account) && op.authority.equals(providerName) + && op.userId == userId) { op.backoff = backoff; op.updateEffectiveRunTime(); } @@ -153,7 +155,7 @@ public class SyncQueue { } } - public void remove(Account account, String authority) { + 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(); @@ -164,6 +166,9 @@ public class SyncQueue { 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; diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index a1e174b..7bb9866 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -25,6 +25,7 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.accounts.Account; +import android.content.SyncManager.AccountAndUser; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; @@ -58,9 +59,16 @@ import java.util.List; * @hide */ public class SyncStorageEngine extends Handler { + private static final String TAG = "SyncManager"; 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_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 @@ -133,6 +141,7 @@ public class SyncStorageEngine extends Handler { public static class PendingOperation { final Account account; + final int userId; final int syncSource; final String authority; final Bundle extras; // note: read-only. @@ -141,9 +150,10 @@ public class SyncStorageEngine extends Handler { int authorityId; byte[] flatExtras; - PendingOperation(Account account, int source, + PendingOperation(Account account, int userId, int source, String authority, Bundle extras, boolean expedited) { this.account = account; + this.userId = userId; this.syncSource = source; this.authority = authority; this.extras = extras != null ? new Bundle(extras) : extras; @@ -153,6 +163,7 @@ public class SyncStorageEngine extends Handler { PendingOperation(PendingOperation other) { this.account = other.account; + this.userId = other.userId; this.syncSource = other.syncSource; this.authority = other.authority; this.extras = other.extras; @@ -162,17 +173,18 @@ public class SyncStorageEngine extends Handler { } static class AccountInfo { - final Account account; + final AccountAndUser accountAndUser; final HashMap<String, AuthorityInfo> authorities = new HashMap<String, AuthorityInfo>(); - AccountInfo(Account account) { - this.account = account; + AccountInfo(AccountAndUser accountAndUser) { + this.accountAndUser = accountAndUser; } } public static class AuthorityInfo { final Account account; + final int userId; final String authority; final int ident; boolean enabled; @@ -182,8 +194,9 @@ public class SyncStorageEngine extends Handler { long delayUntil; final ArrayList<Pair<Bundle, Long>> periodicSyncs; - AuthorityInfo(Account account, String authority, int ident) { + 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; @@ -219,17 +232,29 @@ public class SyncStorageEngine extends Handler { } } + interface OnSyncRequestListener { + /** + * Called when a sync is needed on an account(s) due to some change in state. + * @param account + * @param userId + * @param authority + * @param extras + */ + public void onSyncRequest(Account account, int userId, 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<Account, AccountInfo> mAccounts = - new HashMap<Account, AccountInfo>(); + private final HashMap<AccountAndUser, AccountInfo> mAccounts + = new HashMap<AccountAndUser, AccountInfo>(); private final ArrayList<PendingOperation> mPendingOperations = new ArrayList<PendingOperation>(); - private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>(); + private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs + = new SparseArray<ArrayList<SyncInfo>>(); private final SparseArray<SyncStatusInfo> mSyncStatus = new SparseArray<SyncStatusInfo>(); @@ -282,7 +307,9 @@ public class SyncStorageEngine extends Handler { private int mNumPendingFinished = 0; private int mNextHistoryId = 0; - private boolean mMasterSyncAutomatically = true; + private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>(); + + private OnSyncRequestListener mSyncRequestListener; private SyncStorageEngine(Context context, File dataDir) { mContext = context; @@ -330,6 +357,12 @@ public class SyncStorageEngine extends Handler { 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) { @@ -389,10 +422,10 @@ public class SyncStorageEngine extends Handler { } } - public boolean getSyncAutomatically(Account account, String providerName) { + public boolean getSyncAutomatically(Account account, int userId, String providerName) { synchronized (mAuthorities) { if (account != null) { - AuthorityInfo authority = getAuthorityLocked(account, providerName, + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, "getSyncAutomatically"); return authority != null && authority.enabled; } @@ -402,6 +435,7 @@ public class SyncStorageEngine extends Handler { i--; AuthorityInfo authority = mAuthorities.valueAt(i); if (authority.authority.equals(providerName) + && authority.userId == userId && authority.enabled) { return true; } @@ -410,11 +444,13 @@ public class SyncStorageEngine extends Handler { } } - public void setSyncAutomatically(Account account, String providerName, boolean sync) { - Log.d(TAG, "setSyncAutomatically: " + /*account +*/ ", provider " + providerName - + " -> " + sync); + public void setSyncAutomatically(Account account, int userId, String providerName, + boolean sync) { + Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName + + ", user " + userId + " -> " + sync); synchronized (mAuthorities) { - AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); + AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1, + false); if (authority.enabled == sync) { Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing"); return; @@ -424,15 +460,15 @@ public class SyncStorageEngine extends Handler { } if (sync) { - ContentResolver.requestSync(account, providerName, new Bundle()); + requestSync(account, userId, providerName, new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } - public int getIsSyncable(Account account, String providerName) { + public int getIsSyncable(Account account, int userId, String providerName) { synchronized (mAuthorities) { if (account != null) { - AuthorityInfo authority = getAuthorityLocked(account, providerName, + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, "getIsSyncable"); if (authority == null) { return -1; @@ -452,15 +488,17 @@ public class SyncStorageEngine extends Handler { } } - public void setIsSyncable(Account account, String providerName, int syncable) { + public void setIsSyncable(Account account, int userId, String providerName, int syncable) { if (syncable > 1) { syncable = 1; } else if (syncable < -1) { syncable = -1; } - Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName + " -> " + syncable); + Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName + + ", user " + userId + " -> " + syncable); synchronized (mAuthorities) { - AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); + AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1, + false); if (authority.syncable == syncable) { Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing"); return; @@ -470,14 +508,15 @@ public class SyncStorageEngine extends Handler { } if (syncable > 0) { - ContentResolver.requestSync(account, providerName, new Bundle()); + requestSync(account, userId, providerName, new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } - public Pair<Long, Long> getBackoff(Account account, String providerName) { + public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) { synchronized (mAuthorities) { - AuthorityInfo authority = getAuthorityLocked(account, providerName, "getBackoff"); + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, + "getBackoff"); if (authority == null || authority.backoffTime < 0) { return null; } @@ -485,17 +524,21 @@ public class SyncStorageEngine extends Handler { } } - public void setBackoff(Account account, String providerName, + public void setBackoff(Account account, int userId, String providerName, long nextSyncTime, long nextDelay) { if (Log.isLoggable(TAG, Log.VERBOSE)) { 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.account)) continue; + 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; @@ -510,7 +553,8 @@ public class SyncStorageEngine extends Handler { } } else { AuthorityInfo authority = - getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true); + getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */, + true); if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) { return; } @@ -535,13 +579,15 @@ public class SyncStorageEngine extends Handler { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "clearAllBackoffs:" + " authority:" + authorityInfo.authority - + " account:" + accountInfo.account.name + + " 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.account, authorityInfo.authority, 0); + syncQueue.onBackoffChanged(accountInfo.accountAndUser.account, + accountInfo.accountAndUser.userId, authorityInfo.authority, 0); changed = true; } } @@ -553,14 +599,15 @@ public class SyncStorageEngine extends Handler { } } - public void setDelayUntilTime(Account account, String providerName, long delayUntil) { + public void setDelayUntilTime(Account account, int userId, String providerName, + long delayUntil) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName - + " -> delayUntil " + delayUntil); + + ", user " + userId + " -> delayUntil " + delayUntil); } synchronized (mAuthorities) { AuthorityInfo authority = getOrCreateAuthorityLocked( - account, providerName, -1 /* ident */, true); + account, userId, providerName, -1 /* ident */, true); if (authority.delayUntil == delayUntil) { return; } @@ -570,9 +617,10 @@ public class SyncStorageEngine extends Handler { reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } - public long getDelayUntilTime(Account account, String providerName) { + public long getDelayUntilTime(Account account, int userId, String providerName) { synchronized (mAuthorities) { - AuthorityInfo authority = getAuthorityLocked(account, providerName, "getDelayUntil"); + AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, + "getDelayUntil"); if (authority == null) { return 0; } @@ -580,7 +628,8 @@ public class SyncStorageEngine extends Handler { } } - private void updateOrRemovePeriodicSync(Account account, String providerName, Bundle extras, + private void updateOrRemovePeriodicSync(Account account, int userId, String providerName, + Bundle extras, long period, boolean add) { if (period <= 0) { period = 0; @@ -589,13 +638,14 @@ public class SyncStorageEngine extends Handler { extras = new Bundle(); } if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", provider " + providerName + Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", user " + userId + + ", provider " + providerName + " -> period " + period + ", extras " + extras); } synchronized (mAuthorities) { try { AuthorityInfo authority = - getOrCreateAuthorityLocked(account, providerName, -1, false); + 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 @@ -652,61 +702,67 @@ public class SyncStorageEngine extends Handler { reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } - public void addPeriodicSync(Account account, String providerName, Bundle extras, + public void addPeriodicSync(Account account, int userId, String providerName, Bundle extras, long pollFrequency) { - updateOrRemovePeriodicSync(account, providerName, extras, pollFrequency, true /* add */); + updateOrRemovePeriodicSync(account, userId, providerName, extras, pollFrequency, + true /* add */); } - public void removePeriodicSync(Account account, String providerName, Bundle extras) { - updateOrRemovePeriodicSync(account, providerName, extras, 0 /* period, ignored */, + 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, String providerName) { + public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) { ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>(); synchronized (mAuthorities) { - AuthorityInfo authority = getAuthorityLocked(account, providerName, "getPeriodicSyncs"); + 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)); + syncs.add(new PeriodicSync(account, providerName, item.first, + item.second)); } } } return syncs; } - public void setMasterSyncAutomatically(boolean flag) { + public void setMasterSyncAutomatically(boolean flag, int userId) { synchronized (mAuthorities) { - if (mMasterSyncAutomatically == flag) { + Boolean auto = mMasterSyncAutomatically.get(userId); + if (auto != null && (boolean) auto == flag) { return; } - mMasterSyncAutomatically = flag; + mMasterSyncAutomatically.put(userId, flag); writeAccountInfoLocked(); } if (flag) { - ContentResolver.requestSync(null, null, new Bundle()); + requestSync(null, userId, null, new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT); } - public boolean getMasterSyncAutomatically() { + public boolean getMasterSyncAutomatically(int userId) { synchronized (mAuthorities) { - return mMasterSyncAutomatically; + Boolean auto = mMasterSyncAutomatically.get(userId); + return auto == null ? true : auto; } } - public AuthorityInfo getOrCreateAuthority(Account account, String authority) { + public AuthorityInfo getOrCreateAuthority(Account account, int userId, String authority) { synchronized (mAuthorities) { - return getOrCreateAuthorityLocked(account, authority, + 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, String authority) { + public void removeAuthority(Account account, int userId, String authority) { synchronized (mAuthorities) { - removeAuthorityLocked(account, authority, true /* doWrite */); + removeAuthorityLocked(account, userId, authority, true /* doWrite */); } } @@ -720,12 +776,13 @@ public class SyncStorageEngine extends Handler { * Returns true if there is currently a sync operation for the given * account or authority actively being processed. */ - public boolean isSyncActive(Account account, String authority) { + public boolean isSyncActive(Account account, int userId, String authority) { synchronized (mAuthorities) { - for (SyncInfo syncInfo : mCurrentSyncs) { + for (SyncInfo syncInfo : getCurrentSyncs(userId)) { AuthorityInfo ainfo = getAuthority(syncInfo.authorityId); if (ainfo != null && ainfo.account.equals(account) - && ainfo.authority.equals(authority)) { + && ainfo.authority.equals(authority) + && ainfo.userId == userId) { return true; } } @@ -738,12 +795,13 @@ public class SyncStorageEngine extends Handler { synchronized (mAuthorities) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "insertIntoPending: account=" + op.account - + " auth=" + op.authority - + " src=" + op.syncSource - + " extras=" + op.extras); + + " user=" + op.userId + + " auth=" + op.authority + + " src=" + op.syncSource + + " extras=" + op.extras); } - AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, + AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId, op.authority, -1 /* desired identifier */, true /* write accounts to storage */); @@ -769,6 +827,7 @@ public class SyncStorageEngine extends Handler { synchronized (mAuthorities) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "deleteFromPending: account=" + op.account + + " user=" + op.userId + " auth=" + op.authority + " src=" + op.syncSource + " extras=" + op.extras); @@ -782,7 +841,7 @@ public class SyncStorageEngine extends Handler { mNumPendingFinished++; } - AuthorityInfo authority = getAuthorityLocked(op.account, op.authority, + AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority, "deleteFromPending"); if (authority != null) { if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "removing - " + authority); @@ -791,7 +850,8 @@ public class SyncStorageEngine extends Handler { for (int i=0; i<N; i++) { PendingOperation cur = mPendingOperations.get(i); if (cur.account.equals(op.account) - && cur.authority.equals(op.authority)) { + && cur.authority.equals(op.authority) + && cur.userId == op.userId) { morePending = true; break; } @@ -812,24 +872,6 @@ public class SyncStorageEngine extends Handler { return res; } - public int clearPending() { - int num; - synchronized (mAuthorities) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "clearPending"); - } - num = mPendingOperations.size(); - mPendingOperations.clear(); - final int N = mSyncStatus.size(); - for (int i=0; i<N; i++) { - mSyncStatus.valueAt(i).pending = false; - } - writePendingOperationsLocked(); - } - reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); - return num; - } - /** * Return a copy of the current array of pending operations. The * PendingOperation objects are the real objects stored inside, so that @@ -854,17 +896,18 @@ public class SyncStorageEngine extends Handler { * Called when the set of account has changed, given the new array of * active accounts. */ - public void doDatabaseCleanup(Account[] accounts) { + public void doDatabaseCleanup(Account[] accounts, int userId) { synchronized (mAuthorities) { if (Log.isLoggable(TAG, Log.VERBOSE)) Log.w(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.account)) { + if (!ArrayUtils.contains(accounts, acc.accountAndUser.account) + && acc.accountAndUser.userId == userId) { // This account no longer exists... if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.w(TAG, "Account removed: " + acc.account); + Log.w(TAG, "Account removed: " + acc.accountAndUser); } for (AuthorityInfo auth : acc.authorities.values()) { removing.put(auth.ident, auth); @@ -919,13 +962,14 @@ public class SyncStorageEngine extends Handler { } 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); - mCurrentSyncs.add(syncInfo); + getCurrentSyncs(authority.userId).add(syncInfo); } reportActiveChange(); @@ -935,13 +979,14 @@ public class SyncStorageEngine extends Handler { /** * Called to indicate that a previously active sync is no longer active. */ - public void removeActiveSync(SyncInfo syncInfo) { + public void removeActiveSync(SyncInfo syncInfo, int userId) { synchronized (mAuthorities) { if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "removeActiveSync: account=" - + syncInfo.account + " auth=" + syncInfo.authority); + Log.v(TAG, "removeActiveSync: account=" + syncInfo.account + + " user=" + userId + + " auth=" + syncInfo.authority); } - mCurrentSyncs.remove(syncInfo); + getCurrentSyncs(userId).remove(syncInfo); } reportActiveChange(); @@ -957,15 +1002,15 @@ public class SyncStorageEngine extends Handler { /** * Note that sync has started for the given account and authority. */ - public long insertStartSyncEvent(Account accountName, String authorityName, + public long insertStartSyncEvent(Account accountName, int userId, String authorityName, long now, int source) { long id; synchronized (mAuthorities) { if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "insertStartSyncEvent: account=" + accountName + Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId + " auth=" + authorityName + " source=" + source); } - AuthorityInfo authority = getAuthorityLocked(accountName, authorityName, + AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName, "insertStartSyncEvent"); if (authority == null) { return -1; @@ -1119,9 +1164,14 @@ public class SyncStorageEngine extends Handler { * 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() { + public List<SyncInfo> getCurrentSyncs(int userId) { synchronized (mAuthorities) { - return new ArrayList<SyncInfo>(mCurrentSyncs); + ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId); + if (syncs == null) { + syncs = new ArrayList<SyncInfo>(); + mCurrentSyncs.put(userId, syncs); + } + return new ArrayList<SyncInfo>(syncs); } } @@ -1164,7 +1214,8 @@ public class SyncStorageEngine extends Handler { * @param authority the authority whose row should be selected * @return the SyncStatusInfo for the authority */ - public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) { + public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId, + String authority) { if (account == null || authority == null) { throw new IllegalArgumentException(); } @@ -1174,8 +1225,9 @@ public class SyncStorageEngine extends Handler { SyncStatusInfo cur = mSyncStatus.valueAt(i); AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); - if (ainfo != null && ainfo.authority.equals(authority) && - account.equals(ainfo.account)) { + if (ainfo != null && ainfo.authority.equals(authority) + && ainfo.userId == userId + && account.equals(ainfo.account)) { return cur; } } @@ -1186,7 +1238,7 @@ public class SyncStorageEngine extends Handler { /** * Return true if the pending status is true of any matching authorities. */ - public boolean isSyncPending(Account account, String authority) { + public boolean isSyncPending(Account account, int userId, String authority) { synchronized (mAuthorities) { final int N = mSyncStatus.size(); for (int i=0; i<N; i++) { @@ -1195,6 +1247,9 @@ public class SyncStorageEngine extends Handler { if (ainfo == null) { continue; } + if (userId != ainfo.userId) { + continue; + } if (account != null && !ainfo.account.equals(account)) { continue; } @@ -1235,34 +1290,6 @@ public class SyncStorageEngine extends Handler { } } - /** - * If sync is failing for any of the provider/accounts then determine the time at which it - * started failing and return the earliest time over all the provider/accounts. If none are - * failing then return 0. - */ - public long getInitialSyncFailureTime() { - synchronized (mAuthorities) { - if (!mMasterSyncAutomatically) { - return 0; - } - - long oldest = 0; - int i = mSyncStatus.size(); - while (i > 0) { - i--; - SyncStatusInfo stats = mSyncStatus.valueAt(i); - AuthorityInfo authority = mAuthorities.get(stats.authorityId); - if (authority != null && authority.enabled) { - if (oldest == 0 || stats.initialFailureTime < oldest) { - oldest = stats.initialFailureTime; - } - } - } - - return oldest; - } - } - private int getCurrentDayLocked() { mCal.setTimeInMillis(System.currentTimeMillis()); final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); @@ -1283,18 +1310,19 @@ public class SyncStorageEngine extends Handler { * @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, String authorityName, + private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName, String tag) { - AccountInfo account = mAccounts.get(accountName); - if (account == null) { + AccountAndUser au = new AccountAndUser(accountName, userId); + AccountInfo accountInfo = mAccounts.get(au); + if (accountInfo == null) { if (tag != null) { if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, tag + ": unknown account " + accountName); + Log.v(TAG, tag + ": unknown account " + au); } } return null; } - AuthorityInfo authority = account.authorities.get(authorityName); + AuthorityInfo authority = accountInfo.authorities.get(authorityName); if (authority == null) { if (tag != null) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -1307,12 +1335,13 @@ public class SyncStorageEngine extends Handler { return authority; } - private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, + private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId, String authorityName, int ident, boolean doWrite) { - AccountInfo account = mAccounts.get(accountName); + AccountAndUser au = new AccountAndUser(accountName, userId); + AccountInfo account = mAccounts.get(au); if (account == null) { - account = new AccountInfo(accountName); - mAccounts.put(accountName, account); + account = new AccountInfo(au); + mAccounts.put(au, account); } AuthorityInfo authority = account.authorities.get(authorityName); if (authority == null) { @@ -1323,9 +1352,10 @@ public class SyncStorageEngine extends Handler { } if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "created a new AuthorityInfo for " + accountName - + ", provider " + authorityName); + + ", user " + userId + + ", provider " + authorityName); } - authority = new AuthorityInfo(accountName, authorityName, ident); + authority = new AuthorityInfo(accountName, userId, authorityName, ident); account.authorities.put(authorityName, authority); mAuthorities.put(ident, authority); if (doWrite) { @@ -1336,8 +1366,9 @@ public class SyncStorageEngine extends Handler { return authority; } - private void removeAuthorityLocked(Account account, String authorityName, boolean doWrite) { - AccountInfo accountInfo = mAccounts.get(account); + 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) { @@ -1419,8 +1450,7 @@ public class SyncStorageEngine extends Handler { } String tagName = parser.getName(); if ("accounts".equals(tagName)) { - String listen = parser.getAttributeValue( - null, "listen-for-tickles"); + String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES); String versionString = parser.getAttributeValue(null, "version"); int version; try { @@ -1428,14 +1458,14 @@ public class SyncStorageEngine extends Handler { } catch (NumberFormatException e) { version = 0; } - String nextIdString = parser.getAttributeValue(null, "nextAuthorityId"); + 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 } - mMasterSyncAutomatically = listen == null || Boolean.parseBoolean(listen); + mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen)); eventType = parser.next(); AuthorityInfo authority = null; Pair<Bundle, Long> periodicSync = null; @@ -1449,6 +1479,8 @@ public class SyncStorageEngine extends Handler { 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) { @@ -1511,25 +1543,41 @@ public class SyncStorageEngine extends Handler { } // if we already have a record of this new authority then don't copy over the settings - if (getAuthorityLocked(authority.account, newAuthorityName, "cleanup") != null) { + if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup") + != null) { continue; } AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account, - newAuthorityName, -1 /* ident */, false /* doWrite */); + authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */); newAuthority.enabled = true; writeNeeded = true; } for (AuthorityInfo authorityInfo : authoritiesToRemove) { - removeAuthorityLocked(authorityInfo.account, authorityInfo.authority, - false /* doWrite */); + 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; @@ -1543,10 +1591,12 @@ public class SyncStorageEngine extends Handler { } if (id >= 0) { String authorityName = parser.getAttributeValue(null, "authority"); - String enabled = parser.getAttributeValue(null, "enabled"); + 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"; @@ -1554,12 +1604,13 @@ public class SyncStorageEngine extends Handler { 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), authorityName, id, false); + 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. @@ -1653,9 +1704,17 @@ public class SyncStorageEngine extends Handler { out.startTag(null, "accounts"); out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION)); - out.attribute(null, "nextAuthorityId", Integer.toString(mNextAuthorityId)); - if (!mMasterSyncAutomatically) { - out.attribute(null, "listen-for-tickles", "false"); + out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId)); + + // 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(); @@ -1664,9 +1723,10 @@ public class SyncStorageEngine extends Handler { 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, "enabled", Boolean.toString(authority.enabled)); + out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled)); if (authority.syncable < 0) { out.attribute(null, "syncable", "unknown"); } else { @@ -1788,7 +1848,7 @@ public class SyncStorageEngine extends Handler { } String authorityName = c.getString(c.getColumnIndex("authority")); AuthorityInfo authority = this.getOrCreateAuthorityLocked( - new Account(accountName, accountType), + new Account(accountName, accountType), 0 /* legacy is single-user */, authorityName, -1, false); if (authority != null) { int i = mSyncStatus.size(); @@ -1833,7 +1893,7 @@ public class SyncStorageEngine extends Handler { String value = c.getString(c.getColumnIndex("value")); if (name == null) continue; if (name.equals("listen_for_tickles")) { - setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value)); + setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0); } else if (name.startsWith("sync_provider_")) { String provider = name.substring("sync_provider_".length(), name.length()); @@ -1964,7 +2024,7 @@ public class SyncStorageEngine extends Handler { extras = new Bundle(); } PendingOperation op = new PendingOperation( - authority.account, syncSource, + authority.account, authority.userId, syncSource, authority.authority, extras, expedited); op.authorityId = authorityId; op.flatExtras = flatExtras; @@ -2084,6 +2144,19 @@ public class SyncStorageEngine extends Handler { return bundle; } + private void requestSync(Account account, int userId, 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, 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; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f2133d8..544bd9c 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -119,7 +119,7 @@ public abstract class PackageManager { * {@link PackageInfo} flag: return the * {@link PackageInfo#gids group ids} that are associated with an * application. - * This applies for any API returning an PackageInfo class, either + * This applies for any API returning a PackageInfo class, either * directly or nested inside of another. */ public static final int GET_GIDS = 0x00000100; @@ -142,7 +142,7 @@ public abstract class PackageManager { * {@link ProviderInfo} flag: return the * {@link ProviderInfo#uriPermissionPatterns URI permission patterns} * that are associated with a content provider. - * This applies for any API returning an ProviderInfo class, either + * This applies for any API returning a ProviderInfo class, either * directly or nested inside of another. */ public static final int GET_URI_PERMISSION_PATTERNS = 0x00000800; diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java index 59ec89d..907833d 100644 --- a/core/java/android/database/Cursor.java +++ b/core/java/android/database/Cursor.java @@ -20,6 +20,8 @@ import android.content.ContentResolver; import android.net.Uri; import android.os.Bundle; +import java.io.Closeable; + /** * This interface provides random read-write access to the result set returned * by a database query. @@ -27,7 +29,7 @@ import android.os.Bundle; * Cursor implementations are not required to be synchronized so code using a Cursor from multiple * threads should perform its own synchronization when using the Cursor. */ -public interface Cursor { +public interface Cursor extends Closeable { /* * Values returned by {@link #getType(int)}. * These should be consistent with the corresponding types defined in CursorWindow.h diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index 85f570c..f1f3017 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -169,14 +169,6 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { } /** - * Closes the cursor window and frees its underlying resources when all other - * remaining references have been released. - */ - public void close() { - releaseReference(); - } - - /** * Clears out the existing contents of the window, making it safe to reuse * for new data. * <p> @@ -703,8 +695,13 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mStartPos); - nativeWriteToParcel(mWindowPtr, dest); + acquireReference(); + try { + dest.writeInt(mStartPos); + nativeWriteToParcel(mWindowPtr, dest); + } finally { + releaseReference(); + } if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { releaseReference(); diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 0022118..99d260e 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -269,63 +269,56 @@ public class DatabaseUtils { if (position < 0 || position >= cursor.getCount()) { return; } - window.acquireReference(); - try { - final int oldPos = cursor.getPosition(); - final int numColumns = cursor.getColumnCount(); - window.clear(); - window.setStartPosition(position); - window.setNumColumns(numColumns); - if (cursor.moveToPosition(position)) { - do { - if (!window.allocRow()) { - break; - } - for (int i = 0; i < numColumns; i++) { - final int type = cursor.getType(i); - final boolean success; - switch (type) { - case Cursor.FIELD_TYPE_NULL: - success = window.putNull(position, i); - break; - - case Cursor.FIELD_TYPE_INTEGER: - success = window.putLong(cursor.getLong(i), position, i); - break; - - case Cursor.FIELD_TYPE_FLOAT: - success = window.putDouble(cursor.getDouble(i), position, i); - break; - - case Cursor.FIELD_TYPE_BLOB: { - final byte[] value = cursor.getBlob(i); - success = value != null ? window.putBlob(value, position, i) - : window.putNull(position, i); - break; - } - - default: // assume value is convertible to String - case Cursor.FIELD_TYPE_STRING: { - final String value = cursor.getString(i); - success = value != null ? window.putString(value, position, i) - : window.putNull(position, i); - break; - } + final int oldPos = cursor.getPosition(); + final int numColumns = cursor.getColumnCount(); + window.clear(); + window.setStartPosition(position); + window.setNumColumns(numColumns); + if (cursor.moveToPosition(position)) { + do { + if (!window.allocRow()) { + break; + } + for (int i = 0; i < numColumns; i++) { + final int type = cursor.getType(i); + final boolean success; + switch (type) { + case Cursor.FIELD_TYPE_NULL: + success = window.putNull(position, i); + break; + + case Cursor.FIELD_TYPE_INTEGER: + success = window.putLong(cursor.getLong(i), position, i); + break; + + case Cursor.FIELD_TYPE_FLOAT: + success = window.putDouble(cursor.getDouble(i), position, i); + break; + + case Cursor.FIELD_TYPE_BLOB: { + final byte[] value = cursor.getBlob(i); + success = value != null ? window.putBlob(value, position, i) + : window.putNull(position, i); + break; } - if (!success) { - window.freeLastRow(); + + default: // assume value is convertible to String + case Cursor.FIELD_TYPE_STRING: { + final String value = cursor.getString(i); + success = value != null ? window.putString(value, position, i) + : window.putNull(position, i); break; } } - position += 1; - } while (cursor.moveToNext()); - } - cursor.moveToPosition(oldPos); - } catch (IllegalStateException e){ - // simply ignore it - } finally { - window.releaseReference(); + if (!success) { + window.freeLastRow(); + break; + } + } + position += 1; + } while (cursor.moveToNext()); } + cursor.moveToPosition(oldPos); } /** diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java index 7e91a7b..adfbc6e 100644 --- a/core/java/android/database/sqlite/SQLiteClosable.java +++ b/core/java/android/database/sqlite/SQLiteClosable.java @@ -16,15 +16,39 @@ package android.database.sqlite; +import java.io.Closeable; + /** * An object created from a SQLiteDatabase that can be closed. + * + * This class implements a primitive reference counting scheme for database objects. */ -public abstract class SQLiteClosable { +public abstract class SQLiteClosable implements Closeable { private int mReferenceCount = 1; + /** + * Called when the last reference to the object was released by + * a call to {@link #releaseReference()} or {@link #close()}. + */ protected abstract void onAllReferencesReleased(); - protected void onAllReferencesReleasedFromContainer() {} + /** + * Called when the last reference to the object was released by + * a call to {@link #releaseReferenceFromContainer()}. + * + * @deprecated Do not use. + */ + @Deprecated + protected void onAllReferencesReleasedFromContainer() { + onAllReferencesReleased(); + } + + /** + * Acquires a reference to the object. + * + * @throws IllegalStateException if the last reference to the object has already + * been released. + */ public void acquireReference() { synchronized(this) { if (mReferenceCount <= 0) { @@ -35,6 +59,12 @@ public abstract class SQLiteClosable { } } + /** + * Releases a reference to the object, closing the object if the last reference + * was released. + * + * @see #onAllReferencesReleased() + */ public void releaseReference() { boolean refCountIsZero = false; synchronized(this) { @@ -45,6 +75,14 @@ public abstract class SQLiteClosable { } } + /** + * Releases a reference to the object that was owned by the container of the object, + * closing the object if the last reference was released. + * + * @see #onAllReferencesReleasedFromContainer() + * @deprecated Do not use. + */ + @Deprecated public void releaseReferenceFromContainer() { boolean refCountIsZero = false; synchronized(this) { @@ -54,4 +92,17 @@ public abstract class SQLiteClosable { onAllReferencesReleasedFromContainer(); } } + + /** + * Releases a reference to the object, closing the object if the last reference + * was released. + * + * Calling this method is equivalent to calling {@link #releaseReference}. + * + * @see #releaseReference() + * @see #onAllReferencesReleased() + */ + public void close() { + releaseReference(); + } } diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index d16f29f..0db3e4f 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -704,44 +704,49 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen throw new IllegalArgumentException("window must not be null."); } - int actualPos = -1; - int countedRows = -1; - int filledRows = -1; - final int cookie = mRecentOperations.beginOperation("executeForCursorWindow", - sql, bindArgs); + window.acquireReference(); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + int actualPos = -1; + int countedRows = -1; + int filledRows = -1; + final int cookie = mRecentOperations.beginOperation("executeForCursorWindow", + sql, bindArgs); try { - throwIfStatementForbidden(statement); - bindArguments(statement, bindArgs); - applyBlockGuardPolicy(statement); - attachCancellationSignal(cancellationSignal); + final PreparedStatement statement = acquirePreparedStatement(sql); try { - final long result = nativeExecuteForCursorWindow( - mConnectionPtr, statement.mStatementPtr, window.mWindowPtr, - startPos, requiredPos, countAllRows); - actualPos = (int)(result >> 32); - countedRows = (int)result; - filledRows = window.getNumRows(); - window.setStartPosition(actualPos); - return countedRows; + throwIfStatementForbidden(statement); + bindArguments(statement, bindArgs); + applyBlockGuardPolicy(statement); + attachCancellationSignal(cancellationSignal); + try { + final long result = nativeExecuteForCursorWindow( + mConnectionPtr, statement.mStatementPtr, window.mWindowPtr, + startPos, requiredPos, countAllRows); + actualPos = (int)(result >> 32); + countedRows = (int)result; + filledRows = window.getNumRows(); + window.setStartPosition(actualPos); + return countedRows; + } finally { + detachCancellationSignal(cancellationSignal); + } } finally { - detachCancellationSignal(cancellationSignal); + releasePreparedStatement(statement); } + } catch (RuntimeException ex) { + mRecentOperations.failOperation(cookie, ex); + throw ex; } finally { - releasePreparedStatement(statement); + if (mRecentOperations.endOperationDeferLog(cookie)) { + mRecentOperations.logOperation(cookie, "window='" + window + + "', startPos=" + startPos + + ", actualPos=" + actualPos + + ", filledRows=" + filledRows + + ", countedRows=" + countedRows); + } } - } catch (RuntimeException ex) { - mRecentOperations.failOperation(cookie, ex); - throw ex; } finally { - if (mRecentOperations.endOperationDeferLog(cookie)) { - mRecentOperations.logOperation(cookie, "window='" + window - + "', startPos=" + startPos - + ", actualPos=" + actualPos - + ", filledRows=" + filledRows - + ", countedRows=" + countedRows); - } + window.releaseReference(); } } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 604247e..d41b484 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -492,9 +492,16 @@ public final class SQLiteDatabase extends SQLiteClosable { private void beginTransaction(SQLiteTransactionListener transactionListener, boolean exclusive) { - getThreadSession().beginTransaction(exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE : - SQLiteSession.TRANSACTION_MODE_IMMEDIATE, transactionListener, - getThreadDefaultConnectionFlags(false /*readOnly*/), null); + acquireReference(); + try { + getThreadSession().beginTransaction( + exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE : + SQLiteSession.TRANSACTION_MODE_IMMEDIATE, + transactionListener, + getThreadDefaultConnectionFlags(false /*readOnly*/), null); + } finally { + releaseReference(); + } } /** @@ -502,7 +509,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * are committed and rolled back. */ public void endTransaction() { - getThreadSession().endTransaction(null); + acquireReference(); + try { + getThreadSession().endTransaction(null); + } finally { + releaseReference(); + } } /** @@ -515,7 +527,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * transaction is already marked as successful. */ public void setTransactionSuccessful() { - getThreadSession().setTransactionSuccessful(); + acquireReference(); + try { + getThreadSession().setTransactionSuccessful(); + } finally { + releaseReference(); + } } /** @@ -524,7 +541,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return True if the current thread is in a transaction. */ public boolean inTransaction() { - return getThreadSession().hasTransaction(); + acquireReference(); + try { + return getThreadSession().hasTransaction(); + } finally { + releaseReference(); + } } /** @@ -540,7 +562,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return True if the current thread is holding an active connection to the database. */ public boolean isDbLockedByCurrentThread() { - return getThreadSession().hasConnection(); + acquireReference(); + try { + return getThreadSession().hasConnection(); + } finally { + releaseReference(); + } } /** @@ -599,7 +626,12 @@ public final class SQLiteDatabase extends SQLiteClosable { } private boolean yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYieldDelay) { - return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null); + acquireReference(); + try { + return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null); + } finally { + releaseReference(); + } } /** @@ -788,13 +820,6 @@ public final class SQLiteDatabase extends SQLiteClosable { } /** - * Close the database. - */ - public void close() { - dispose(false); - } - - /** * Registers a CustomFunction callback as a function that can be called from * SQLite database triggers. * @@ -948,8 +973,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. */ public SQLiteStatement compileStatement(String sql) throws SQLException { - throwIfNotOpen(); // fail fast - return new SQLiteStatement(this, sql, null); + acquireReference(); + try { + return new SQLiteStatement(this, sql, null); + } finally { + releaseReference(); + } } /** @@ -1110,12 +1139,16 @@ public final class SQLiteDatabase extends SQLiteClosable { boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal) { - throwIfNotOpen(); // fail fast - String sql = SQLiteQueryBuilder.buildQueryString( - distinct, table, columns, selection, groupBy, having, orderBy, limit); + acquireReference(); + try { + String sql = SQLiteQueryBuilder.buildQueryString( + distinct, table, columns, selection, groupBy, having, orderBy, limit); - return rawQueryWithFactory(cursorFactory, sql, selectionArgs, - findEditTable(table), cancellationSignal); + return rawQueryWithFactory(cursorFactory, sql, selectionArgs, + findEditTable(table), cancellationSignal); + } finally { + releaseReference(); + } } /** @@ -1260,12 +1293,15 @@ public final class SQLiteDatabase extends SQLiteClosable { public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable, CancellationSignal cancellationSignal) { - throwIfNotOpen(); // fail fast - - SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, - cancellationSignal); - return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory, - selectionArgs); + acquireReference(); + try { + SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, + cancellationSignal); + return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory, + selectionArgs); + } finally { + releaseReference(); + } } /** @@ -1384,38 +1420,44 @@ public final class SQLiteDatabase extends SQLiteClosable { */ public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { - StringBuilder sql = new StringBuilder(); - sql.append("INSERT"); - sql.append(CONFLICT_VALUES[conflictAlgorithm]); - sql.append(" INTO "); - sql.append(table); - sql.append('('); - - Object[] bindArgs = null; - int size = (initialValues != null && initialValues.size() > 0) ? initialValues.size() : 0; - if (size > 0) { - bindArgs = new Object[size]; - int i = 0; - for (String colName : initialValues.keySet()) { - sql.append((i > 0) ? "," : ""); - sql.append(colName); - bindArgs[i++] = initialValues.get(colName); + acquireReference(); + try { + StringBuilder sql = new StringBuilder(); + sql.append("INSERT"); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(" INTO "); + sql.append(table); + sql.append('('); + + Object[] bindArgs = null; + int size = (initialValues != null && initialValues.size() > 0) + ? initialValues.size() : 0; + if (size > 0) { + bindArgs = new Object[size]; + int i = 0; + for (String colName : initialValues.keySet()) { + sql.append((i > 0) ? "," : ""); + sql.append(colName); + bindArgs[i++] = initialValues.get(colName); + } + sql.append(')'); + sql.append(" VALUES ("); + for (i = 0; i < size; i++) { + sql.append((i > 0) ? ",?" : "?"); + } + } else { + sql.append(nullColumnHack + ") VALUES (NULL"); } sql.append(')'); - sql.append(" VALUES ("); - for (i = 0; i < size; i++) { - sql.append((i > 0) ? ",?" : "?"); - } - } else { - sql.append(nullColumnHack + ") VALUES (NULL"); - } - sql.append(')'); - SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); - try { - return statement.executeInsert(); + SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); + try { + return statement.executeInsert(); + } finally { + statement.close(); + } } finally { - statement.close(); + releaseReference(); } } @@ -1430,12 +1472,17 @@ public final class SQLiteDatabase extends SQLiteClosable { * whereClause. */ public int delete(String table, String whereClause, String[] whereArgs) { - SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + - (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); + acquireReference(); try { - return statement.executeUpdateDelete(); + SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + + (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); + try { + return statement.executeUpdateDelete(); + } finally { + statement.close(); + } } finally { - statement.close(); + releaseReference(); } } @@ -1470,38 +1517,43 @@ public final class SQLiteDatabase extends SQLiteClosable { throw new IllegalArgumentException("Empty values"); } - StringBuilder sql = new StringBuilder(120); - sql.append("UPDATE "); - sql.append(CONFLICT_VALUES[conflictAlgorithm]); - sql.append(table); - sql.append(" SET "); - - // move all bind args to one array - int setValuesSize = values.size(); - int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length); - Object[] bindArgs = new Object[bindArgsSize]; - int i = 0; - for (String colName : values.keySet()) { - sql.append((i > 0) ? "," : ""); - sql.append(colName); - bindArgs[i++] = values.get(colName); - sql.append("=?"); - } - if (whereArgs != null) { - for (i = setValuesSize; i < bindArgsSize; i++) { - bindArgs[i] = whereArgs[i - setValuesSize]; + acquireReference(); + try { + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE "); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); + sql.append(table); + sql.append(" SET "); + + // move all bind args to one array + int setValuesSize = values.size(); + int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length); + Object[] bindArgs = new Object[bindArgsSize]; + int i = 0; + for (String colName : values.keySet()) { + sql.append((i > 0) ? "," : ""); + sql.append(colName); + bindArgs[i++] = values.get(colName); + sql.append("=?"); + } + if (whereArgs != null) { + for (i = setValuesSize; i < bindArgsSize; i++) { + bindArgs[i] = whereArgs[i - setValuesSize]; + } + } + if (!TextUtils.isEmpty(whereClause)) { + sql.append(" WHERE "); + sql.append(whereClause); } - } - if (!TextUtils.isEmpty(whereClause)) { - sql.append(" WHERE "); - sql.append(whereClause); - } - SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); - try { - return statement.executeUpdateDelete(); + SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); + try { + return statement.executeUpdateDelete(); + } finally { + statement.close(); + } } finally { - statement.close(); + releaseReference(); } } @@ -1579,24 +1631,29 @@ public final class SQLiteDatabase extends SQLiteClosable { } private int executeSql(String sql, Object[] bindArgs) throws SQLException { - if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { - boolean disableWal = false; - synchronized (mLock) { - if (!mHasAttachedDbsLocked) { - mHasAttachedDbsLocked = true; - disableWal = true; + acquireReference(); + try { + if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { + boolean disableWal = false; + synchronized (mLock) { + if (!mHasAttachedDbsLocked) { + mHasAttachedDbsLocked = true; + disableWal = true; + } + } + if (disableWal) { + disableWriteAheadLogging(); } } - if (disableWal) { - disableWriteAheadLogging(); - } - } - SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs); - try { - return statement.executeUpdateDelete(); + SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs); + try { + return statement.executeUpdateDelete(); + } finally { + statement.close(); + } } finally { - statement.close(); + releaseReference(); } } @@ -1881,26 +1938,32 @@ public final class SQLiteDatabase extends SQLiteClosable { attachedDbs.add(new Pair<String, String>("main", mConfigurationLocked.path)); return attachedDbs; } + + acquireReference(); } - // has attached databases. query sqlite to get the list of attached databases. - Cursor c = null; try { - c = rawQuery("pragma database_list;", null); - while (c.moveToNext()) { - // sqlite returns a row for each database in the returned list of databases. - // in each row, - // 1st column is the database name such as main, or the database - // name specified on the "ATTACH" command - // 2nd column is the database file path. - attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2))); + // has attached databases. query sqlite to get the list of attached databases. + Cursor c = null; + try { + c = rawQuery("pragma database_list;", null); + while (c.moveToNext()) { + // sqlite returns a row for each database in the returned list of databases. + // in each row, + // 1st column is the database name such as main, or the database + // name specified on the "ATTACH" command + // 2nd column is the database file path. + attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2))); + } + } finally { + if (c != null) { + c.close(); + } } + return attachedDbs; } finally { - if (c != null) { - c.close(); - } + releaseReference(); } - return attachedDbs; } /** @@ -1917,35 +1980,38 @@ public final class SQLiteDatabase extends SQLiteClosable { * false otherwise. */ public boolean isDatabaseIntegrityOk() { - throwIfNotOpen(); // fail fast - - List<Pair<String, String>> attachedDbs = null; + acquireReference(); try { - attachedDbs = getAttachedDbs(); - if (attachedDbs == null) { - throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " + - "be retrieved. probably because the database is closed"); + List<Pair<String, String>> attachedDbs = null; + try { + attachedDbs = getAttachedDbs(); + if (attachedDbs == null) { + throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " + + "be retrieved. probably because the database is closed"); + } + } catch (SQLiteException e) { + // can't get attachedDb list. do integrity check on the main database + attachedDbs = new ArrayList<Pair<String, String>>(); + attachedDbs.add(new Pair<String, String>("main", getPath())); } - } catch (SQLiteException e) { - // can't get attachedDb list. do integrity check on the main database - attachedDbs = new ArrayList<Pair<String, String>>(); - attachedDbs.add(new Pair<String, String>("main", getPath())); - } - for (int i = 0; i < attachedDbs.size(); i++) { - Pair<String, String> p = attachedDbs.get(i); - SQLiteStatement prog = null; - try { - prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);"); - String rslt = prog.simpleQueryForString(); - if (!rslt.equalsIgnoreCase("ok")) { - // integrity_checker failed on main or attached databases - Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt); - return false; + for (int i = 0; i < attachedDbs.size(); i++) { + Pair<String, String> p = attachedDbs.get(i); + SQLiteStatement prog = null; + try { + prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);"); + String rslt = prog.simpleQueryForString(); + if (!rslt.equalsIgnoreCase("ok")) { + // integrity_checker failed on main or attached databases + Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt); + return false; + } + } finally { + if (prog != null) prog.close(); } - } finally { - if (prog != null) prog.close(); } + } finally { + releaseReference(); } return true; } @@ -1955,12 +2021,6 @@ public final class SQLiteDatabase extends SQLiteClosable { return "SQLiteDatabase: " + getPath(); } - private void throwIfNotOpen() { - synchronized (mConnectionPoolLocked) { - throwIfNotOpenLocked(); - } - } - private void throwIfNotOpenLocked() { if (mConnectionPoolLocked == null) { throw new IllegalStateException("The database '" + mConfigurationLocked.label diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 9f0edfb..94a23cb 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -190,13 +190,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { } /** - * Release this program's resources, making it invalid. - */ - public void close() { - releaseReference(); - } - - /** * Given an array of String bindArgs, this method binds all of them in one single call. * * @param bindArgs the String array of bind args, none of which must be null. diff --git a/core/java/android/emoji/EmojiFactory.java b/core/java/android/emoji/EmojiFactory.java index e0b12ae..8fd8695 100644 --- a/core/java/android/emoji/EmojiFactory.java +++ b/core/java/android/emoji/EmojiFactory.java @@ -33,7 +33,7 @@ public final class EmojiFactory { private int sCacheSize = 100; - // HashMap for caching Bitmap object. In order not to make an cache object + // HashMap for caching Bitmap object. In order not to make a cache object // blow up, we use LinkedHashMap with size limit. private class CustomLinkedHashMap<K, V> extends LinkedHashMap<K, V> { public CustomLinkedHashMap() { diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index cca208a..2775c7b 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -36,7 +36,6 @@ import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; - /** * The Camera class is used to set image capture settings, start/stop preview, * snap pictures, and retrieve frames for encoding for video. This class is a @@ -1359,7 +1358,7 @@ public class Camera { /** * Returns an empty {@link Parameters} for testing purpose. * - * @return an Parameter object. + * @return a Parameter object. * * @hide */ @@ -3128,7 +3127,7 @@ public class Camera { public void getFocusDistances(float[] output) { if (output == null || output.length != 3) { throw new IllegalArgumentException( - "output must be an float array with three elements."); + "output must be a float array with three elements."); } splitFloat(get(KEY_FOCUS_DISTANCES), output); } diff --git a/core/java/android/hardware/CameraSound.java b/core/java/android/hardware/CameraSound.java deleted file mode 100644 index dc97ff0..0000000 --- a/core/java/android/hardware/CameraSound.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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 android.hardware; - -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.os.SystemProperties; -import android.util.Log; - -import java.io.IOException; - -/** - * <p>Use this class to play an appropriate sound when implementing a custom - * still or video recording mechanism through the preview callbacks.</p> - * - * <p>There is no need to play sounds when using {@link #android.hardware.Camera#takePicture} - * or {@link android.media.MediaRecorder} for still images or video, - * respectively, as these play their own sounds when needed.</p> - * - * @hide - */ -public class CameraSound { - private static final String TAG = "CameraSound"; - /** - * The sound used by {@link android.hardware.Camera#takePicture} to - * indicate still image capture. - */ - public static final int SHUTTER_CLICK = 0; - - /** - * A sound to indicate that focusing has completed. Because deciding - * when this occurs is application-dependent, this sound is not used by - * any methods in the Camera class. - */ - public static final int FOCUS_COMPLETE = 1; - - /** - * The sound used by {@link android.media.MediaRecorder#start} to - * indicate the start of video recording. - */ - public static final int START_VIDEO_RECORDING = 2; - - /** - * The sound used by {@link android.media.MediaRecorder#stop} to - * indicate the end of video recording. - */ - public static final int STOP_VIDEO_RECORDING = 3; - - private static final int NUM_SOUNDS = 4; - private CameraSoundPlayer[] mCameraSoundPlayers; - - public CameraSound() { - } - - /** - * <p>Play one of the predefined platform sounds for camera actions.</p> - * - * <p>Use this method to play a platform-specific sound for various camera - * actions. The sound playing is done asynchronously, with the same behavior - * and content as the sounds played by {@link #takePicture takePicture}, - * {@link android.media.MediaRecorder#start MediaRecorder.start}, and - * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p> - * - * <p>Using this method makes it easy to match the default device sounds - * when recording or capturing data through the preview callbacks.</p> - * - * @param soundId The type of sound to play, selected from SHUTTER_CLICK, - * FOCUS_COMPLETE, START_VIDEO_RECORDING, or STOP_VIDEO_RECORDING. - * @see android.hardware#takePicture - * @see android.media.MediaRecorder - * @see #SHUTTER_CLICK - * @see #FOCUS_COMPLETE - * @see #START_VIDEO_RECORDING - * @see #STOP_VIDEO_RECORDING - */ - public void playSound(int soundId) { - if (mCameraSoundPlayers == null) { - mCameraSoundPlayers = new CameraSoundPlayer[NUM_SOUNDS]; - } - if (mCameraSoundPlayers[soundId] == null) { - mCameraSoundPlayers[soundId] = new CameraSoundPlayer(soundId); - } - mCameraSoundPlayers[soundId].play(); - } - - public void release() { - if (mCameraSoundPlayers != null) { - for (CameraSoundPlayer csp: mCameraSoundPlayers) { - if (csp != null) { - csp.release(); - } - } - mCameraSoundPlayers = null; - } - } - - private static class CameraSoundPlayer implements Runnable { - private int mSoundId; - private MediaPlayer mPlayer; - private Thread mThread; - private boolean mExit; - private int mPlayCount; - - private static final String mShutterSound = - "/system/media/audio/ui/camera_click.ogg"; - private static final String mFocusSound = - "/system/media/audio/ui/camera_focus.ogg"; - private static final String mVideoStartSound = - "/system/media/audio/ui/VideoRecord.ogg"; - private static final String mVideoStopSound = - "/system/media/audio/ui/VideoRecord.ogg"; - - @Override - public void run() { - String soundFilePath; - switch (mSoundId) { - case SHUTTER_CLICK: - soundFilePath = mShutterSound; - break; - case FOCUS_COMPLETE: - soundFilePath = mFocusSound; - break; - case START_VIDEO_RECORDING: - soundFilePath = mVideoStartSound; - break; - case STOP_VIDEO_RECORDING: - soundFilePath = mVideoStopSound; - break; - default: - Log.e(TAG, "Unknown sound " + mSoundId + " requested."); - return; - } - mPlayer = new MediaPlayer(); - try { - mPlayer.setAudioStreamType(AudioManager.STREAM_SYSTEM_ENFORCED); - mPlayer.setDataSource(soundFilePath); - mPlayer.setLooping(false); - mPlayer.prepare(); - } catch(IOException e) { - Log.e(TAG, "Error setting up sound " + mSoundId, e); - return; - } - - while(true) { - try { - synchronized (this) { - while(true) { - if (mExit) { - return; - } else if (mPlayCount <= 0) { - wait(); - } else { - mPlayCount--; - break; - } - } - } - mPlayer.start(); - } catch (Exception e) { - Log.e(TAG, "Error playing sound " + mSoundId, e); - } - } - } - - public CameraSoundPlayer(int soundId) { - mSoundId = soundId; - } - - public void play() { - if (mThread == null) { - mThread = new Thread(this); - mThread.start(); - } - synchronized (this) { - mPlayCount++; - notifyAll(); - } - } - - public void release() { - if (mThread != null) { - synchronized (this) { - mExit = true; - notifyAll(); - } - try { - mThread.join(); - } catch (InterruptedException e) { - } - mThread = null; - } - if (mPlayer != null) { - mPlayer.release(); - mPlayer = null; - } - } - - @Override - protected void finalize() { - release(); - } - } -}
\ No newline at end of file diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 68fc101..63fb32d 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -57,7 +57,7 @@ public class Sensor { public static final int TYPE_GYROSCOPE = 4; /** - * A constant describing an light sensor type. See + * A constant describing a light sensor type. See * {@link android.hardware.SensorEvent#values SensorEvent.values} for more * details. */ @@ -77,7 +77,7 @@ public class Sensor { public static final int TYPE_TEMPERATURE = 7; /** - * A constant describing an proximity sensor type. See + * A constant describing a proximity sensor type. See * {@link android.hardware.SensorEvent#values SensorEvent.values} for more * details. */ diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 53cdf21..ba7dc4a 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -2173,7 +2173,7 @@ public class InputMethodService extends AbstractInputMethodService { * This is called when, while currently displayed in extract mode, the * current input target changes. The default implementation will * auto-hide the IME if the new target is not a full editor, since this - * can be an confusing experience for the user. + * can be a confusing experience for the user. */ public void onExtractingInputChanged(EditorInfo ei) { if (ei.inputType == InputType.TYPE_NULL) { diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java index 06c6c6e..6ad8fe3 100644 --- a/core/java/android/net/http/CertificateChainValidator.java +++ b/core/java/android/net/http/CertificateChainValidator.java @@ -18,6 +18,8 @@ package android.net.http; import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyManagementException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -128,10 +130,13 @@ public class CertificateChainValidator { */ public static void handleTrustStorageUpdate() { - X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultTrustManager(); - if( x509TrustManager instanceof TrustManagerImpl ) { - TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager; - trustManager.handleTrustStorageUpdate(); + try { + X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultTrustManager(); + if( x509TrustManager instanceof TrustManagerImpl ) { + TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager; + trustManager.handleTrustStorageUpdate(); + } + } catch (KeyManagementException ignored) { } } @@ -165,7 +170,7 @@ public class CertificateChainValidator { try { SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(chain, authType); return null; // No errors. - } catch (CertificateException e) { + } catch (GeneralSecurityException e) { if (HttpLog.LOGV) { HttpLog.v("failed to validate the certificate chain, error: " + e.getMessage()); diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java index 863304c..1cd73d2 100644 --- a/core/java/android/net/http/SslError.java +++ b/core/java/android/net/http/SslError.java @@ -64,7 +64,7 @@ public class SslError { public static final int SSL_MAX_ERROR = 6; /** - * The SSL error set bitfield (each individual error is an bit index; + * The SSL error set bitfield (each individual error is a bit index; * multiple individual errors can be OR-ed) */ int mErrors; diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 215e836..6c1445d 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -28,6 +28,8 @@ import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; +import libcore.io.Os; +import libcore.io.StructStat; /** * Tools for managing files. Not for public consumption. @@ -52,8 +54,10 @@ public class FileUtils { /** * File status information. This class maps directly to the POSIX stat structure. + * @deprecated use {@link StructStat} instead. * @hide */ + @Deprecated public static final class FileStatus { public int dev; public int ino; @@ -77,7 +81,9 @@ public class FileUtils { * exists. * @return true if the file exists and false if it does not exist. If you do not have * permission to stat the file, then this method will return false. + * @deprecated use {@link Os#stat(String)} instead. */ + @Deprecated public static boolean getFileStatus(String path, FileStatus status) { StrictMode.noteDiskRead(); return getFileStatusNative(path, status); @@ -90,6 +96,10 @@ public class FileUtils { public static native int setPermissions(String file, int mode, int uid, int gid); + /** + * @deprecated use {@link Os#stat(String)} instead. + */ + @Deprecated public static native int getPermissions(String file, int[] outPermissions); public static native int setUMask(int mask); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 15e3af4..788ab74 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -180,9 +180,14 @@ public final class Parcel { private static final String TAG = "Parcel"; @SuppressWarnings({"UnusedDeclaration"}) - private int mObject; // used by native code - @SuppressWarnings({"UnusedDeclaration"}) - private int mOwnObject; // used by native code + private int mNativePtr; // used by native code + + /** + * Flag indicating if {@link #mNativePtr} was allocated by this object, + * indicating that we're responsible for its lifecycle. + */ + private boolean mOwnsNativeParcelObject; + private RuntimeException mStack; private static final int POOL_SIZE = 6; @@ -224,6 +229,48 @@ public final class Parcel { private static final int EX_ILLEGAL_STATE = -5; private static final int EX_HAS_REPLY_HEADER = -128; // special; see below + private static native int nativeDataSize(int nativePtr); + private static native int nativeDataAvail(int nativePtr); + private static native int nativeDataPosition(int nativePtr); + private static native int nativeDataCapacity(int nativePtr); + private static native void nativeSetDataSize(int nativePtr, int size); + private static native void nativeSetDataPosition(int nativePtr, int pos); + private static native void nativeSetDataCapacity(int nativePtr, int size); + + private static native boolean nativePushAllowFds(int nativePtr, boolean allowFds); + private static native void nativeRestoreAllowFds(int nativePtr, boolean lastValue); + + private static native void nativeWriteByteArray(int nativePtr, byte[] b, int offset, int len); + private static native void nativeWriteInt(int nativePtr, int val); + private static native void nativeWriteLong(int nativePtr, long val); + private static native void nativeWriteFloat(int nativePtr, float val); + private static native void nativeWriteDouble(int nativePtr, double val); + private static native void nativeWriteString(int nativePtr, String val); + private static native void nativeWriteStrongBinder(int nativePtr, IBinder val); + private static native void nativeWriteFileDescriptor(int nativePtr, FileDescriptor val); + + private static native byte[] nativeCreateByteArray(int nativePtr); + private static native int nativeReadInt(int nativePtr); + private static native long nativeReadLong(int nativePtr); + private static native float nativeReadFloat(int nativePtr); + private static native double nativeReadDouble(int nativePtr); + private static native String nativeReadString(int nativePtr); + private static native IBinder nativeReadStrongBinder(int nativePtr); + private static native FileDescriptor nativeReadFileDescriptor(int nativePtr); + + private static native int nativeCreate(); + private static native void nativeFreeBuffer(int nativePtr); + private static native void nativeDestroy(int nativePtr); + + private static native byte[] nativeMarshall(int nativePtr); + private static native void nativeUnmarshall( + int nativePtr, byte[] data, int offest, int length); + private static native void nativeAppendFrom( + int thisNativePtr, int otherNativePtr, int offset, int length); + private static native boolean nativeHasFileDescriptors(int nativePtr); + private static native void nativeWriteInterfaceToken(int nativePtr, String interfaceName); + private static native void nativeEnforceInterface(int nativePtr, String interfaceName); + public final static Parcelable.Creator<String> STRING_CREATOR = new Parcelable.Creator<String>() { public String createFromParcel(Parcel source) { @@ -262,7 +309,15 @@ public final class Parcel { public final void recycle() { if (DEBUG_RECYCLE) mStack = null; freeBuffer(); - final Parcel[] pool = mOwnObject != 0 ? sOwnedPool : sHolderPool; + + final Parcel[] pool; + if (mOwnsNativeParcelObject) { + pool = sOwnedPool; + } else { + mNativePtr = 0; + pool = sHolderPool; + } + synchronized (pool) { for (int i=0; i<POOL_SIZE; i++) { if (pool[i] == null) { @@ -276,19 +331,25 @@ public final class Parcel { /** * Returns the total amount of data contained in the parcel. */ - public final native int dataSize(); + public final int dataSize() { + return nativeDataSize(mNativePtr); + } /** * Returns the amount of data remaining to be read from the * parcel. That is, {@link #dataSize}-{@link #dataPosition}. */ - public final native int dataAvail(); + public final int dataAvail() { + return nativeDataAvail(mNativePtr); + } /** * Returns the current position in the parcel data. Never * more than {@link #dataSize}. */ - public final native int dataPosition(); + public final int dataPosition() { + return nativeDataPosition(mNativePtr); + } /** * Returns the total amount of space in the parcel. This is always @@ -296,7 +357,9 @@ public final class Parcel { * amount of room left until the parcel needs to re-allocate its * data buffer. */ - public final native int dataCapacity(); + public final int dataCapacity() { + return nativeDataCapacity(mNativePtr); + } /** * Change the amount of data in the parcel. Can be either smaller or @@ -305,14 +368,18 @@ public final class Parcel { * * @param size The new number of bytes in the Parcel. */ - public final native void setDataSize(int size); + public final void setDataSize(int size) { + nativeSetDataSize(mNativePtr, size); + } /** * Move the current read/write position in the parcel. * @param pos New offset in the parcel; must be between 0 and * {@link #dataSize}. */ - public final native void setDataPosition(int pos); + public final void setDataPosition(int pos) { + nativeSetDataPosition(mNativePtr, pos); + } /** * Change the capacity (current available space) of the parcel. @@ -321,13 +388,19 @@ public final class Parcel { * less than {@link #dataSize} -- that is, you can not drop existing data * with this method. */ - public final native void setDataCapacity(int size); + public final void setDataCapacity(int size) { + nativeSetDataCapacity(mNativePtr, size); + } /** @hide */ - public final native boolean pushAllowFds(boolean allowFds); + public final boolean pushAllowFds(boolean allowFds) { + return nativePushAllowFds(mNativePtr, allowFds); + } /** @hide */ - public final native void restoreAllowFds(boolean lastValue); + public final void restoreAllowFds(boolean lastValue) { + nativeRestoreAllowFds(mNativePtr, lastValue); + } /** * Returns the raw bytes of the parcel. @@ -340,27 +413,40 @@ public final class Parcel { * such does not attempt to maintain compatibility with data created * in different versions of the platform. */ - public final native byte[] marshall(); + public final byte[] marshall() { + return nativeMarshall(mNativePtr); + } /** * Set the bytes in data to be the raw bytes of this Parcel. */ - public final native void unmarshall(byte[] data, int offest, int length); + public final void unmarshall(byte[] data, int offest, int length) { + nativeUnmarshall(mNativePtr, data, offest, length); + } - public final native void appendFrom(Parcel parcel, int offset, int length); + public final void appendFrom(Parcel parcel, int offset, int length) { + nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length); + } /** * Report whether the parcel contains any marshalled file descriptors. */ - public final native boolean hasFileDescriptors(); + public final boolean hasFileDescriptors() { + return nativeHasFileDescriptors(mNativePtr); + } /** * Store or read an IBinder interface token in the parcel at the current * {@link #dataPosition}. This is used to validate that the marshalled * transaction is intended for the target interface. */ - public final native void writeInterfaceToken(String interfaceName); - public final native void enforceInterface(String interfaceName); + public final void writeInterfaceToken(String interfaceName) { + nativeWriteInterfaceToken(mNativePtr, interfaceName); + } + + public final void enforceInterface(String interfaceName) { + nativeEnforceInterface(mNativePtr, interfaceName); + } /** * Write a byte array into the parcel at the current {@link #dataPosition}, @@ -372,7 +458,7 @@ public final class Parcel { } /** - * Write an byte array into the parcel at the current {@link #dataPosition}, + * Write a byte array into the parcel at the current {@link #dataPosition}, * growing {@link #dataCapacity} if needed. * @param b Bytes to place into the parcel. * @param offset Index of first byte to be written. @@ -384,40 +470,48 @@ public final class Parcel { return; } Arrays.checkOffsetAndCount(b.length, offset, len); - writeNative(b, offset, len); + nativeWriteByteArray(mNativePtr, b, offset, len); } - private native void writeNative(byte[] b, int offset, int len); - /** * Write an integer value into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */ - public final native void writeInt(int val); + public final void writeInt(int val) { + nativeWriteInt(mNativePtr, val); + } /** * Write a long integer value into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */ - public final native void writeLong(long val); + public final void writeLong(long val) { + nativeWriteLong(mNativePtr, val); + } /** * Write a floating point value into the parcel at the current * dataPosition(), growing dataCapacity() if needed. */ - public final native void writeFloat(float val); + public final void writeFloat(float val) { + nativeWriteFloat(mNativePtr, val); + } /** * Write a double precision floating point value into the parcel at the * current dataPosition(), growing dataCapacity() if needed. */ - public final native void writeDouble(double val); + public final void writeDouble(double val) { + nativeWriteDouble(mNativePtr, val); + } /** * Write a string value into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */ - public final native void writeString(String val); + public final void writeString(String val) { + nativeWriteString(mNativePtr, val); + } /** * Write a CharSequence value into the parcel at the current dataPosition(), @@ -432,7 +526,9 @@ public final class Parcel { * Write an object into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */ - public final native void writeStrongBinder(IBinder val); + public final void writeStrongBinder(IBinder val) { + nativeWriteStrongBinder(mNativePtr, val); + } /** * Write an object into the parcel at the current dataPosition(), @@ -452,10 +548,12 @@ public final class Parcel { * accepts contextual flags and will close the original file descriptor * if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p> */ - public final native void writeFileDescriptor(FileDescriptor val); + public final void writeFileDescriptor(FileDescriptor val) { + nativeWriteFileDescriptor(mNativePtr, val); + } /** - * Write an byte value into the parcel at the current dataPosition(), + * Write a byte value into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */ public final void writeByte(byte val) { @@ -1341,29 +1439,39 @@ public final class Parcel { /** * Read an integer value from the parcel at the current dataPosition(). */ - public final native int readInt(); + public final int readInt() { + return nativeReadInt(mNativePtr); + } /** * Read a long integer value from the parcel at the current dataPosition(). */ - public final native long readLong(); + public final long readLong() { + return nativeReadLong(mNativePtr); + } /** * Read a floating point value from the parcel at the current * dataPosition(). */ - public final native float readFloat(); + public final float readFloat() { + return nativeReadFloat(mNativePtr); + } /** * Read a double precision floating point value from the parcel at the * current dataPosition(). */ - public final native double readDouble(); + public final double readDouble() { + return nativeReadDouble(mNativePtr); + } /** * Read a string value from the parcel at the current dataPosition(). */ - public final native String readString(); + public final String readString() { + return nativeReadString(mNativePtr); + } /** * Read a CharSequence value from the parcel at the current dataPosition(). @@ -1376,17 +1484,18 @@ public final class Parcel { /** * Read an object from the parcel at the current dataPosition(). */ - public final native IBinder readStrongBinder(); + public final IBinder readStrongBinder() { + return nativeReadStrongBinder(mNativePtr); + } /** * Read a FileDescriptor from the parcel at the current dataPosition(). */ public final ParcelFileDescriptor readFileDescriptor() { - FileDescriptor fd = internalReadFileDescriptor(); + FileDescriptor fd = nativeReadFileDescriptor(mNativePtr); return fd != null ? new ParcelFileDescriptor(fd) : null; } - private native FileDescriptor internalReadFileDescriptor(); /*package*/ static native FileDescriptor openFileDescriptor(String file, int mode) throws FileNotFoundException; /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig) @@ -1471,7 +1580,9 @@ public final class Parcel { /** * Read and return a byte[] object from the parcel. */ - public final native byte[] createByteArray(); + public final byte[] createByteArray() { + return nativeCreateByteArray(mNativePtr); + } /** * Read a byte[] object from the parcel and copy it into the @@ -2065,12 +2176,37 @@ public final class Parcel { return new Parcel(obj); } - private Parcel(int obj) { + private Parcel(int nativePtr) { if (DEBUG_RECYCLE) { mStack = new RuntimeException(); } //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack); - init(obj); + init(nativePtr); + } + + private void init(int nativePtr) { + if (nativePtr != 0) { + mNativePtr = nativePtr; + mOwnsNativeParcelObject = false; + } else { + mNativePtr = nativeCreate(); + mOwnsNativeParcelObject = true; + } + } + + private void freeBuffer() { + if (mOwnsNativeParcelObject) { + nativeFreeBuffer(mNativePtr); + } + } + + private void destroy() { + if (mNativePtr != 0) { + if (mOwnsNativeParcelObject) { + nativeDestroy(mNativePtr); + } + mNativePtr = 0; + } } @Override @@ -2083,10 +2219,6 @@ public final class Parcel { destroy(); } - private native void freeBuffer(); - private native void init(int obj); - private native void destroy(); - /* package */ void readMapInternal(Map outVal, int N, ClassLoader loader) { while (N > 0) { diff --git a/core/java/android/os/ParcelUuid.java b/core/java/android/os/ParcelUuid.java index 88fcfc5..2c68ddd 100644 --- a/core/java/android/os/ParcelUuid.java +++ b/core/java/android/os/ParcelUuid.java @@ -42,7 +42,7 @@ public final class ParcelUuid implements Parcelable { * * @param uuid * the UUID string to parse. - * @return an ParcelUuid instance. + * @return a ParcelUuid instance. * @throws NullPointerException * if {@code uuid} is {@code null}. * @throws IllegalArgumentException diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index a0ad9c0..759be91 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -109,7 +109,7 @@ import java.util.concurrent.atomic.AtomicInteger; * effort mechanism. Notably, disk or network access from JNI calls * won't necessarily trigger it. Future versions of Android may catch * more (or fewer) operations, so you should never leave StrictMode - * enabled in shipping applications on the Android Market. + * enabled in applications distributed on Google Play. */ public final class StrictMode { private static final String TAG = "StrictMode"; diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index a29ecb5..c7e3c08 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -84,7 +84,7 @@ public class Contacts { @Deprecated public static final int KIND_ORGANIZATION = 4; /** - * Signifies an Phone row that is stored in the Phones table + * Signifies a Phone row that is stored in the Phones table * @deprecated see {@link android.provider.ContactsContract} */ @Deprecated diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 6c6b118..38945c2 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -192,7 +192,7 @@ public final class MediaStore { /** * Standard Intent action that can be sent to have the camera application - * capture an video and return it. + * capture a video and return it. * <p> * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality. * <p> @@ -441,12 +441,12 @@ public final class MediaStore { public static final int MEDIA_TYPE_AUDIO = 2; /** - * Constant for the {@link #MEDIA_TYPE} column indicating that file is an video file. + * Constant for the {@link #MEDIA_TYPE} column indicating that file is a video file. */ public static final int MEDIA_TYPE_VIDEO = 3; /** - * Constant for the {@link #MEDIA_TYPE} column indicating that file is an playlist file. + * Constant for the {@link #MEDIA_TYPE} column indicating that file is a playlist file. */ public static final int MEDIA_TYPE_PLAYLIST = 4; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b42417a..fbb3273 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2644,10 +2644,10 @@ public final class Settings { /** * Whether the package installer should allow installation of apps downloaded from - * sources other than the Android Market (vending machine). + * sources other than Google Play. * * 1 = allow installing from other sources - * 0 = only allow installing from the Android Market + * 0 = only allow installing from Google Play */ public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; @@ -3129,6 +3129,14 @@ public final class Settings { "wifi_watchdog_arp_interval_ms"; /** + * ms delay interval between rssi polling when the signal is known to be weak + * @hide + */ + public static final String WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS = + "wifi_watchdog_rssi_fetch_interval_ms"; + + + /** * ms delay before rechecking a connect SSID for walled garden with a http download. * @hide */ diff --git a/core/java/android/server/BluetoothBondState.java b/core/java/android/server/BluetoothBondState.java index fbc1c27..0446f02 100644 --- a/core/java/android/server/BluetoothBondState.java +++ b/core/java/android/server/BluetoothBondState.java @@ -140,7 +140,7 @@ class BluetoothBondState { return; } - // Check if this was an pending outgoing bonding. + // Check if this was a pending outgoing bonding. // If yes, reset the state. if (oldState == BluetoothDevice.BOND_BONDING) { if (address.equals(mPendingOutgoingBonding)) { diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index a52e2ba..715d1f2 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -222,23 +222,27 @@ class MeasuredText { return wid; } - int breakText(int start, int limit, boolean forwards, float width) { + int breakText(int limit, boolean forwards, float width) { float[] w = mWidths; if (forwards) { - for (int i = start; i < limit; ++i) { - if ((width -= w[i]) < 0) { - return i - start; - } + int i = 0; + while (i < limit) { + width -= w[i]; + if (width < 0.0f) break; + i++; } + while (i > 0 && mChars[i - 1] == ' ') i--; + return i; } else { - for (int i = limit; --i >= start;) { - if ((width -= w[i]) < 0) { - return limit - i -1; - } + int i = limit - 1; + while (i >= 0) { + width -= w[i]; + if (width < 0.0f) break; + i--; } + while (i < limit - 1 && mChars[i + 1] == ' ') i++; + return limit - i - 1; } - - return limit - start; } float measure(int start, int limit) { diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index afae5bb2..270624c 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1091,13 +1091,13 @@ public class TextUtils { if (avail < 0) { // it all goes } else if (where == TruncateAt.START) { - right = len - mt.breakText(0, len, false, avail); + right = len - mt.breakText(len, false, avail); } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) { - left = mt.breakText(0, len, true, avail); + left = mt.breakText(len, true, avail); } else { - right = len - mt.breakText(0, len, false, avail / 2); + right = len - mt.breakText(len, false, avail / 2); avail -= mt.measure(right, len); - left = mt.breakText(0, right, true, avail); + left = mt.breakText(right, true, avail); } if (callback != null) { diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java index f554b90..113a4be 100644 --- a/core/java/android/text/method/BaseMovementMethod.java +++ b/core/java/android/text/method/BaseMovementMethod.java @@ -350,7 +350,7 @@ public class BaseMovementMethod implements MovementMethod { } /** - * Performs an line-end movement action. + * Performs a line-end movement action. * Moves the cursor or scrolls to the end of the line. * * @param widget The text view. diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index f4d7af9..d217cab 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -81,8 +81,8 @@ public final class Choreographer { private static final int MSG_DO_ANIMATION = 0; private static final int MSG_DO_DRAW = 1; private static final int MSG_DO_SCHEDULE_VSYNC = 2; - private static final int MSG_POST_DELAYED_ANIMATION = 3; - private static final int MSG_POST_DELAYED_DRAW = 4; + private static final int MSG_DO_SCHEDULE_ANIMATION = 3; + private static final int MSG_DO_SCHEDULE_DRAW = 4; private final Object mLock = new Object(); @@ -92,8 +92,8 @@ public final class Choreographer { private Callback mCallbackPool; - private Callback mAnimationCallbacks; - private Callback mDrawCallbacks; + private final CallbackQueue mAnimationCallbackQueue = new CallbackQueue(); + private final CallbackQueue mDrawCallbackQueue = new CallbackQueue(); private boolean mAnimationScheduled; private boolean mDrawScheduled; @@ -152,134 +152,176 @@ public final class Choreographer { } /** + * Subtracts typical frame delay time from a delay interval in milliseconds. + * + * This method can be used to compensate for animation delay times that have baked + * in assumptions about the frame delay. For example, it's quite common for code to + * assume a 60Hz frame time and bake in a 16ms delay. When we call + * {@link #postAnimationCallbackDelayed} we want to know how long to wait before + * posting the animation callback but let the animation timer take care of the remaining + * frame delay time. + * + * This method is somewhat conservative about how much of the frame delay it + * subtracts. It uses the same value returned by {@link #getFrameDelay} which by + * default is 10ms even though many parts of the system assume 16ms. Consequently, + * we might still wait 6ms before posting an animation callback that we want to run + * on the next frame, but this is much better than waiting a whole 16ms and likely + * missing the deadline. + * + * @param delayMillis The original delay time including an assumed frame delay. + * @return The adjusted delay time with the assumed frame delay subtracted out. + */ + public static long subtractFrameDelay(long delayMillis) { + final long frameDelay = sFrameDelay; + return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay; + } + + /** * Posts a callback to run on the next animation cycle. * The callback only runs once and then is automatically removed. * - * @param runnable The callback to run during the next animation cycle. + * @param action The callback action to run during the next animation cycle. + * @param token The callback token, or null if none. * * @see #removeAnimationCallback */ - public void postAnimationCallback(Runnable runnable) { - if (runnable == null) { - throw new IllegalArgumentException("runnable must not be null"); - } - postAnimationCallbackUnchecked(runnable); - } - - private void postAnimationCallbackUnchecked(Runnable runnable) { - synchronized (mLock) { - mAnimationCallbacks = addCallbackLocked(mAnimationCallbacks, runnable); - scheduleAnimationLocked(); - } + public void postAnimationCallback(Runnable action, Object token) { + postAnimationCallbackDelayed(action, token, 0); } /** * Posts a callback to run on the next animation cycle following the specified delay. * The callback only runs once and then is automatically removed. * - * @param runnable The callback to run during the next animation cycle following + * @param action The callback action to run during the next animation cycle after * the specified delay. + * @param token The callback token, or null if none. * @param delayMillis The delay time in milliseconds. * * @see #removeAnimationCallback */ - public void postAnimationCallbackDelayed(Runnable runnable, long delayMillis) { - if (runnable == null) { - throw new IllegalArgumentException("runnable must not be null"); + public void postAnimationCallbackDelayed(Runnable action, Object token, long delayMillis) { + if (action == null) { + throw new IllegalArgumentException("action must not be null"); } - if (delayMillis <= 0) { - postAnimationCallbackUnchecked(runnable); - } else { - Message msg = mHandler.obtainMessage(MSG_POST_DELAYED_ANIMATION, runnable); - mHandler.sendMessageDelayed(msg, delayMillis); + + if (DEBUG) { + Log.d(TAG, "PostAnimationCallback: " + action + ", token=" + token + + ", delayMillis=" + delayMillis); + } + + synchronized (mLock) { + final long now = SystemClock.uptimeMillis(); + final long dueTime = now + delayMillis; + mAnimationCallbackQueue.addCallbackLocked(dueTime, action, token); + + if (dueTime <= now) { + scheduleAnimationLocked(now); + } else { + Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_ANIMATION, action); + mHandler.sendMessageAtTime(msg, dueTime); + } } } /** - * Removes animation callbacks for the specified runnable. - * Does nothing if the specified animation callback has not been posted or has already - * been removed. + * Removes animation callbacks that have the specified action and token. * - * @param runnable The animation callback to remove. + * @param action The action property of the callbacks to remove, or null to remove + * callbacks with any action. + * @param token The token property of the callbacks to remove, or null to remove + * callbacks with any token. * * @see #postAnimationCallback * @see #postAnimationCallbackDelayed */ - public void removeAnimationCallbacks(Runnable runnable) { - if (runnable == null) { - throw new IllegalArgumentException("runnable must not be null"); + public void removeAnimationCallbacks(Runnable action, Object token) { + if (DEBUG) { + Log.d(TAG, "RemoveAnimationCallbacks: " + action + ", token=" + token); } + synchronized (mLock) { - mAnimationCallbacks = removeCallbacksLocked(mAnimationCallbacks, runnable); + mAnimationCallbackQueue.removeCallbacksLocked(action, token); + if (action != null && token == null) { + mHandler.removeMessages(MSG_DO_SCHEDULE_ANIMATION, action); + } } - mHandler.removeMessages(MSG_POST_DELAYED_ANIMATION, runnable); } /** * Posts a callback to run on the next draw cycle. * The callback only runs once and then is automatically removed. * - * @param runnable The callback to run during the next draw cycle. + * @param action The callback action to run during the next draw cycle. + * @param token The callback token, or null if none. * * @see #removeDrawCallback */ - public void postDrawCallback(Runnable runnable) { - if (runnable == null) { - throw new IllegalArgumentException("runnable must not be null"); - } - postDrawCallbackUnchecked(runnable); - } - - private void postDrawCallbackUnchecked(Runnable runnable) { - synchronized (mLock) { - mDrawCallbacks = addCallbackLocked(mDrawCallbacks, runnable); - scheduleDrawLocked(); - } + public void postDrawCallback(Runnable action, Object token) { + postDrawCallbackDelayed(action, token, 0); } /** * Posts a callback to run on the next draw cycle following the specified delay. * The callback only runs once and then is automatically removed. * - * @param runnable The callback to run during the next draw cycle following + * @param action The callback action to run during the next animation cycle after * the specified delay. + * @param token The callback token, or null if none. * @param delayMillis The delay time in milliseconds. * * @see #removeDrawCallback */ - public void postDrawCallbackDelayed(Runnable runnable, long delayMillis) { - if (runnable == null) { - throw new IllegalArgumentException("runnable must not be null"); + public void postDrawCallbackDelayed(Runnable action, Object token, long delayMillis) { + if (action == null) { + throw new IllegalArgumentException("action must not be null"); } - if (delayMillis <= 0) { - postDrawCallbackUnchecked(runnable); - } else { - Message msg = mHandler.obtainMessage(MSG_POST_DELAYED_DRAW, runnable); - mHandler.sendMessageDelayed(msg, delayMillis); + + if (DEBUG) { + Log.d(TAG, "PostDrawCallback: " + action + ", token=" + token + + ", delayMillis=" + delayMillis); + } + + synchronized (mLock) { + final long now = SystemClock.uptimeMillis(); + final long dueTime = now + delayMillis; + mDrawCallbackQueue.addCallbackLocked(dueTime, action, token); + scheduleDrawLocked(now); + + if (dueTime <= now) { + scheduleDrawLocked(now); + } else { + Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_DRAW, action); + mHandler.sendMessageAtTime(msg, dueTime); + } } } /** - * Removes draw callbacks for the specified runnable. - * Does nothing if the specified draw callback has not been posted or has already - * been removed. + * Removes draw callbacks that have the specified action and token. * - * @param runnable The draw callback to remove. + * @param action The action property of the callbacks to remove, or null to remove + * callbacks with any action. + * @param token The token property of the callbacks to remove, or null to remove + * callbacks with any token. * * @see #postDrawCallback * @see #postDrawCallbackDelayed */ - public void removeDrawCallbacks(Runnable runnable) { - if (runnable == null) { - throw new IllegalArgumentException("runnable must not be null"); + public void removeDrawCallbacks(Runnable action, Object token) { + if (DEBUG) { + Log.d(TAG, "RemoveDrawCallbacks: " + action + ", token=" + token); } + synchronized (mLock) { - mDrawCallbacks = removeCallbacksLocked(mDrawCallbacks, runnable); + mDrawCallbackQueue.removeCallbacksLocked(action, token); + if (action != null && token == null) { + mHandler.removeMessages(MSG_DO_SCHEDULE_DRAW, action); + } } - mHandler.removeMessages(MSG_POST_DELAYED_DRAW, runnable); } - private void scheduleAnimationLocked() { + private void scheduleAnimationLocked(long now) { if (!mAnimationScheduled) { mAnimationScheduled = true; if (USE_VSYNC) { @@ -291,14 +333,13 @@ public final class Choreographer { // otherwise post a message to schedule the vsync from the UI thread // as soon as possible. if (isRunningOnLooperThreadLocked()) { - doScheduleVsyncLocked(); + scheduleVsyncLocked(); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { - final long now = SystemClock.uptimeMillis(); final long nextAnimationTime = Math.max(mLastAnimationTime + sFrameDelay, now); if (DEBUG) { Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms."); @@ -310,18 +351,18 @@ public final class Choreographer { } } - private void scheduleDrawLocked() { + private void scheduleDrawLocked(long now) { if (!mDrawScheduled) { mDrawScheduled = true; if (USE_ANIMATION_TIMER_FOR_DRAW) { - scheduleAnimationLocked(); + scheduleAnimationLocked(now); } else { if (DEBUG) { Log.d(TAG, "Scheduling draw immediately."); } Message msg = mHandler.obtainMessage(MSG_DO_DRAW); msg.setAsynchronous(true); - mHandler.sendMessage(msg); + mHandler.sendMessageAtTime(msg, now); } } } @@ -336,7 +377,7 @@ public final class Choreographer { void doAnimationInner() { final long start; - final Callback callbacks; + Callback callbacks; synchronized (mLock) { if (!mAnimationScheduled) { return; // no work to do @@ -350,8 +391,7 @@ public final class Choreographer { } mLastAnimationTime = start; - callbacks = mAnimationCallbacks; - mAnimationCallbacks = null; + callbacks = mAnimationCallbackQueue.extractDueCallbacksLocked(start); } if (callbacks != null) { @@ -368,7 +408,7 @@ public final class Choreographer { void doDraw() { final long start; - final Callback callbacks; + Callback callbacks; synchronized (mLock) { if (!mDrawScheduled) { return; // no work to do @@ -382,8 +422,7 @@ public final class Choreographer { } mLastDrawTime = start; - callbacks = mDrawCallbacks; - mDrawCallbacks = null; + callbacks = mDrawCallbackQueue.extractDueCallbacksLocked(start); } if (callbacks != null) { @@ -400,55 +439,45 @@ public final class Choreographer { void doScheduleVsync() { synchronized (mLock) { - doScheduleVsyncLocked(); + if (mAnimationScheduled) { + scheduleVsyncLocked(); + } } } - private void doScheduleVsyncLocked() { - if (mAnimationScheduled) { - mDisplayEventReceiver.scheduleVsync(); + void doScheduleAnimation() { + synchronized (mLock) { + final long now = SystemClock.uptimeMillis(); + if (mAnimationCallbackQueue.hasDueCallbacksLocked(now)) { + scheduleAnimationLocked(now); + } } } - private boolean isRunningOnLooperThreadLocked() { - return Looper.myLooper() == mLooper; + void doScheduleDraw() { + synchronized (mLock) { + final long now = SystemClock.uptimeMillis(); + if (mDrawCallbackQueue.hasDueCallbacksLocked(now)) { + scheduleDrawLocked(now); + } + } } - private Callback addCallbackLocked(Callback head, Runnable runnable) { - Callback callback = obtainCallbackLocked(runnable); - if (head == null) { - return callback; - } - Callback tail = head; - while (tail.next != null) { - tail = tail.next; - } - tail.next = callback; - return head; + private void scheduleVsyncLocked() { + mDisplayEventReceiver.scheduleVsync(); } - private Callback removeCallbacksLocked(Callback head, Runnable runnable) { - Callback predecessor = null; - for (Callback callback = head; callback != null;) { - final Callback next = callback.next; - if (callback.runnable == runnable) { - if (predecessor != null) { - predecessor.next = next; - } else { - head = next; - } - recycleCallbackLocked(callback); - } else { - predecessor = callback; - } - callback = next; - } - return head; + private boolean isRunningOnLooperThreadLocked() { + return Looper.myLooper() == mLooper; } private void runCallbacks(Callback head) { while (head != null) { - head.runnable.run(); + if (DEBUG) { + Log.d(TAG, "RunCallback: " + head.action + ", token=" + head.token + + ", waitMillis=" + (SystemClock.uptimeMillis() - head.dueTime)); + } + head.action.run(); head = head.next; } } @@ -461,7 +490,7 @@ public final class Choreographer { } } - private Callback obtainCallbackLocked(Runnable runnable) { + private Callback obtainCallbackLocked(long dueTime, Runnable action, Object token) { Callback callback = mCallbackPool; if (callback == null) { callback = new Callback(); @@ -469,12 +498,15 @@ public final class Choreographer { mCallbackPool = callback.next; callback.next = null; } - callback.runnable = runnable; + callback.dueTime = dueTime; + callback.action = action; + callback.token = token; return callback; } private void recycleCallbackLocked(Callback callback) { - callback.runnable = null; + callback.action = null; + callback.token = null; callback.next = mCallbackPool; mCallbackPool = callback; } @@ -496,11 +528,11 @@ public final class Choreographer { case MSG_DO_SCHEDULE_VSYNC: doScheduleVsync(); break; - case MSG_POST_DELAYED_ANIMATION: - postAnimationCallbackUnchecked((Runnable)msg.obj); + case MSG_DO_SCHEDULE_ANIMATION: + doScheduleAnimation(); break; - case MSG_POST_DELAYED_DRAW: - postDrawCallbackUnchecked((Runnable)msg.obj); + case MSG_DO_SCHEDULE_DRAW: + doScheduleDraw(); break; } } @@ -519,6 +551,77 @@ public final class Choreographer { private static final class Callback { public Callback next; - public Runnable runnable; + public long dueTime; + public Runnable action; + public Object token; + } + + private final class CallbackQueue { + private Callback mHead; + + public boolean hasDueCallbacksLocked(long now) { + return mHead != null && mHead.dueTime <= now; + } + + public Callback extractDueCallbacksLocked(long now) { + Callback callbacks = mHead; + if (callbacks == null || callbacks.dueTime > now) { + return null; + } + + Callback last = callbacks; + Callback next = last.next; + while (next != null) { + if (next.dueTime > now) { + last.next = null; + break; + } + last = next; + next = next.next; + } + mHead = next; + return callbacks; + } + + public void addCallbackLocked(long dueTime, Runnable action, Object token) { + Callback callback = obtainCallbackLocked(dueTime, action, token); + Callback entry = mHead; + if (entry == null) { + mHead = callback; + return; + } + if (dueTime < entry.dueTime) { + callback.next = entry; + mHead = callback; + return; + } + while (entry.next != null) { + if (dueTime < entry.next.dueTime) { + callback.next = entry.next; + break; + } + entry = entry.next; + } + entry.next = callback; + } + + public void removeCallbacksLocked(Runnable action, Object token) { + Callback predecessor = null; + for (Callback callback = mHead; callback != null;) { + final Callback next = callback.next; + if ((action == null || callback.action == action) + && (token == null || callback.token == token)) { + if (predecessor != null) { + predecessor.next = next; + } else { + mHead = next; + } + recycleCallbackLocked(callback); + } else { + predecessor = callback; + } + callback = next; + } + } } } diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index 49450bd..a97167b 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -167,7 +167,7 @@ abstract class HardwareLayer { /** * Specifies the display list to use to refresh the layer. - * + * * @param displayList The display list containing the drawing commands to * execute in this layer * @param dirtyRect The dirty region of the layer that needs to be redrawn diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index ec95863..bf91700 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -1284,15 +1284,10 @@ public abstract class HardwareRenderer { usePbufferSurface(managedContext.getContext()); } - switch (level) { - case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: - case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: - case ComponentCallbacks2.TRIM_MEMORY_MODERATE: - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE); - break; - case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL); - break; + if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { + GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL); + } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { + GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE); } } diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 715fa7b..497bc90b 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -49,6 +49,7 @@ oneway interface IWindow { boolean reportDraw, in Configuration newConfig); void dispatchAppVisibility(boolean visible); void dispatchGetNewSurface(); + void dispatchScreenState(boolean on); /** * Tell the window that it is either gaining or losing focus. Keep it up diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index c1e9946..fc02cc1 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -315,7 +315,7 @@ public class TextureView extends View { } @Override - HardwareLayer getHardwareLayer(boolean immediateRefresh) { + HardwareLayer getHardwareLayer() { if (mLayer == null) { if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) { return null; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ecfca74..7a1923b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1994,6 +1994,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS = 0x00000004; /** + * Indicates that the screen has changed state and is now off. + * + * @see #onScreenStateChanged(int) + */ + public static final int SCREEN_STATE_OFF = 0x0; + + /** + * Indicates that the screen has changed state and is now on. + * + * @see #onScreenStateChanged(int) + */ + public static final int SCREEN_STATE_ON = 0x1; + + /** * Controls the over-scroll mode for this view. * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)}, * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}, @@ -3093,13 +3107,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal setBackgroundDrawable(background); } - mUserPaddingRelative = (startPadding >= 0 || endPadding >= 0); - // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved // layout direction). Those cached values will be used later during padding resolution. mUserPaddingStart = startPadding; mUserPaddingEnd = endPadding; + updateUserPaddingRelative(); + if (padding >= 0) { leftPadding = padding; topPadding = padding; @@ -3146,6 +3160,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal computeOpaqueFlags(); } + private void updateUserPaddingRelative() { + mUserPaddingRelative = (mUserPaddingStart >= 0 || mUserPaddingEnd >= 0); + } + /** * Non-public constructor for use in testing */ @@ -3934,6 +3952,24 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} + * {@link AccessibilityEvent} to make an announcement which is related to some + * sort of a context change for which none of the events representing UI transitions + * is a good fit. For example, announcing a new page in a book. If accessibility + * is not enabled this method does nothing. + * + * @param text The announcement text. + */ + public void announceForAccessibility(CharSequence text) { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_ANNOUNCEMENT); + event.getText().add(text); + sendAccessibilityEventUnchecked(event); + } + } + + /** * @see #sendAccessibilityEvent(int) * * Note: Called from the default {@link AccessibilityDelegate}. @@ -5247,6 +5283,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mNextFocusForwardId == View.NO_ID) return null; return findViewInsideOutShouldExist(root, mNextFocusForwardId); case FOCUS_BACKWARD: { + if (mID == View.NO_ID) return null; final int id = mID; return root.findViewByPredicateInsideOut(this, new Predicate<View>() { @Override @@ -8774,6 +8811,52 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * <p>Causes the Runnable to execute on the next animation time step. + * The runnable will be run on the user interface thread.</p> + * + * <p>This method can be invoked from outside of the UI thread + * only when this View is attached to a window.</p> + * + * @param action The Runnable that will be executed. + * + * @hide + */ + public void postOnAnimation(Runnable action) { + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + attachInfo.mViewRootImpl.mChoreographer.postAnimationCallback(action, null); + } else { + // Assume that post will succeed later + ViewRootImpl.getRunQueue().post(action); + } + } + + /** + * <p>Causes the Runnable to execute on the next animation time step, + * after the specified amount of time elapses. + * The runnable will be run on the user interface thread.</p> + * + * <p>This method can be invoked from outside of the UI thread + * only when this View is attached to a window.</p> + * + * @param action The Runnable that will be executed. + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + * + * @hide + */ + public void postOnAnimationDelayed(Runnable action, long delayMillis) { + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + attachInfo.mViewRootImpl.mChoreographer.postAnimationCallbackDelayed( + action, null, delayMillis); + } else { + // Assume that post will succeed later + ViewRootImpl.getRunQueue().postDelayed(action, delayMillis); + } + } + + /** * <p>Removes the specified Runnable from the message queue.</p> * * <p>This method can be invoked from outside of the UI thread @@ -8787,12 +8870,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * (for instance, if the Runnable was not in the queue already.) */ public boolean removeCallbacks(Runnable action) { - final AttachInfo attachInfo = mAttachInfo; - if (attachInfo != null) { - attachInfo.mHandler.removeCallbacks(action); - } else { - // Assume that post will succeed later - ViewRootImpl.getRunQueue().removeCallbacks(action); + if (action != null) { + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + attachInfo.mHandler.removeCallbacks(action); + attachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(action, null); + } else { + // Assume that post will succeed later + ViewRootImpl.getRunQueue().removeCallbacks(action); + } } return true; } @@ -9599,6 +9685,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * @see #onScreenStateChanged(int) + */ + void dispatchScreenStateChanged(int screenState) { + onScreenStateChanged(screenState); + } + + /** + * This method is called whenever the state of the screen this view is + * attached to changes. A state change will usually occurs when the screen + * turns on or off (whether it happens automatically or the user does it + * manually.) + * + * @param screenState The new state of the screen. Can be either + * {@link #SCREEN_STATE_ON} or {@link #SCREEN_STATE_OFF} + */ + public void onScreenStateChanged(int screenState) { + } + + /** * Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing * that the parent directionality can and will be resolved before its children. */ @@ -9643,6 +9748,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Set to resolved mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED; onResolvedLayoutDirectionChanged(); + // Resolve padding + resolvePadding(); } /** @@ -9697,7 +9804,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom; - recomputePadding(); + if(isPaddingRelative()) { + setPaddingRelative(mUserPaddingStart, mPaddingTop, mUserPaddingEnd, mUserPaddingBottom); + } else { + recomputePadding(); + } onPaddingChanged(resolvedLayoutDirection); } @@ -10220,7 +10331,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled() && mAttachInfo.mHardwareRenderer.validate()) { - getHardwareLayer(true); + getHardwareLayer(); } break; case LAYER_TYPE_SOFTWARE: @@ -10242,7 +10353,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return A HardwareLayer ready to render, or null if an error occurred. */ - HardwareLayer getHardwareLayer(boolean immediateRefresh) { + HardwareLayer getHardwareLayer() { if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null || !mAttachInfo.mHardwareRenderer.isEnabled()) { return null; @@ -10272,33 +10383,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return null; } - if (!immediateRefresh) { - mHardwareLayer.redraw(getDisplayList(), mLocalDirtyRect); - mLocalDirtyRect.setEmpty(); - } else { - HardwareCanvas currentCanvas = mAttachInfo.mHardwareCanvas; - final HardwareCanvas canvas = mHardwareLayer.start(currentCanvas); - - // Make sure all the GPU resources have been properly allocated - if (canvas == null) { - mHardwareLayer.end(currentCanvas); - return null; - } - - mAttachInfo.mHardwareCanvas = canvas; - try { - canvas.setViewport(width, height); - canvas.onPreDraw(mLocalDirtyRect); - mLocalDirtyRect.setEmpty(); - - canvas.drawDisplayList(getDisplayList(), mRight - mLeft, mBottom - mTop, null, - DisplayList.FLAG_CLIP_CHILDREN); - } finally { - canvas.onPostDraw(); - mHardwareLayer.end(currentCanvas); - mAttachInfo.mHardwareCanvas = currentCanvas; - } - } + mHardwareLayer.redraw(getDisplayList(), mLocalDirtyRect); + mLocalDirtyRect.setEmpty(); } return mHardwareLayer; @@ -10990,13 +11076,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * <p>Indicates whether this view is attached to an hardware accelerated + * <p>Indicates whether this view is attached to a hardware accelerated * window or not.</p> * * <p>Even if this method returns true, it does not mean that every call * to {@link #draw(android.graphics.Canvas)} will be made with an hardware * accelerated {@link android.graphics.Canvas}. For instance, if this view - * is drawn onto an offscren {@link android.graphics.Bitmap} and its + * is drawn onto an offscreen {@link android.graphics.Bitmap} and its * window is hardware accelerated, * {@link android.graphics.Canvas#isHardwareAccelerated()} will likely * return false, and this method will return true.</p> @@ -11265,7 +11351,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (hasNoCache) { boolean layerRendered = false; if (layerType == LAYER_TYPE_HARDWARE) { - final HardwareLayer layer = getHardwareLayer(false); + final HardwareLayer layer = getHardwareLayer(); if (layer != null && layer.isValid()) { mLayerPaint.setAlpha((int) (alpha * 255)); ((HardwareCanvas) canvas).drawHardwareLayer(layer, 0, 0, mLayerPaint); @@ -11861,10 +11947,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void scheduleDrawable(Drawable who, Runnable what, long when) { if (verifyDrawable(who) && what != null) { + final long delay = when - SystemClock.uptimeMillis(); if (mAttachInfo != null) { - mAttachInfo.mHandler.postAtTime(what, who, when); + mAttachInfo.mViewRootImpl.mChoreographer.postAnimationCallbackDelayed( + what, who, Choreographer.subtractFrameDelay(delay)); } else { - ViewRootImpl.getRunQueue().postDelayed(what, when - SystemClock.uptimeMillis()); + ViewRootImpl.getRunQueue().postDelayed(what, delay); } } } @@ -11878,7 +11966,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public void unscheduleDrawable(Drawable who, Runnable what) { if (verifyDrawable(who) && what != null) { if (mAttachInfo != null) { - mAttachInfo.mHandler.removeCallbacks(what, who); + mAttachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(what, who); } else { ViewRootImpl.getRunQueue().removeCallbacks(what); } @@ -11895,8 +11983,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #drawableStateChanged */ public void unscheduleDrawable(Drawable who) { - if (mAttachInfo != null) { - mAttachInfo.mHandler.removeCallbacksAndMessages(who); + if (mAttachInfo != null && who != null) { + mAttachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(null, who); } } @@ -12257,15 +12345,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param bottom the bottom padding in pixels */ public void setPadding(int left, int top, int right, int bottom) { - boolean changed = false; - + mUserPaddingStart = -1; + mUserPaddingEnd = -1; mUserPaddingRelative = false; + internalSetPadding(left, top, right, bottom); + } + + private void internalSetPadding(int left, int top, int right, int bottom) { mUserPaddingLeft = left; mUserPaddingRight = right; mUserPaddingBottom = bottom; final int viewFlags = mViewFlags; + boolean changed = false; // Common case is there are no scroll bars. if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) { @@ -12334,18 +12427,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param bottom the bottom padding in pixels */ public void setPaddingRelative(int start, int top, int end, int bottom) { - mUserPaddingRelative = true; - mUserPaddingStart = start; mUserPaddingEnd = end; + mUserPaddingRelative = true; switch(getResolvedLayoutDirection()) { case LAYOUT_DIRECTION_RTL: - setPadding(end, top, start, bottom); + internalSetPadding(end, top, start, bottom); break; case LAYOUT_DIRECTION_LTR: default: - setPadding(start, top, end, bottom); + internalSetPadding(start, top, end, bottom); } } @@ -13053,7 +13145,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Creates an string of whitespaces used for indentation. + * Creates a string of whitespaces used for indentation. * * @param depth the indentation level * @return a String containing (depth * 2 + 3) * 2 white spaces @@ -14895,6 +14987,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal boolean mHardwareAccelerationRequested; HardwareRenderer mHardwareRenderer; + boolean mScreenOn; + /** * Scale factor used by the compatibility mode */ diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index b455ad5..20183ee 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -305,8 +305,9 @@ public class ViewConfiguration { mScaledTouchExplorationTapSlop = (int) (density * TOUCH_EXPLORATION_TAP_SLOP + 0.5f); mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f); + final Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); // Size of the screen in bytes, in ARGB_8888 format - mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels; + mMaximumDrawingCacheSize = 4 * display.getRawWidth() * display.getRawHeight(); mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f); mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f); diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 2a17845..8f6badf 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -264,7 +264,7 @@ public class ViewDebug { /** * Defines a mapping from an int value to a String. Such a mapping can be used - * in a @ExportedProperty to provide more meaningful values to the end user. + * in an @ExportedProperty to provide more meaningful values to the end user. * * @see android.view.ViewDebug.ExportedProperty */ @@ -287,8 +287,8 @@ public class ViewDebug { } /** - * Defines a mapping from an flag to a String. Such a mapping can be used - * in a @ExportedProperty to provide more meaningful values to the end user. + * Defines a mapping from a flag to a String. Such a mapping can be used + * in an @ExportedProperty to provide more meaningful values to the end user. * * @see android.view.ViewDebug.ExportedProperty */ diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 0c63286..c9e0242 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2254,6 +2254,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override + void dispatchScreenStateChanged(int screenState) { + super.dispatchScreenStateChanged(screenState); + + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchScreenStateChanged(screenState); + } + } + + @Override boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { boolean handled = super.dispatchPopulateAccessibilityEventInternal(event); if (handled) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 80d4c53..72365c7 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -46,6 +46,7 @@ import android.os.LatencyTimer; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -390,6 +391,9 @@ public final class ViewRootImpl implements ViewParent, mProfileRendering = Boolean.parseBoolean( SystemProperties.get(PROPERTY_PROFILE_RENDERING, "false")); mChoreographer = Choreographer.getInstance(); + + PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mAttachInfo.mScreenOn = powerManager.isScreenOn(); } /** @@ -757,6 +761,19 @@ public final class ViewRootImpl implements ViewParent, scheduleTraversals(); } + void handleScreenStateChange(boolean on) { + if (on != mAttachInfo.mScreenOn) { + mAttachInfo.mScreenOn = on; + if (mView != null) { + mView.dispatchScreenStateChanged(on ? View.SCREEN_STATE_ON : View.SCREEN_STATE_OFF); + } + if (on) { + mFullRedrawNeeded = true; + scheduleTraversals(); + } + } + } + /** * {@inheritDoc} */ @@ -867,7 +884,7 @@ public final class ViewRootImpl implements ViewParent, void scheduleFrame() { if (!mFrameScheduled) { mFrameScheduled = true; - mChoreographer.postDrawCallback(mFrameRunnable); + mChoreographer.postDrawCallback(mFrameRunnable, null); } } @@ -876,7 +893,7 @@ public final class ViewRootImpl implements ViewParent, if (mFrameScheduled) { mFrameScheduled = false; - mChoreographer.removeDrawCallbacks(mFrameRunnable); + mChoreographer.removeDrawCallbacks(mFrameRunnable, null); } } @@ -1886,6 +1903,8 @@ public final class ViewRootImpl implements ViewParent, } private void performDraw() { + if (!mAttachInfo.mScreenOn) return; + final long drawStartTime; if (ViewDebug.DEBUG_LATENCY) { drawStartTime = System.nanoTime(); @@ -2018,8 +2037,7 @@ public final class ViewRootImpl implements ViewParent, } if (!dirty.isEmpty() || mIsAnimating) { - if (mAttachInfo.mHardwareRenderer != null - && mAttachInfo.mHardwareRenderer.isEnabled()) { + if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. mIsAnimating = false; mHardwareYOffset = yoff; @@ -2485,6 +2503,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 21; private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 22; private final static int MSG_PROCESS_INPUT_EVENTS = 23; + private final static int MSG_DISPATCH_SCREEN_STATE = 24; final class ViewRootHandler extends Handler { @Override @@ -2741,6 +2760,11 @@ public final class ViewRootImpl implements ViewParent, .findAccessibilityNodeInfosByTextUiThread(msg); } } break; + case MSG_DISPATCH_SCREEN_STATE: { + if (mView != null) { + handleScreenStateChange(msg.arg1 == 1); + } + } break; } } } @@ -4027,7 +4051,7 @@ public final class ViewRootImpl implements ViewParent, } if (mPosted && mViews.isEmpty() && mViewRects.isEmpty()) { - mChoreographer.removeAnimationCallbacks(this); + mChoreographer.removeAnimationCallbacks(this, null); mPosted = false; } } @@ -4068,7 +4092,7 @@ public final class ViewRootImpl implements ViewParent, private void postIfNeededLocked() { if (!mPosted) { - mChoreographer.postAnimationCallback(this); + mChoreographer.postAnimationCallback(this, null); mPosted = true; } } @@ -4121,6 +4145,12 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } + public void dispatchScreenStateChange(boolean on) { + Message msg = mHandler.obtainMessage(MSG_DISPATCH_SCREEN_STATE); + msg.arg1 = on ? 1 : 0; + mHandler.sendMessage(msg); + } + public void dispatchGetNewSurface() { Message msg = mHandler.obtainMessage(MSG_DISPATCH_GET_NEW_SURFACE); mHandler.sendMessage(msg); @@ -4322,6 +4352,13 @@ public final class ViewRootImpl implements ViewParent, } } + public void dispatchScreenState(boolean on) { + final ViewRootImpl viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchScreenStateChange(on); + } + } + public void dispatchGetNewSurface() { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c0eb65b..f3ef329 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -787,10 +787,10 @@ public interface WindowManager extends ViewManager { * hardware accelerated. This is used for the starting preview windows * in the system process, which don't need to have the overhead of * hardware acceleration (they are just a static rendering), but should - * be rendered as much to match the actual window of the app even if it + * be rendered as such to match the actual window of the app even if it * is hardware accelerated. * Even if the window isn't hardware accelerated, still do its rendering - * as if it is. + * as if it was. * Like {@link #FLAG_HARDWARE_ACCELERATED} except for trusted system windows * that need hardware acceleration (e.g. LockScreen), where hardware acceleration * is generally disabled. This flag must be specified in addition to @@ -803,7 +803,7 @@ public interface WindowManager extends ViewManager { /** * In the system process, we globally do not use hardware acceleration - * because there are many threads doing UI there and they an conflict. + * because there are many threads doing UI there and they conflict. * If certain parts of the UI that really do want to use hardware * acceleration, this flag can be set to force it. This is basically * for the lock screen. Anyone else using it, you are probably wrong. @@ -814,7 +814,7 @@ public interface WindowManager extends ViewManager { /** * By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers - * may elect to skp these notifications if they are no doing anything productive with + * may elect to skip these notifications if they are not doing anything productive with * them (they do not affect the wallpaper scrolling operation) by calling * {@link * android.service.wallpaper.WallpaperService.Engine#setOffsetNotificationsEnabled(boolean)}. diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index d482b35..0e4a30f 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -432,29 +432,25 @@ public class WindowManagerImpl implements WindowManager { */ public void trimMemory(int level) { if (HardwareRenderer.isAvailable()) { - switch (level) { - case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: - case ComponentCallbacks2.TRIM_MEMORY_MODERATE: - // On low and medium end gfx devices - if (!ActivityManager.isHighEndGfx(getDefaultDisplay())) { - // Destroy all hardware surfaces and resources associated to - // known windows - synchronized (this) { - if (mViews == null) return; - int count = mViews.length; - for (int i = 0; i < count; i++) { - mRoots[i].terminateHardwareResources(); - } + // On low and medium end gfx devices + if (!ActivityManager.isHighEndGfx(getDefaultDisplay())) { + if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { + // Destroy all hardware surfaces and resources associated to + // known windows + synchronized (this) { + if (mViews == null) return; + int count = mViews.length; + for (int i = 0; i < count; i++) { + mRoots[i].terminateHardwareResources(); } - // Force a full memory flush - HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); - mNeedsEglTerminate = true; - break; } - // high end gfx devices fall through to next case - default: - HardwareRenderer.trimMemory(level); + // Force a full memory flush + HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); + mNeedsEglTerminate = true; + return; + } } + HardwareRenderer.trimMemory(level); } } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 75b875a..58844fc 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -429,6 +429,26 @@ import java.util.List; * view.</br> * </p> * <p> + * <b>MISCELLANEOUS TYPES</b></br> + * </p> + * <p> + * <b>Announcement</b> - represents the event of an application making an + * announcement. Usually this announcement is related to some sort of a context + * change for which none of the events representing UI transitions is a good fit. + * For example, announcing a new page in a book.</br> + * <em>Type:</em> {@link #TYPE_ANNOUNCEMENT}</br> + * <em>Properties:</em></br> + * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> + * <li>{@link #getSource()} - The source info (for registered clients).</li> + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the announcement.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * </ul> + * </p> + * <p> * <b>Security note</b> * <p> * Since an event contains the text of its source privacy can be compromised by leaking @@ -538,6 +558,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000; /** + * Represents the event of an application making an announcement. + */ + public static final int TYPE_ANNOUNCEMENT = 0x00004000; + + /** * Mask for {@link AccessibilityEvent} all types. * * @see #TYPE_VIEW_CLICKED @@ -554,6 +579,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @see #TYPE_WINDOW_CONTENT_CHANGED * @see #TYPE_VIEW_SCROLLED * @see #TYPE_VIEW_TEXT_SELECTION_CHANGED + * @see #TYPE_ANNOUNCEMENT */ public static final int TYPES_ALL_MASK = 0xFFFFFFFF; @@ -984,6 +1010,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par return "TYPE_VIEW_TEXT_SELECTION_CHANGED"; case TYPE_VIEW_SCROLLED: return "TYPE_VIEW_SCROLLED"; + case TYPE_ANNOUNCEMENT: + return "TYPE_ANNOUNCEMENT"; default: return null; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index c094fda..03c6211 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -994,7 +994,7 @@ public class AccessibilityNodeInfo implements Parcelable { protected void enforceNotSealed() { if (isSealed()) { throw new IllegalStateException("Cannot perform this " - + "action on an sealed instance."); + + "action on a sealed instance."); } } diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 23b235c..bc6074f 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -632,7 +632,7 @@ public class AccessibilityRecord { void enforceNotSealed() { if (isSealed()) { throw new IllegalStateException("Cannot perform this " - + "action on an sealed instance."); + + "action on a sealed instance."); } } diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java index 9d8475d..137743a 100644 --- a/core/java/android/view/textservice/SpellCheckerInfo.java +++ b/core/java/android/view/textservice/SpellCheckerInfo.java @@ -38,7 +38,7 @@ import java.io.IOException; import java.util.ArrayList; /** - * This class is used to specify meta information of an spell checker. + * This class is used to specify meta information of a spell checker. */ public final class SpellCheckerInfo implements Parcelable { private static final String TAG = SpellCheckerInfo.class.getSimpleName(); @@ -53,7 +53,7 @@ public final class SpellCheckerInfo implements Parcelable { private final String mSettingsActivityName; /** - * The array of the subtypes. + * The array of subtypes. */ private final ArrayList<SpellCheckerSubtype> mSubtypes = new ArrayList<SpellCheckerSubtype>(); diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java index 9105f19..6ff3b9b 100644 --- a/core/java/android/view/textservice/SpellCheckerSession.java +++ b/core/java/android/view/textservice/SpellCheckerSession.java @@ -281,9 +281,6 @@ public class SpellCheckerSession { if (DBG) { Log.w(TAG, "Get suggestions from the spell checker."); } - if (scp.mTextInfos.length != 1) { - throw new IllegalArgumentException(); - } try { session.onGetSentenceSuggestionsMultiple( scp.mTextInfos, scp.mSuggestionsLimit); diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 2afb841..800ebc8 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -76,6 +76,8 @@ class CallbackProxy extends Handler { private volatile WebBackForwardListClient mWebBackForwardListClient; // Used to call startActivity during url override. private final Context mContext; + // block messages flag for destroy + private boolean mBlockMessages; // Message IDs private static final int PAGE_STARTED = 100; @@ -155,10 +157,18 @@ class CallbackProxy extends Handler { mBackForwardList = new WebBackForwardList(this); } + protected synchronized void blockMessages() { + mBlockMessages = true; + } + + protected synchronized boolean messagesBlocked() { + return mBlockMessages; + } + protected void shutdown() { + removeCallbacksAndMessages(null); setWebViewClient(null); setWebChromeClient(null); - removeCallbacksAndMessages(null); } /** @@ -265,6 +275,7 @@ class CallbackProxy extends Handler { // in the UI thread. The WebViewClient and WebChromeClient functions // that check for a non-null callback are ok because java ensures atomic // 32-bit reads and writes. + if (messagesBlocked()) return; switch (msg.what) { case PAGE_STARTED: String startedUrl = msg.getData().getString("url"); diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java index 542dd21..9970c93 100644 --- a/core/java/android/webkit/URLUtil.java +++ b/core/java/android/webkit/URLUtil.java @@ -182,7 +182,7 @@ public final class URLUtil { } /** - * @return True iff the url is an proxy url to allow cookieless network + * @return True iff the url is a proxy url to allow cookieless network * requests from a file url. * @deprecated Cookieless proxy is no longer supported. */ diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java index a22fc26..e672b62 100644 --- a/core/java/android/webkit/ViewStateSerializer.java +++ b/core/java/android/webkit/ViewStateSerializer.java @@ -52,12 +52,12 @@ class ViewStateSerializer { throws IOException { DataInputStream dis = new DataInputStream(stream); int version = dis.readInt(); - if (version != VERSION) { + if (version > VERSION) { throw new IOException("Unexpected version: " + version); } int contentWidth = dis.readInt(); int contentHeight = dis.readInt(); - int baseLayer = nativeDeserializeViewState(dis, + int baseLayer = nativeDeserializeViewState(version, dis, new byte[WORKING_STREAM_STORAGE]); final WebViewCore.DrawData draw = new WebViewCore.DrawData(); @@ -76,7 +76,7 @@ class ViewStateSerializer { OutputStream stream, byte[] storage); // Returns a pointer to the BaseLayer - private static native int nativeDeserializeViewState( + private static native int nativeDeserializeViewState(int version, InputStream stream, byte[] storage); private ViewStateSerializer() {} diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index c9a3ff1..856f787 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -1121,7 +1121,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc static final int WEBCORE_INITIALIZED_MSG_ID = 107; static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 108; static final int UPDATE_ZOOM_RANGE = 109; - static final int UNHANDLED_NAV_KEY = 110; + static final int TAKE_FOCUS = 110; static final int CLEAR_TEXT_ENTRY = 111; static final int UPDATE_TEXT_SELECTION_MSG_ID = 112; static final int SHOW_RECT_MSG_ID = 113; @@ -1357,14 +1357,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Construct a new WebView with layout parameters, a default style and a set - * of custom Javscript interfaces to be added to the WebView at initialization - * time. This guarantees that these interfaces will be available when the JS - * context is initialized. - * @param javaScriptInterfaces is a Map of interface names, as keys, and - * object implementing those interfaces, as values. - * @param privateBrowsing If true the web view will be initialized in private mode. - * @hide This is an implementation detail. + * See {@link WebViewProvider#init(Map, boolean)} */ @Override public void init(Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { @@ -1867,36 +1860,36 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Specify whether the horizontal scrollbar has overlay style. - * @param overlay TRUE if horizontal scrollbar should have overlay style. + * See {@link WebView#setHorizontalScrollbarOverlay(boolean)} */ + @Override public void setHorizontalScrollbarOverlay(boolean overlay) { checkThread(); mOverlayHorizontalScrollbar = overlay; } /** - * Specify whether the vertical scrollbar has overlay style. - * @param overlay TRUE if vertical scrollbar should have overlay style. + * See {@link WebView#setVerticalScrollbarOverlay(boolean) */ + @Override public void setVerticalScrollbarOverlay(boolean overlay) { checkThread(); mOverlayVerticalScrollbar = overlay; } /** - * Return whether horizontal scrollbar has overlay style - * @return TRUE if horizontal scrollbar has overlay style. + * See {@link WebView#overlayHorizontalScrollbar()} */ + @Override public boolean overlayHorizontalScrollbar() { checkThread(); return mOverlayHorizontalScrollbar; } /** - * Return whether vertical scrollbar has overlay style - * @return TRUE if vertical scrollbar has overlay style. + * See {@link WebView#overlayVerticalScrollbar()} */ + @Override public boolean overlayVerticalScrollbar() { checkThread(); return mOverlayVerticalScrollbar; @@ -1934,11 +1927,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Return the visible height (in pixels) of the embedded title bar (if any). - * - * @return This method is obsolete and always returns 0. - * @deprecated This method is now obsolete. + * See {@link WebView#getVisibleTitleHeight()} */ + @Override @Deprecated public int getVisibleTitleHeight() { // Actually, this method returns the height of the embedded title bar if one is set via the @@ -1985,17 +1976,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * @return The SSL certificate for the main top-level page or null if - * there is no certificate (the site is not secure). + * See {@link WebView#getCertificate()} */ + @Override public SslCertificate getCertificate() { checkThread(); return mCertificate; } /** - * Sets the SSL certificate for the main top-level page. + * See {@link WebView#setCertificate(SslCertificate)} */ + @Override public void setCertificate(SslCertificate certificate) { checkThread(); if (DebugFlags.WEB_VIEW) { @@ -2010,26 +2002,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc //------------------------------------------------------------------------- /** - * Save the username and password for a particular host in the WebView's - * internal database. - * @param host The host that required the credentials. - * @param username The username for the given host. - * @param password The password for the given host. + * See {@link WebView#savePassword(String, String, String)} */ + @Override public void savePassword(String host, String username, String password) { checkThread(); mDatabase.setUsernamePassword(host, username, password); } /** - * Set the HTTP authentication credentials for a given host and realm. - * - * @param host The host for the credentials. - * @param realm The realm for the credentials. - * @param username The username for the password. If it is null, it means - * password can't be saved. - * @param password The password + * See {@link WebView#setHttpAuthUsernamePassword(String, String, String, String)} */ + @Override public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) { checkThread(); @@ -2037,14 +2021,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Retrieve the HTTP authentication username and password for a given - * host & realm pair - * - * @param host The host for which the credentials apply. - * @param realm The realm for which the credentials apply. - * @return String[] if found, String[0] is username, which can be null and - * String[1] is password. Return null if it can't find anything. + * See {@link WebView#getHttpAuthUsernamePassword(String, String)} */ + @Override public String[] getHttpAuthUsernamePassword(String host, String realm) { checkThread(); return mDatabase.getHttpAuthUsernamePassword(host, realm); @@ -2082,16 +2061,16 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Destroy the internal state of the WebView. This method should be called - * after the WebView has been removed from the view system. No other - * methods may be called on a WebView after destroy. + * See {@link WebView#destroy()} */ + @Override public void destroy() { checkThread(); destroyImpl(); } private void destroyImpl() { + mCallbackProxy.blockMessages(); clearHelpers(); if (mListBoxDialog != null) { mListBoxDialog.dismiss(); @@ -2115,10 +2094,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Enables platform notifications of data state and proxy changes. - * Notifications are enabled by default. - * - * @deprecated This method is now obsolete. + * See {@link WebView#enablePlatformNotifications()} */ @Deprecated public static void enablePlatformNotifications() { @@ -2132,10 +2108,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Disables platform notifications of data state and proxy changes. - * Notifications are enabled by default. - * - * @deprecated This method is now obsolete. + * See {@link WebView#disablePlatformNotifications()} */ @Deprecated public static void disablePlatformNotifications() { @@ -2161,11 +2134,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Inform WebView of the network state. This is used to set - * the JavaScript property window.navigator.isOnline and - * generates the online/offline event as specified in HTML5, sec. 5.7.7 - * @param networkUp boolean indicating if network is available + * See {@link WebView#setNetworkAvailable(boolean)} */ + @Override public void setNetworkAvailable(boolean networkUp) { checkThread(); mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE, @@ -2183,19 +2154,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc map.put("subtype", subtype); mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map); } + /** - * Save the state of this WebView used in - * {@link android.app.Activity#onSaveInstanceState}. Please note that this - * method no longer stores the display data for this WebView. The previous - * behavior could potentially leak files if {@link #restoreState} was never - * called. See {@link #savePicture} and {@link #restorePicture} for saving - * and restoring the display data. - * @param outState The Bundle to store the WebView state. - * @return The same copy of the back/forward list used to save the state. If - * saveState fails, the returned list will be null. - * @see #savePicture - * @see #restorePicture + * See {@link WebView#saveState(Bundle)} */ + @Override public WebBackForwardList saveState(Bundle outState) { checkThread(); if (outState == null) { @@ -2244,14 +2207,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Save the current display data to the Bundle given. Used in conjunction - * with {@link #saveState}. - * @param b A Bundle to store the display data. - * @param dest The file to store the serialized picture data. Will be - * overwritten with this WebView's picture data. - * @return True if the picture was successfully saved. - * @deprecated This method is now obsolete. + * See {@link WebView#savePicture(Bundle, File)} */ + @Override @Deprecated public boolean savePicture(Bundle b, final File dest) { checkThread(); @@ -2311,15 +2269,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Restore the display data that was save in {@link #savePicture}. Used in - * conjunction with {@link #restoreState}. - * - * Note that this will not work if the WebView is hardware accelerated. - * @param b A Bundle containing the saved display data. - * @param src The file where the picture data was stored. - * @return True if the picture was successfully restored. - * @deprecated This method is now obsolete. + * See {@link WebView#restorePicture(Bundle, File)}; */ + @Override @Deprecated public boolean restorePicture(Bundle b, File src) { checkThread(); @@ -2402,7 +2354,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Clears the view state set with {@link #loadViewState(InputStream)}. * This WebView will then switch to showing the content from webkit - * @hide */ public void clearViewState() { mBlockWebkitViewMessages = false; @@ -2411,19 +2362,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Restore the state of this WebView from the given map used in - * {@link android.app.Activity#onRestoreInstanceState}. This method should - * be called to restore the state of the WebView before using the object. If - * it is called after the WebView has had a chance to build state (load - * pages, create a back/forward list, etc.) there may be undesirable - * side-effects. Please note that this method no longer restores the - * display data for this WebView. See {@link #savePicture} and {@link - * #restorePicture} for saving and restoring the display data. - * @param inState The incoming Bundle of state. - * @return The restored back/forward list or null if restoreState failed. - * @see #savePicture - * @see #restorePicture + * See {@link WebView#restoreState(Bundle)} */ + @Override public WebBackForwardList restoreState(Bundle inState) { checkThread(); WebBackForwardList returnList = null; @@ -2478,15 +2419,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Load the given URL with the specified additional HTTP headers. - * @param url The URL of the resource to load. - * @param additionalHttpHeaders The additional headers to be used in the - * HTTP request for this URL, specified as a map from name to - * value. Note that if this map contains any of the headers - * that are set by default by the WebView, such as those - * controlling caching, accept types or the User-Agent, their - * values may be overriden by the WebView's defaults. + * See {@link WebView#loadUrl(String, Map)} */ + @Override public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { checkThread(); loadUrlImpl(url, additionalHttpHeaders); @@ -2502,9 +2437,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Load the given URL. - * @param url The URL of the resource to load. + * See {@link WebView#loadUrl(String)} */ + @Override public void loadUrl(String url) { checkThread(); loadUrlImpl(url); @@ -2518,13 +2453,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Load the url with postData using "POST" method into the WebView. If url - * is not a network url, it will be loaded with {link - * {@link #loadUrl(String)} instead. - * - * @param url The url of the resource to load. - * @param postData The data will be passed to "POST" request. + * See {@link WebView#postUrl(String, byte[])} */ + @Override public void postUrl(String url, byte[] postData) { checkThread(); if (URLUtil.isNetworkUrl(url)) { @@ -2540,31 +2471,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Load the given data into the WebView using a 'data' scheme URL. - * <p> - * Note that JavaScript's same origin policy means that script running in a - * page loaded using this method will be unable to access content loaded - * using any scheme other than 'data', including 'http(s)'. To avoid this - * restriction, use {@link - * #loadDataWithBaseURL(String,String,String,String,String) - * loadDataWithBaseURL()} with an appropriate base URL. - * <p> - * If the value of the encoding parameter is 'base64', then the data must - * be encoded as base64. Otherwise, the data must use ASCII encoding for - * octets inside the range of safe URL characters and use the standard %xx - * hex encoding of URLs for octets outside that range. For example, - * '#', '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively. - * <p> - * The 'data' scheme URL formed by this method uses the default US-ASCII - * charset. If you need need to set a different charset, you should form a - * 'data' scheme URL which explicitly specifies a charset parameter in the - * mediatype portion of the URL and call {@link #loadUrl(String)} instead. - * Note that the charset obtained from the mediatype portion of a data URL - * always overrides that specified in the HTML or XML document itself. - * @param data A String of data in the given encoding. - * @param mimeType The MIME type of the data, e.g. 'text/html'. - * @param encoding The encoding of the data. + * See {@link WebView#loadData(String, String, String)} */ + @Override public void loadData(String data, String mimeType, String encoding) { checkThread(); loadDataImpl(data, mimeType, encoding); @@ -2582,27 +2491,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Load the given data into the WebView, using baseUrl as the base URL for - * the content. The base URL is used both to resolve relative URLs and when - * applying JavaScript's same origin policy. The historyUrl is used for the - * history entry. - * <p> - * Note that content specified in this way can access local device files - * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than - * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'. - * <p> - * If the base URL uses the data scheme, this method is equivalent to - * calling {@link #loadData(String,String,String) loadData()} and the - * historyUrl is ignored. - * @param baseUrl URL to use as the page's base URL. If null defaults to - * 'about:blank' - * @param data A String of data in the given encoding. - * @param mimeType The MIMEType of the data, e.g. 'text/html'. If null, - * defaults to 'text/html'. - * @param encoding The encoding of the data. - * @param historyUrl URL to use as the history entry, if null defaults to - * 'about:blank'. + * See {@link WebView#loadDataWithBaseURL(String, String, String, String, String)} */ + @Override public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) { checkThread(); @@ -2623,10 +2514,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Saves the current view as a web archive. - * - * @param filename The filename where the archive should be placed. + * See {@link WebView#saveWebArchive(String)} */ + @Override public void saveWebArchive(String filename) { checkThread(); saveWebArchiveImpl(filename, false, null); @@ -2646,17 +2536,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Saves the current view as a web archive. - * - * @param basename The filename where the archive should be placed. - * @param autoname If false, takes basename to be a file. If true, basename - * is assumed to be a directory in which a filename will be - * chosen according to the url of the current page. - * @param callback Called after the web archive has been saved. The - * parameter for onReceiveValue will either be the filename - * under which the file was saved, or null if saving the - * file failed. + * See {@link WebView#saveWebArchive(String, boolean, ValueCallback)} */ + @Override public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) { checkThread(); saveWebArchiveImpl(basename, autoname, callback); @@ -2669,8 +2551,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Stop the current load. + * See {@link WebView#stopLoading()} */ + @Override public void stopLoading() { checkThread(); // TODO: should we clear all the messages in the queue before sending @@ -2680,8 +2563,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Reload the current url. + * See {@link WebView#reload()} */ + @Override public void reload() { checkThread(); clearHelpers(); @@ -2690,9 +2574,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Return true if this WebView has a back history item. - * @return True iff this WebView has a back history item. + * See {@link WebView#canGoBack()} */ + @Override public boolean canGoBack() { checkThread(); WebBackForwardList l = mCallbackProxy.getBackForwardList(); @@ -2706,17 +2590,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Go back in the history of this WebView. + * See {@link WebView#goBack()} */ + @Override public void goBack() { checkThread(); goBackOrForwardImpl(-1); } /** - * Return true if this WebView has a forward history item. - * @return True iff this Webview has a forward history item. + * See {@link WebView#canGoForward()} */ + @Override public boolean canGoForward() { checkThread(); WebBackForwardList l = mCallbackProxy.getBackForwardList(); @@ -2730,19 +2615,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Go forward in the history of this WebView. + * See {@link WebView#goForward()} */ + @Override public void goForward() { checkThread(); goBackOrForwardImpl(1); } /** - * Return true if the page can go back or forward the given - * number of steps. - * @param steps The negative or positive number of steps to move the - * history. + * See {@link WebView#canGoBackOrForward(int)} */ + @Override public boolean canGoBackOrForward(int steps) { checkThread(); WebBackForwardList l = mCallbackProxy.getBackForwardList(); @@ -2757,12 +2641,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Go to the history item that is the number of steps away from - * the current item. Steps is negative if backward and positive - * if forward. - * @param steps The number of steps to take back or forward in the back - * forward list. + * See {@link WebView#goBackOrForward(int)} */ + @Override public void goBackOrForward(int steps) { checkThread(); goBackOrForwardImpl(steps); @@ -2781,8 +2662,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Returns true if private browsing is enabled in this WebView. + * See {@link WebView#isPrivateBrowsingEnabled()} */ + @Override public boolean isPrivateBrowsingEnabled() { checkThread(); return getSettings().isPrivateBrowsingEnabled(); @@ -2802,10 +2684,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Scroll the contents of the view up by half the view size - * @param top true to jump to the top of the page - * @return true if the page was scrolled + * See {@link WebView#pageUp(boolean)} */ + @Override public boolean pageUp(boolean top) { checkThread(); if (mNativeClass == 0) { @@ -2828,10 +2709,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Scroll the contents of the view down by half the page size - * @param bottom true to jump to bottom of page - * @return true if the page was scrolled + * See {@link WebView#pageDown(boolean)} */ + @Override public boolean pageDown(boolean bottom) { checkThread(); if (mNativeClass == 0) { @@ -2853,9 +2733,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Clear the view so that onDraw() will draw nothing but white background, - * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY + * See {@link WebView#clearView()} */ + @Override public void clearView() { checkThread(); mContentWidth = 0; @@ -2865,14 +2745,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Return a new picture that captures the current display of the webview. - * This is a copy of the display, and will be unaffected if the webview - * later loads a different URL. - * - * @return a picture containing the current contents of the view. Note this - * picture is of the entire document, and is not restricted to the - * bounds of the view. + * See {@link WebView#capturePicture()} */ + @Override public Picture capturePicture() { checkThread(); if (mNativeClass == 0) return null; @@ -2882,9 +2757,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Return the current scale of the WebView - * @return The current scale. + * See {@link WebView#getScale()} */ + @Override public float getScale() { checkThread(); return mZoomManager.getScale(); @@ -2900,25 +2775,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Set the initial scale for the WebView. 0 means default. If - * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the - * way. Otherwise it starts with 100%. If initial scale is greater than 0, - * WebView starts with this value as initial scale. - * Please note that unlike the scale properties in the viewport meta tag, - * this method doesn't take the screen density into account. - * - * @param scaleInPercent The initial scale in percent. + * See {@link WebView#setInitialScale(int)} */ + @Override public void setInitialScale(int scaleInPercent) { checkThread(); mZoomManager.setInitialScaleInPercent(scaleInPercent); } /** - * Invoke the graphical zoom picker widget for this WebView. This will - * result in the zoom widget appearing on the screen to control the zoom - * level of this WebView. + * See {@link WebView#invokeZoomPicker()} */ + @Override public void invokeZoomPicker() { checkThread(); if (!getSettings().supportZoom()) { @@ -2930,23 +2798,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Return a HitTestResult based on the current cursor node. If a HTML::a tag - * is found and the anchor has a non-JavaScript url, the HitTestResult type - * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the - * anchor does not have a url or if it is a JavaScript url, the type will - * be UNKNOWN_TYPE and the url has to be retrieved through - * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is - * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in - * the "extra" field. A type of - * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as - * a child node. If a phone number is found, the HitTestResult type is set - * to PHONE_TYPE and the phone number is set in the "extra" field of - * HitTestResult. If a map address is found, the HitTestResult type is set - * to GEO_TYPE and the address is set in the "extra" field of HitTestResult. - * If an email address is found, the HitTestResult type is set to EMAIL_TYPE - * and the email is set in the "extra" field of HitTestResult. Otherwise, - * HitTestResult type is set to UNKNOWN_TYPE. + * See {@link WebView#getHitTestResult()} */ + @Override public HitTestResult getHitTestResult() { checkThread(); return mInitialHitTestResult; @@ -2980,19 +2834,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Request the anchor or image element URL at the last tapped point. - * If hrefMsg is null, this method returns immediately and does not - * dispatch hrefMsg to its target. If the tapped point hits an image, - * an anchor, or an image in an anchor, the message associates - * strings in named keys in its data. The value paired with the key - * may be an empty string. - * - * @param hrefMsg This message will be dispatched with the result of the - * request. The message data contains three keys: - * - "url" returns the anchor's href attribute. - * - "title" returns the anchor's text. - * - "src" returns the image's src attribute. + * See {@link WebView#requestFocusNodeHref(Message)} */ + @Override public void requestFocusNodeHref(Message hrefMsg) { checkThread(); if (hrefMsg == null) { @@ -3013,12 +2857,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Request the url of the image last touched by the user. msg will be sent - * to its target with a String representing the url as its object. - * - * @param msg This message will be dispatched with the result of the request - * as the data member with "url" as key. The result can be null. + * See {@link WebView#requestImageRef(Message)} */ + @Override public void requestImageRef(Message msg) { checkThread(); if (0 == mNativeClass) return; // client isn't initialized @@ -3521,11 +3362,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Get the url for the current page. This is not always the same as the url - * passed to WebViewClient.onPageStarted because although the load for - * that url has begun, the current page may not have changed. - * @return The url for the current page. + * See {@link WebView#getUrl()} */ + @Override public String getUrl() { checkThread(); WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); @@ -3533,13 +3372,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Get the original url for the current page. This is not always the same - * as the url passed to WebViewClient.onPageStarted because although the - * load for that url has begun, the current page may not have changed. - * Also, there may have been redirects resulting in a different url to that - * originally requested. - * @return The url that was originally requested for the current page. + * See {@link WebView#getOriginalUrl()} */ + @Override public String getOriginalUrl() { checkThread(); WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); @@ -3547,10 +3382,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Get the title for the current page. This is the title of the current page - * until WebViewClient.onReceivedTitle is called. - * @return The title for the current page. + * See {@link WebView#getTitle()} */ + @Override public String getTitle() { checkThread(); WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); @@ -3558,10 +3392,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Get the favicon for the current page. This is the favicon of the current - * page until WebViewClient.onReceivedIcon is called. - * @return The favicon for the current page. + * See {@link WebView#getFavicon()} */ + @Override public Bitmap getFavicon() { checkThread(); WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); @@ -3569,37 +3402,36 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Get the touch icon url for the apple-touch-icon <link> element, or - * a URL on this site's server pointing to the standard location of a - * touch icon. - * @hide + * See {@link WebView#getTouchIconUrl()} */ + @Override public String getTouchIconUrl() { WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); return h != null ? h.getTouchIconUrl() : null; } /** - * Get the progress for the current page. - * @return The progress for the current page between 0 and 100. + * See {@link WebView#getProgress()} */ + @Override public int getProgress() { checkThread(); return mCallbackProxy.getProgress(); } /** - * @return the height of the HTML content. + * See {@link WebView#getContentHeight()} */ + @Override public int getContentHeight() { checkThread(); return mContentHeight; } /** - * @return the width of the HTML content. - * @hide + * See {@link WebView#getContentWidth()} */ + @Override public int getContentWidth() { return mContentWidth; } @@ -3612,32 +3444,27 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Pause all layout, parsing, and JavaScript timers for all webviews. This - * is a global requests, not restricted to just this webview. This can be - * useful if the application has been paused. + * See {@link WebView#pauseTimers()} */ + @Override public void pauseTimers() { checkThread(); mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); } /** - * Resume all layout, parsing, and JavaScript timers for all webviews. - * This will resume dispatching all timers. + * See {@link WebView#resumeTimers()} */ + @Override public void resumeTimers() { checkThread(); mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); } /** - * Call this to pause any extra processing associated with this WebView and - * its associated DOM, plugins, JavaScript etc. For example, if the WebView - * is taken offscreen, this could be called to reduce unnecessary CPU or - * network traffic. When the WebView is again "active", call onResume(). - * - * Note that this differs from pauseTimers(), which affects all WebViews. + * See {@link WebView#onPause()} */ + @Override public void onPause() { checkThread(); if (!mIsPaused) { @@ -3674,8 +3501,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Call this to resume a WebView after a previous call to onPause(). + * See {@link WebView#onResume()} */ + @Override public void onResume() { checkThread(); if (mIsPaused) { @@ -3697,29 +3525,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Returns true if the view is paused, meaning onPause() was called. Calling - * onResume() sets the paused state back to false. - * @hide + * See {@link WebView#isPaused()} */ + @Override public boolean isPaused() { return mIsPaused; } /** - * Call this to inform the view that memory is low so that it can - * free any available memory. + * See {@link WebView#freeMemory()} */ + @Override public void freeMemory() { checkThread(); mWebViewCore.sendMessage(EventHub.FREE_MEMORY); } /** - * Clear the resource cache. Note that the cache is per-application, so - * this will clear the cache for all WebViews used. - * - * @param includeDiskFiles If false, only the RAM cache is cleared. + * See {@link WebView#clearCache(boolean)} */ + @Override public void clearCache(boolean includeDiskFiles) { checkThread(); // Note: this really needs to be a static method as it clears cache for all @@ -3730,17 +3555,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Make sure that clearing the form data removes the adapter from the - * currently focused textfield if there is one. + * See {@link WebView#clearFormData()} */ + @Override public void clearFormData() { checkThread(); // TODO: Implement b/6083041 } /** - * Tell the WebView to clear its internal back/forward list. + * See {@link WebView#clearHistory()} */ + @Override public void clearHistory() { checkThread(); mCallbackProxy.getBackForwardList().setClearPending(); @@ -3748,46 +3574,37 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Clear the SSL preferences table stored in response to proceeding with SSL - * certificate errors. + * See {@link WebView#clearSslPreferences()} */ + @Override public void clearSslPreferences() { checkThread(); mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE); } /** - * Return the WebBackForwardList for this WebView. This contains the - * back/forward list for use in querying each item in the history stack. - * This is a copy of the private WebBackForwardList so it contains only a - * snapshot of the current state. Multiple calls to this method may return - * different objects. The object returned from this method will not be - * updated to reflect any new state. + * See {@link WebView#copyBackForwardList()} */ + @Override public WebBackForwardList copyBackForwardList() { checkThread(); return mCallbackProxy.getBackForwardList().clone(); } - /* - * Highlight and scroll to the next occurance of String in findAll. - * Wraps the page infinitely, and scrolls. Must be called after - * calling findAll. - * - * @param forward Direction to search. + /** + * See {@link WebView#findNext(boolean)} */ + @Override public void findNext(boolean forward) { checkThread(); if (0 == mNativeClass) return; // client isn't initialized mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0); } - /* - * Find all instances of find on the page and highlight them. - * @param find String to find. - * @return int The number of occurances of the String "find" - * that were found. + /** + * See {@link WebView#findAll(String)} */ + @Override public int findAll(String find) { return findAllBody(find, false); } @@ -3935,9 +3752,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return WebViewCore.nativeFindAddress(addr, caseInsensitive); } - /* - * Clear the highlighting surrounding text matches created by findAll. + /** + * See {@link WebView#clearMatches()} */ + @Override public void clearMatches() { checkThread(); if (mNativeClass == 0) @@ -3965,11 +3783,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Query the document to see if it contains any image references. The - * message object will be dispatched with arg1 being set to 1 if images - * were found and 0 if the document does not reference any images. - * @param response The message that will be dispatched with the result. + * See {@link WebView#documentHasImages(Message)} */ + @Override public void documentHasImages(Message response) { checkThread(); if (response == null) { @@ -4081,6 +3897,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // helper to pin the scrollTo parameters (already in view coordinates) // returns true if the scroll was changed private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) { + abortAnimation(); x = pinLocX(x); y = pinLocY(y); int dx = x - getScrollX(); @@ -4089,7 +3906,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if ((dx | dy) == 0) { return false; } - abortAnimation(); if (animate) { // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, @@ -4361,9 +4177,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // is used in the view system. return; } - int vx = contentToViewX(cx); - int vy = contentToViewY(cy); - pinScrollTo(vx, vy, true, 0); + int vx = contentToViewDimension(cx - mScrollOffset.x); + int vy = contentToViewDimension(cy - mScrollOffset.y); + pinScrollBy(vx, vy, true, 0); } /** @@ -4396,10 +4212,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Set the WebViewClient that will receive various notifications and - * requests. This will replace the current handler. - * @param client An implementation of WebViewClient. + * See {@link WebView#setWebViewClient(WebViewClient)} */ + @Override public void setWebViewClient(WebViewClient client) { checkThread(); mCallbackProxy.setWebViewClient(client); @@ -4416,22 +4231,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Register the interface to be used when content can not be handled by - * the rendering engine, and should be downloaded instead. This will replace - * the current handler. - * @param listener An implementation of DownloadListener. + * See {@link WebView#setDownloadListener(DownloadListener)} */ + @Override public void setDownloadListener(DownloadListener listener) { checkThread(); mCallbackProxy.setDownloadListener(listener); } /** - * Set the chrome handler. This is an implementation of WebChromeClient for - * use in handling JavaScript dialogs, favicons, titles, and the progress. - * This will replace the current handler. - * @param client An implementation of WebChromeClient. + * See {@link WebView#setWebChromeClient(WebChromeClient)} */ + @Override public void setWebChromeClient(WebChromeClient client) { checkThread(); mCallbackProxy.setWebChromeClient(client); @@ -4467,11 +4278,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Set the Picture listener. This is an interface used to receive - * notifications of a new Picture. - * @param listener An implementation of WebView.PictureListener. - * @deprecated This method is now obsolete. + * See {@link WebView#setPictureListener(PictureListener)} */ + @Override @Deprecated public void setPictureListener(PictureListener listener) { checkThread(); @@ -4495,31 +4304,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * This method injects the supplied Java object into the WebView. The - * object is injected into the JavaScript context of the main frame, using - * the supplied name. This allows the Java object to be accessed from - * JavaScript. Note that that injected objects will not appear in - * JavaScript until the page is next (re)loaded. For example: - * <pre> webView.addJavascriptInterface(new Object(), "injectedObject"); - * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null); - * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre> - * <p><strong>IMPORTANT:</strong> - * <ul> - * <li> addJavascriptInterface() can be used to allow JavaScript to control - * the host application. This is a powerful feature, but also presents a - * security risk. Use of this method in a WebView containing untrusted - * content could allow an attacker to manipulate the host application in - * unintended ways, executing Java code with the permissions of the host - * application. Use extreme care when using this method in a WebView which - * could contain untrusted content. - * <li> JavaScript interacts with Java object on a private, background - * thread of the WebView. Care is therefore required to maintain thread - * safety.</li> - * </ul></p> - * @param object The Java object to inject into the WebView's JavaScript - * context. Null values are ignored. - * @param name The name used to expose the instance in JavaScript. + * See {@link WebView#addJavascriptInterface(Object, String)} */ + @Override public void addJavascriptInterface(Object object, String name) { checkThread(); if (object == null) { @@ -4532,9 +4319,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Removes a previously added JavaScript interface with the given name. - * @param interfaceName The name of the interface to remove. + * See {@link WebView#removeJavascriptInterface(String)} */ + @Override public void removeJavascriptInterface(String interfaceName) { checkThread(); if (mWebViewCore != null) { @@ -4545,33 +4332,28 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Return the WebSettings object used to control the settings for this - * WebView. - * @return A WebSettings object that can be used to control this WebView's - * settings. + * See {@link WebView#getSettings()} + * Note this returns WebSettingsClassic, a sub-class of WebSettings, which can be used + * to access extension APIs. */ + @Override public WebSettingsClassic getSettings() { checkThread(); return (mWebViewCore != null) ? mWebViewCore.getSettings() : null; } - /** - * Return the list of currently loaded plugins. - * @return The list of currently loaded plugins. - * - * @hide - * @deprecated This was used for Gears, which has been deprecated. - */ + /** + * See {@link WebView#getPluginList()} + */ @Deprecated public static synchronized PluginList getPluginList() { checkThread(); return new PluginList(); } - /** - * @hide - * @deprecated This was used for Gears, which has been deprecated. - */ + /** + * See {@link WebView#refreshPlugins(boolean)} + */ @Deprecated public void refreshPlugins(boolean reloadOpenPages) { checkThread(); @@ -4925,9 +4707,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (layer == 0 || isPictureAfterFirstLayout) { mWebViewCore.resumeWebKitDraw(); } else if (queueFull) { - // temporarily disable webkit draw throttling - // TODO: re-enable - // mWebViewCore.pauseWebKitDraw(); + mWebViewCore.pauseWebKitDraw(); } if (mHTML5VideoViewProxy != null) { @@ -5309,8 +5089,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (keyCode >= KeyEvent.KEYCODE_DPAD_UP && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { switchOutDrawHistory(); - letPageHandleNavKey(keyCode, event.getEventTime(), true, event.getMetaState()); - return true; } if (isEnterActionKey(keyCode)) { @@ -5342,7 +5120,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } // pass the key to DOM - mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); + sendKeyEvent(event); // return true as DOM handles the key return true; } @@ -5405,12 +5183,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } - if (keyCode >= KeyEvent.KEYCODE_DPAD_UP - && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { - letPageHandleNavKey(keyCode, event.getEventTime(), false, event.getMetaState()); - return true; - } - if (isEnterActionKey(keyCode)) { // remove the long press message first mPrivateHandler.removeMessages(LONG_PRESS_CENTER); @@ -5424,7 +5196,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } // pass the key to DOM - mWebViewCore.sendMessage(EventHub.KEY_UP, event); + sendKeyEvent(event); // return true as DOM handles the key return true; } @@ -5511,10 +5283,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Use this method to put the WebView into text selection mode. - * Do not rely on this functionality; it will be deprecated in the future. - * @deprecated This method is now obsolete. + * See {@link WebView#emulateShiftHeld()} */ + @Override @Deprecated public void emulateShiftHeld() { checkThread(); @@ -5757,6 +5528,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc setFocusControllerActive(false); mKeysPressed.clear(); } + if (!mTouchHighlightRegion.isEmpty()) { + mWebView.invalidate(mTouchHighlightRegion.getBounds()); + } } void setGLRectViewport() { @@ -5968,6 +5742,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return false; } + if (!mWebView.isFocused()) { + mWebView.requestFocus(); + } + if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" + mTouchMode @@ -6357,7 +6135,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc break; } case MotionEvent.ACTION_UP: { - if (!mWebView.isFocused()) mWebView.requestFocus(); // pass the touch events from UI thread to WebCore thread if (shouldForwardTouchEvent()) { TouchEventData ted = new TouchEventData(); @@ -6956,9 +6733,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc case KeyEvent.KEYCODE_DPAD_LEFT: return SoundEffectConstants.NAVIGATION_LEFT; } - throw new IllegalArgumentException("keyCode must be one of " + - "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " + - "KEYCODE_DPAD_LEFT}."); + return 0; } private void doTrackball(long time, int metaState) { @@ -7181,18 +6956,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Returns a view containing zoom controls i.e. +/- buttons. The caller is - * in charge of installing this view to the view hierarchy. This view will - * become visible when the user starts scrolling via touch and fade away if - * the user does not interact with it. - * <p/> - * API version 3 introduces a built-in zoom mechanism that is shown - * automatically by the MapView. This is the preferred approach for - * showing the zoom UI. - * - * @deprecated The built-in zoom mechanism is preferred, see - * {@link WebSettings#setBuiltInZoomControls(boolean)}. + * See {@link WebView#getZoomControls()} */ + @Override @Deprecated public View getZoomControls() { checkThread(); @@ -7220,34 +6986,36 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * @return TRUE if the WebView can be zoomed in. + * See {@link WebView#canZoomIn()} */ + @Override public boolean canZoomIn() { checkThread(); return mZoomManager.canZoomIn(); } /** - * @return TRUE if the WebView can be zoomed out. + * See {@link WebView#canZoomOut()} */ + @Override public boolean canZoomOut() { checkThread(); return mZoomManager.canZoomOut(); } /** - * Perform zoom in in the webview - * @return TRUE if zoom in succeeds. FALSE if no zoom changes. + * See {@link WebView#zoomIn()} */ + @Override public boolean zoomIn() { checkThread(); return mZoomManager.zoomIn(); } /** - * Perform zoom out in the webview - * @return TRUE if zoom out succeeds. FALSE if no zoom changes. + * See {@link WebView#zoomOut()} */ + @Override public boolean zoomOut() { checkThread(); return mZoomManager.zoomOut(); @@ -7471,7 +7239,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc viewToContentX(getScrollX() + getWidth() - mWebView.getVerticalScrollbarWidth()), viewToContentY(getScrollY() + getViewHeightWithTitle())); - content = nativeSubtractLayers(content); int screenTop = contentToViewY(content.top); int screenBottom = contentToViewY(content.bottom); int height = screenBottom - screenTop; @@ -8232,8 +7999,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc case FORM_DID_BLUR: // TODO: Figure out if this is needed for something (b/6111763) break; - case UNHANDLED_NAV_KEY: - // TODO: Support this (b/6109044) + case TAKE_FOCUS: + int direction = msg.arg1; + View focusSearch = mWebView.focusSearch(direction); + if (focusSearch != null && focusSearch != mWebView) { + focusSearch.requestFocus(); + } break; case CLEAR_TEXT_ENTRY: hideSoftKeyboard(); @@ -8527,9 +8298,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return false; } if (mFocusedNode.mHasFocus && !mWebView.isInTouchMode()) { - return !mFocusedNode.mEditable; + return mDrawCursorRing && !mFocusedNode.mEditable; } - if (mInitialHitTestResult.getType() == HitTestResult.UNKNOWN_TYPE) { + if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) { return false; } long delay = System.currentTimeMillis() - mTouchHighlightRequested; @@ -9129,14 +8900,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ private void letPageHandleNavKey(int keyCode, long time, boolean down, int metaState) { int keyEventAction; - int eventHubAction; if (down) { keyEventAction = KeyEvent.ACTION_DOWN; - eventHubAction = EventHub.KEY_DOWN; - mWebView.playSoundEffect(keyCodeToSoundsEffect(keyCode)); } else { keyEventAction = KeyEvent.ACTION_UP; - eventHubAction = EventHub.KEY_UP; } KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode, @@ -9144,7 +8911,41 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc | (metaState & KeyEvent.META_ALT_ON) | (metaState & KeyEvent.META_SYM_ON) , KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0); - mWebViewCore.sendMessage(eventHubAction, event); + sendKeyEvent(event); + } + + private void sendKeyEvent(KeyEvent event) { + int direction = 0; + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_DOWN: + direction = View.FOCUS_DOWN; + break; + case KeyEvent.KEYCODE_DPAD_UP: + direction = View.FOCUS_UP; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + direction = View.FOCUS_LEFT; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + direction = View.FOCUS_RIGHT; + break; + case KeyEvent.KEYCODE_TAB: + direction = event.isShiftPressed() ? View.FOCUS_BACKWARD : View.FOCUS_FORWARD; + break; + } + if (direction != 0 && mWebView.focusSearch(direction) == null) { + // Can't take focus in that direction + direction = 0; + } + int eventHubAction = EventHub.KEY_UP; + if (event.getAction() == KeyEvent.ACTION_DOWN) { + eventHubAction = EventHub.KEY_DOWN; + int sound = keyCodeToSoundsEffect(event.getKeyCode()); + if (sound != 0) { + mWebView.playSoundEffect(sound); + } + } + mWebViewCore.sendMessage(eventHubAction, direction, event); } /** @@ -9158,9 +8959,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * Set the background color. It's white by default. Pass - * zero to make the view transparent. - * @param color the ARGB color described by Color.java + * See {@link WebView#setBackgroundColor(int)} */ @Override public void setBackgroundColor(int color) { @@ -9169,8 +8968,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * @deprecated This method is now obsolete. + * See {@link WebView#debugDump()} */ + @Override @Deprecated public void debugDump() { } @@ -9334,7 +9134,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private native void nativeCopyBaseContentToPicture(Picture pict); private native boolean nativeHasContent(); private native void nativeStopGL(); - private native Rect nativeSubtractLayers(Rect content); private native void nativeDiscardAllTextures(); private native void nativeTileProfilingStart(); private native float nativeTileProfilingStop(); diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 81de356..0c34037 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -204,7 +204,7 @@ public class WebViewClient { /** * Notify the host application that an SSL error occurred while loading a - * resource, but the WebView but chose to proceed anyway based on a + * resource, but the WebView chose to proceed anyway based on a * decision retained from a previous response to onReceivedSslError(). * @hide */ @@ -220,7 +220,7 @@ public class WebViewClient { * default behavior is to cancel, returning no client certificate. * * @param view The WebView that is initiating the callback. - * @param handler An ClientCertRequestHandler object that will + * @param handler A ClientCertRequestHandler object that will * handle the user's response. * @param host_and_port The host and port of the requesting server. * @@ -266,7 +266,7 @@ public class WebViewClient { * Notify the host application that a key was not handled by the WebView. * Except system keys, WebView always consumes the keys in the normal flow * or if shouldOverrideKeyEvent returns true. This is called asynchronously - * from where the key is dispatched. It gives the host application an chance + * from where the key is dispatched. It gives the host application a chance * to handle the unhandled key events. * * @param view The WebView that is initiating the callback. diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 65356f5..de30755 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -139,6 +139,8 @@ public final class WebViewCore { private int mHighMemoryUsageThresholdMb; private int mHighUsageDeltaMb; + private int mChromeCanFocusDirection; + // The thread name used to identify the WebCore thread and for use in // debugging other classes that require operation within the WebCore thread. /* package */ static final String THREAD_NAME = "WebViewCoreThread"; @@ -344,6 +346,58 @@ public final class WebViewCore { } /** + * Called by JNI to advance focus to the next view. + */ + private void chromeTakeFocus(int webkitDirection) { + if (mWebView == null) return; + Message m = mWebView.mPrivateHandler.obtainMessage( + WebViewClassic.TAKE_FOCUS); + m.arg1 = mapDirection(webkitDirection); + m.sendToTarget(); + } + + /** + * Called by JNI to see if we can take focus in the given direction. + */ + private boolean chromeCanTakeFocus(int webkitDirection) { + int direction = mapDirection(webkitDirection); + return direction == mChromeCanFocusDirection && direction != 0; + } + + /** + * Maps a Webkit focus direction to a framework one + */ + private int mapDirection(int webkitDirection) { + /* + * This is WebKit's FocusDirection enum (from FocusDirection.h) + enum FocusDirection { + FocusDirectionNone = 0, + FocusDirectionForward, + FocusDirectionBackward, + FocusDirectionUp, + FocusDirectionDown, + FocusDirectionLeft, + FocusDirectionRight + }; + */ + switch (webkitDirection) { + case 1: + return View.FOCUS_FORWARD; + case 2: + return View.FOCUS_BACKWARD; + case 3: + return View.FOCUS_UP; + case 4: + return View.FOCUS_DOWN; + case 5: + return View.FOCUS_LEFT; + case 6: + return View.FOCUS_RIGHT; + } + return 0; + } + + /** * Called by JNI. Open a file chooser to upload a file. * @param acceptType The value of the 'accept' attribute of the * input tag associated with this file picker. @@ -1190,6 +1244,23 @@ public final class WebViewCore { + " arg1=" + msg.arg1 + " arg2=" + msg.arg2 + " obj=" + msg.obj); } + switch (msg.what) { + case PAUSE_TIMERS: + mSavedPriority = Process.getThreadPriority(mTid); + Process.setThreadPriority(mTid, + Process.THREAD_PRIORITY_BACKGROUND); + pauseTimers(); + if (mNativeClass != 0) { + nativeCloseIdleConnections(mNativeClass); + } + return; + + case RESUME_TIMERS: + Process.setThreadPriority(mTid, mSavedPriority); + resumeTimers(); + return; + } + if (mWebView == null || mNativeClass == 0) { if (DebugFlags.WEB_VIEW_CORE) { Log.w(LOGTAG, "Rejecting message " + msg.what @@ -1198,8 +1269,6 @@ public final class WebViewCore { return; } if (mDestroying == true - && msg.what != EventHub.RESUME_TIMERS - && msg.what != EventHub.PAUSE_TIMERS && msg.what != EventHub.DESTROY) { if (DebugFlags.WEB_VIEW_CORE) { Log.v(LOGTAG, "Rejecting message " + msg.what @@ -1311,11 +1380,11 @@ public final class WebViewCore { break; case KEY_DOWN: - key((KeyEvent) msg.obj, true); + key((KeyEvent) msg.obj, msg.arg1, true); break; case KEY_UP: - key((KeyEvent) msg.obj, false); + key((KeyEvent) msg.obj, msg.arg1, false); break; case KEY_PRESS: @@ -1365,18 +1434,6 @@ public final class WebViewCore { restoreState(msg.arg1); break; - case PAUSE_TIMERS: - mSavedPriority = Process.getThreadPriority(mTid); - Process.setThreadPriority(mTid, - Process.THREAD_PRIORITY_BACKGROUND); - pauseTimers(); - nativeCloseIdleConnections(mNativeClass); - break; - - case RESUME_TIMERS: - Process.setThreadPriority(mTid, mSavedPriority); - resumeTimers(); - break; case ON_PAUSE: nativePause(mNativeClass); @@ -1907,12 +1964,10 @@ public final class WebViewCore { */ void destroy() { synchronized (mEventHub) { - // Do not call removeMessages as then we risk removing PAUSE_TIMERS - // or RESUME_TIMERS messages, which we must still handle as they - // are per process. DESTROY will instead trigger a white list in - // mEventHub, skipping any remaining messages in the queue + // send DESTROY to front of queue + // PAUSE/RESUME timers will still be processed even if they get handled later mEventHub.mDestroying = true; - mEventHub.sendMessage( + mEventHub.sendMessageAtFrontOfQueue( Message.obtain(null, EventHub.DESTROY)); mEventHub.blockMessages(); } @@ -1950,11 +2005,12 @@ public final class WebViewCore { return mBrowserFrame.saveWebArchive(filename, autoname); } - private void key(KeyEvent evt, boolean isDown) { + private void key(KeyEvent evt, int canTakeFocusDirection, boolean isDown) { if (DebugFlags.WEB_VIEW_CORE) { Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", " + evt); } + mChromeCanFocusDirection = canTakeFocusDirection; int keyCode = evt.getKeyCode(); int unicodeChar = evt.getUnicodeChar(); @@ -1964,18 +2020,18 @@ public final class WebViewCore { unicodeChar = evt.getCharacters().codePointAt(0); } - if (!nativeKey(mNativeClass, keyCode, unicodeChar, evt.getRepeatCount(), + boolean handled = nativeKey(mNativeClass, keyCode, unicodeChar, evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(), - evt.isSymPressed(), isDown) && keyCode != KeyEvent.KEYCODE_ENTER) { + evt.isSymPressed(), isDown); + mChromeCanFocusDirection = 0; + if (!handled && keyCode != KeyEvent.KEYCODE_ENTER) { if (keyCode >= KeyEvent.KEYCODE_DPAD_UP && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { - if (DebugFlags.WEB_VIEW_CORE) { - Log.v(LOGTAG, "key: arrow unused by page: " + keyCode); - } - if (mWebView != null && evt.isDown()) { - Message.obtain(mWebView.mPrivateHandler, - WebViewClassic.UNHANDLED_NAV_KEY, keyCode, - 0).sendToTarget(); + if (canTakeFocusDirection != 0 && isDown) { + Message m = mWebView.mPrivateHandler.obtainMessage( + WebViewClassic.TAKE_FOCUS); + m.arg1 = canTakeFocusDirection; + m.sendToTarget(); } return; } diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 603cea1..233d892 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -161,6 +161,12 @@ public class CheckedTextView extends TextView implements Checkable { } @Override + public void setPaddingRelative(int start, int top, int end, int bottom) { + super.setPaddingRelative(start, top, end, bottom); + mBasePadding = getPaddingEnd(); + } + + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -221,16 +227,6 @@ public class CheckedTextView extends TextView implements Checkable { } @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); - if (isChecked()) { - event.getText().add(mContext.getString(R.string.radiobutton_selected)); - } else { - event.getText().add(mContext.getString(R.string.radiobutton_not_selected)); - } - } - - @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(CheckedTextView.class.getName()); diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index badfaa7..c2d8bda 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -740,7 +740,7 @@ public class ExpandableListView extends ListView { /** * Converts a flat list position (the raw position of an item (child or group) - * in the list) to an group and/or child position (represented in a + * in the list) to a group and/or child position (represented in a * packed position). This is useful in situations where the caller needs to * use the underlying {@link ListView}'s methods. Use * {@link ExpandableListView#getPackedPositionType} , diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 0b4ebf4..0db6ef2 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -1389,7 +1389,7 @@ public class HorizontalScrollView extends FrameLayout { } mChildToScrollTo = null; - // Calling this with the present values causes it to re-clam them + // Calling this with the present values causes it to re-claim them scrollTo(mScrollX, mScrollY); } @@ -1412,7 +1412,7 @@ public class HorizontalScrollView extends FrameLayout { } /** - * Return true if child is an descendant of parent, (or equal to the parent). + * Return true if child is a descendant of parent, (or equal to the parent). */ private boolean isViewDescendantOf(View child, View parent) { if (child == parent) { @@ -1427,7 +1427,7 @@ public class HorizontalScrollView extends FrameLayout { * Fling the scroll view * * @param velocityX The initial velocity in the X direction. Positive - * numbers mean that the finger/curor is moving down the screen, + * numbers mean that the finger/cursor is moving down the screen, * which means we want to scroll towards the left. */ public void fling(int velocityX) { diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 07ae93b..3001ea1 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -193,9 +193,6 @@ public class ImageView extends View { } } - /** - * @hide - */ @Override public int getResolvedLayoutDirection(Drawable dr) { return (dr == mDrawable) ? diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 5c97593..1d966b3 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -1017,7 +1017,7 @@ public class ListPopupWindow { View hintView = mPromptView; if (hintView != null) { - // if an hint has been specified, we accomodate more space for it and + // if a hint has been specified, we accomodate more space for it and // add a text view in the drop down menu, at the bottom of the list LinearLayout hintContainer = new LinearLayout(context); hintContainer.setOrientation(LinearLayout.VERTICAL); @@ -1080,6 +1080,8 @@ public class ListPopupWindow { if (!mDropDownVerticalOffsetSet) { mDropDownVerticalOffset = -mTempRect.top; } + } else { + mTempRect.setEmpty(); } // Max height available on the screen for a popup. @@ -1092,7 +1094,25 @@ public class ListPopupWindow { return maxHeight + padding; } - final int listContent = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED, + final int childWidthSpec; + switch (mDropDownWidth) { + case ViewGroup.LayoutParams.WRAP_CONTENT: + childWidthSpec = MeasureSpec.makeMeasureSpec( + mContext.getResources().getDisplayMetrics().widthPixels - + (mTempRect.left + mTempRect.right), + MeasureSpec.AT_MOST); + break; + case ViewGroup.LayoutParams.MATCH_PARENT: + childWidthSpec = MeasureSpec.makeMeasureSpec( + mContext.getResources().getDisplayMetrics().widthPixels - + (mTempRect.left + mTempRect.right), + MeasureSpec.EXACTLY); + break; + default: + childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY); + break; + } + final int listContent = mDropDownList.measureHeightOfChildren(childWidthSpec, 0, ListView.NO_POSITION, maxHeight - otherHeights, -1); // add padding only if the list has items in it, that way we don't show // the popup if it is not needed diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index e298acb..3bc4f7f 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -910,9 +910,6 @@ public class ProgressBar extends View { } } - /** - * @hide - */ @Override public int getResolvedLayoutDirection(Drawable who) { return (who == mProgressDrawable || who == mIndeterminateDrawable) ? diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java index b6dac3e..b1bb1c0 100644 --- a/core/java/android/widget/RadioButton.java +++ b/core/java/android/widget/RadioButton.java @@ -78,16 +78,6 @@ public class RadioButton extends CompoundButton { } @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); - if (isChecked()) { - event.getText().add(mContext.getString(R.string.radiobutton_selected)); - } else { - event.getText().add(mContext.getString(R.string.radiobutton_not_selected)); - } - } - - @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(RadioButton.class.getName()); diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 3ffc0fe..25dd438 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -1412,7 +1412,7 @@ public class ScrollView extends FrameLayout { } mChildToScrollTo = null; - // Calling this with the present values causes it to re-clam them + // Calling this with the present values causes it to re-claim them scrollTo(mScrollX, mScrollY); } @@ -1436,7 +1436,7 @@ public class ScrollView extends FrameLayout { } /** - * Return true if child is an descendant of parent, (or equal to the parent). + * Return true if child is a descendant of parent, (or equal to the parent). */ private boolean isViewDescendantOf(View child, View parent) { if (child == parent) { diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java index 4b17a92..98bcfff 100644 --- a/core/java/android/widget/SimpleAdapter.java +++ b/core/java/android/widget/SimpleAdapter.java @@ -268,7 +268,7 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { /** * Called by bindView() to set the text for a TextView but only if * there is no existing ViewBinder or if the existing ViewBinder cannot - * handle binding to an TextView. + * handle binding to a TextView. * * @param v TextView to receive text * @param text the text to be set for the TextView diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java index c5c6c69..f74a314 100644 --- a/core/java/android/widget/SimpleCursorAdapter.java +++ b/core/java/android/widget/SimpleCursorAdapter.java @@ -216,7 +216,7 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { /** * Called by bindView() to set the text for a TextView but only if * there is no existing ViewBinder or if the existing ViewBinder cannot - * handle binding to an TextView. + * handle binding to a TextView. * * Intended to be overridden by Adapters that need to filter strings * retrieved from the database. diff --git a/core/java/android/widget/SimpleCursorTreeAdapter.java b/core/java/android/widget/SimpleCursorTreeAdapter.java index a033542..6babf3e 100644 --- a/core/java/android/widget/SimpleCursorTreeAdapter.java +++ b/core/java/android/widget/SimpleCursorTreeAdapter.java @@ -283,7 +283,7 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter /** * Called by bindView() to set the text for a TextView but only if * there is no existing ViewBinder or if the existing ViewBinder cannot - * handle binding to an TextView. + * handle binding to a TextView. * * Intended to be overridden by Adapters that need to filter strings * retrieved from the database. diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index df2996c..9afaee3 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -290,7 +290,7 @@ public class SpellChecker implements SpellCheckerSessionListener { private SpellCheckSpan onGetSuggestionsInternal( SuggestionsInfo suggestionsInfo, int offset, int length) { - if (suggestionsInfo.getCookie() != mCookie) { + if (suggestionsInfo == null || suggestionsInfo.getCookie() != mCookie) { return null; } final Editable editable = (Editable) mTextView.getText(); @@ -335,9 +335,15 @@ public class SpellChecker implements SpellCheckerSessionListener { for (int i = 0; i < results.length; ++i) { final SentenceSuggestionsInfo ssi = results[i]; + if (ssi == null) { + continue; + } SpellCheckSpan spellCheckSpan = null; for (int j = 0; j < ssi.getSuggestionsCount(); ++j) { final SuggestionsInfo suggestionsInfo = ssi.getSuggestionsInfoAt(j); + if (suggestionsInfo == null) { + continue; + } final int offset = ssi.getOffsetAt(j); final int length = ssi.getLengthAt(j); final SpellCheckSpan scs = onGetSuggestionsInternal( @@ -488,11 +494,15 @@ public class SpellChecker implements SpellCheckerSessionListener { editable.removeSpan(mRange); return; } + // Stop spell checking when there are no characters in the range. + if (wordEnd < start) { + return; + } wordStart = regionEnd; // TODO: Find the start position of the sentence. // Set span with the context - final int spellCheckStart = Math.min( - start, Math.max(wordStart, regionEnd - WORD_ITERATOR_INTERVAL)); + final int spellCheckStart = Math.max( + 0, Math.min(wordStart, regionEnd - WORD_ITERATOR_INTERVAL)); if (regionEnd <= spellCheckStart) { return; } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 89c506f..aef8a34 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -26,7 +26,7 @@ import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.DisplayMetrics; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -203,6 +203,130 @@ public class Spinner extends AbsSpinner implements OnClickListener { } } + /** + * Set the background drawable for the spinner's popup window of choices. + * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes. + * + * @param background Background drawable + * + * @attr ref android.R.styleable#Spinner_popupBackground + */ + public void setPopupBackgroundDrawable(Drawable background) { + if (!(mPopup instanceof DropdownPopup)) { + Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring..."); + return; + } + ((DropdownPopup) mPopup).setBackgroundDrawable(background); + } + + /** + * Set the background drawable for the spinner's popup window of choices. + * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes. + * + * @param resId Resource ID of a background drawable + * + * @attr ref android.R.styleable#Spinner_popupBackground + */ + public void setPopupBackgroundResource(int resId) { + setPopupBackgroundDrawable(getContext().getResources().getDrawable(resId)); + } + + /** + * Get the background drawable for the spinner's popup window of choices. + * Only valid in {@link #MODE_DROPDOWN}; other modes will return null. + * + * @return background Background drawable + * + * @attr ref android.R.styleable#Spinner_popupBackground + */ + public Drawable getPopupBackground() { + return mPopup.getBackground(); + } + + /** + * Set a vertical offset in pixels for the spinner's popup window of choices. + * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes. + * + * @param pixels Vertical offset in pixels + * + * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset + */ + public void setDropDownVerticalOffset(int pixels) { + mPopup.setVerticalOffset(pixels); + } + + /** + * Get the configured vertical offset in pixels for the spinner's popup window of choices. + * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0. + * + * @return Vertical offset in pixels + * + * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset + */ + public int getDropDownVerticalOffset() { + return mPopup.getVerticalOffset(); + } + + /** + * Set a horizontal offset in pixels for the spinner's popup window of choices. + * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes. + * + * @param pixels Horizontal offset in pixels + * + * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset + */ + public void setDropDownHorizontalOffset(int pixels) { + mPopup.setHorizontalOffset(pixels); + } + + /** + * Get the configured horizontal offset in pixels for the spinner's popup window of choices. + * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0. + * + * @return Horizontal offset in pixels + * + * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset + */ + public int getDropDownHorizontalOffset() { + return mPopup.getHorizontalOffset(); + } + + /** + * Set the width of the spinner's popup window of choices in pixels. This value + * may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} + * to match the width of the Spinner itself, or + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size + * of contained dropdown list items. + * + * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p> + * + * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT + * + * @attr ref android.R.styleable#Spinner_dropDownWidth + */ + public void setDropDownWidth(int pixels) { + if (!(mPopup instanceof DropdownPopup)) { + Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring"); + return; + } + mDropDownWidth = pixels; + } + + /** + * Get the configured width of the spinner's popup window of choices in pixels. + * The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} + * meaning the popup window will match the width of the Spinner itself, or + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size + * of contained dropdown list items. + * + * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT + * + * @attr ref android.R.styleable#Spinner_dropDownWidth + */ + public int getDropDownWidth() { + return mDropDownWidth; + } + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); @@ -232,6 +356,16 @@ public class Spinner extends AbsSpinner implements OnClickListener { } } + /** + * Describes how the selected item view is positioned. The default is determined by the + * current theme. + * + * @return A {@link android.view.Gravity Gravity} value + */ + public int getGravity() { + return mGravity; + } + @Override public void setAdapter(SpinnerAdapter adapter) { super.setAdapter(adapter); @@ -676,6 +810,13 @@ public class Spinner extends AbsSpinner implements OnClickListener { */ public void setPromptText(CharSequence hintText); public CharSequence getHintText(); + + public void setBackgroundDrawable(Drawable bg); + public void setVerticalOffset(int px); + public void setHorizontalOffset(int px); + public Drawable getBackground(); + public int getVerticalOffset(); + public int getHorizontalOffset(); } private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener { @@ -720,6 +861,36 @@ public class Spinner extends AbsSpinner implements OnClickListener { } dismiss(); } + + @Override + public void setBackgroundDrawable(Drawable bg) { + Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring"); + } + + @Override + public void setVerticalOffset(int px) { + Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring"); + } + + @Override + public void setHorizontalOffset(int px) { + Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring"); + } + + @Override + public Drawable getBackground() { + return null; + } + + @Override + public int getVerticalOffset() { + return 0; + } + + @Override + public int getHorizontalOffset() { + return 0; + } } private class DropdownPopup extends ListPopupWindow implements SpinnerPopup { diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index 334b9c4..a897cc3 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -169,6 +169,8 @@ public class Switch extends CompoundButton { /** * Sets the switch text color, size, style, hint color, and highlight color * from the specified TextAppearance resource. + * + * @attr ref android.R.styleable#Switch_switchTextAppearance */ public void setSwitchTextAppearance(Context context, int resid) { TypedArray appearance = @@ -274,7 +276,151 @@ public class Switch extends CompoundButton { } /** + * Set the amount of horizontal padding between the switch and the associated text. + * + * @param pixels Amount of padding in pixels + * + * @attr ref android.R.styleable#Switch_switchPadding + */ + public void setSwitchPadding(int pixels) { + mSwitchPadding = pixels; + requestLayout(); + } + + /** + * Get the amount of horizontal padding between the switch and the associated text. + * + * @return Amount of padding in pixels + * + * @attr ref android.R.styleable#Switch_switchPadding + */ + public int getSwitchPadding() { + return mSwitchPadding; + } + + /** + * Set the minimum width of the switch in pixels. The switch's width will be the maximum + * of this value and its measured width as determined by the switch drawables and text used. + * + * @param pixels Minimum width of the switch in pixels + * + * @attr ref android.R.styleable#Switch_switchMinWidth + */ + public void setSwitchMinWidth(int pixels) { + mSwitchMinWidth = pixels; + requestLayout(); + } + + /** + * Get the minimum width of the switch in pixels. The switch's width will be the maximum + * of this value and its measured width as determined by the switch drawables and text used. + * + * @return Minimum width of the switch in pixels + * + * @attr ref android.R.styleable#Switch_switchMinWidth + */ + public int getSwitchMinWidth() { + return mSwitchMinWidth; + } + + /** + * Set the horizontal padding around the text drawn on the switch itself. + * + * @param pixels Horizontal padding for switch thumb text in pixels + * + * @attr ref android.R.styleable#Switch_thumbTextPadding + */ + public void setThumbTextPadding(int pixels) { + mThumbTextPadding = pixels; + requestLayout(); + } + + /** + * Get the horizontal padding around the text drawn on the switch itself. + * + * @return Horizontal padding for switch thumb text in pixels + * + * @attr ref android.R.styleable#Switch_thumbTextPadding + */ + public int getThumbTextPadding() { + return mThumbTextPadding; + } + + /** + * Set the drawable used for the track that the switch slides within. + * + * @param track Track drawable + * + * @attr ref android.R.styleable#Switch_track + */ + public void setTrackDrawable(Drawable track) { + mTrackDrawable = track; + requestLayout(); + } + + /** + * Set the drawable used for the track that the switch slides within. + * + * @param resId Resource ID of a track drawable + * + * @attr ref android.R.styleable#Switch_track + */ + public void setTrackResource(int resId) { + setTrackDrawable(getContext().getResources().getDrawable(resId)); + } + + /** + * Get the drawable used for the track that the switch slides within. + * + * @return Track drawable + * + * @attr ref android.R.styleable#Switch_track + */ + public Drawable getTrackDrawable() { + return mTrackDrawable; + } + + /** + * Set the drawable used for the switch "thumb" - the piece that the user + * can physically touch and drag along the track. + * + * @param thumb Thumb drawable + * + * @attr ref android.R.styleable#Switch_thumb + */ + public void setThumbDrawable(Drawable thumb) { + mThumbDrawable = thumb; + requestLayout(); + } + + /** + * Set the drawable used for the switch "thumb" - the piece that the user + * can physically touch and drag along the track. + * + * @param resId Resource ID of a thumb drawable + * + * @attr ref android.R.styleable#Switch_thumb + */ + public void setThumbResource(int resId) { + setThumbDrawable(getContext().getResources().getDrawable(resId)); + } + + /** + * Get the drawable used for the switch "thumb" - the piece that the user + * can physically touch and drag along the track. + * + * @return Thumb drawable + * + * @attr ref android.R.styleable#Switch_thumb + */ + public Drawable getThumbDrawable() { + return mThumbDrawable; + } + + /** * Returns the text displayed when the button is in the checked state. + * + * @attr ref android.R.styleable#Switch_textOn */ public CharSequence getTextOn() { return mTextOn; @@ -282,6 +428,8 @@ public class Switch extends CompoundButton { /** * Sets the text displayed when the button is in the checked state. + * + * @attr ref android.R.styleable#Switch_textOn */ public void setTextOn(CharSequence textOn) { mTextOn = textOn; @@ -290,6 +438,8 @@ public class Switch extends CompoundButton { /** * Returns the text displayed when the button is not in the checked state. + * + * @attr ref android.R.styleable#Switch_textOff */ public CharSequence getTextOff() { return mTextOff; @@ -297,6 +447,8 @@ public class Switch extends CompoundButton { /** * Sets the text displayed when the button is not in the checked state. + * + * @attr ref android.R.styleable#Switch_textOff */ public void setTextOff(CharSequence textOff) { mTextOff = textOff; @@ -367,17 +519,8 @@ public class Switch extends CompoundButton { @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(event); - if (isChecked()) { - CharSequence text = mOnLayout.getText(); - if (TextUtils.isEmpty(text)) { - text = mContext.getString(R.string.switch_on); - } - event.getText().add(text); - } else { - CharSequence text = mOffLayout.getText(); - if (TextUtils.isEmpty(text)) { - text = mContext.getString(R.string.switch_off); - } + CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText(); + if (!TextUtils.isEmpty(text)) { event.getText().add(text); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0639e6b..b8db848 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -216,6 +216,8 @@ import java.util.Locale; * @attr ref android.R.styleable#TextView_drawableBottom * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableLeft + * @attr ref android.R.styleable#TextView_drawableStart + * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawablePadding * @attr ref android.R.styleable#TextView_lineSpacingExtra * @attr ref android.R.styleable#TextView_lineSpacingMultiplier @@ -1520,8 +1522,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Returns the start padding of the view, plus space for the start * Drawable if any. - * - * @hide */ public int getCompoundPaddingStart() { resolveDrawables(); @@ -1537,8 +1537,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Returns the end padding of the view, plus space for the end * Drawable if any. - * - * @hide */ public int getCompoundPaddingEnd() { resolveDrawables(); @@ -1636,8 +1634,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Returns the total start padding of the view, including the start * Drawable if any. - * - * @hide */ public int getTotalPaddingStart() { return getCompoundPaddingStart(); @@ -1646,8 +1642,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Returns the total end padding of the view, including the end * Drawable if any. - * - * @hide */ public int getTotalPaddingEnd() { return getCompoundPaddingEnd(); @@ -1849,8 +1843,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom - * - * @hide */ public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end, Drawable bottom) { @@ -1972,8 +1964,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom - * - * @hide */ public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, int bottom) { @@ -1996,8 +1986,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom - * - * @hide */ public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top, Drawable end, Drawable bottom) { @@ -2034,8 +2022,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Returns drawables for the start, top, end, and bottom borders. - * - * @hide */ public Drawable[] getCompoundDrawablesRelative() { final Drawables dr = mDrawables; @@ -2093,6 +2079,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener invalidate(); } + @Override + public void setPaddingRelative(int start, int top, int end, int bottom) { + if (start != getPaddingStart() || + end != getPaddingEnd() || + top != mPaddingTop || + bottom != mPaddingBottom) { + nullLayouts(); + } + + // the super call will requestLayout() + super.setPaddingRelative(start, top, end, bottom); + invalidate(); + } + /** * Gets the autolink mask of the text. See {@link * android.text.util.Linkify#ALL Linkify.ALL} and peers for @@ -4280,6 +4280,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override + public void onScreenStateChanged(int screenState) { + super.onScreenStateChanged(screenState); + if (mEditor != null) getEditor().onScreenStateChanged(screenState); + } + + @Override protected boolean isPaddingOffsetRequired() { return mShadowRadius != 0 || mDrawables != null; } @@ -4390,9 +4396,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - /** - * @hide - */ @Override public int getResolvedLayoutDirection(Drawable who) { if (who == null) return View.LAYOUT_DIRECTION_LTR; @@ -11397,6 +11400,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hideControllers(); } + void onScreenStateChanged(int screenState) { + switch (screenState) { + case SCREEN_STATE_ON: + resumeBlink(); + break; + case SCREEN_STATE_OFF: + suspendBlink(); + break; + } + } + + private void suspendBlink() { + if (mBlink != null) { + mBlink.cancel(); + } + } + + private void resumeBlink() { + if (mBlink != null) { + mBlink.uncancel(); + makeBlink(); + } + } + void adjustInputType(boolean password, boolean passwordInputType, boolean webPasswordInputType, boolean numberPasswordInputType) { // mInputType has been set from inputType, possibly modified by mInputMethod. diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java index a0edafe..4beee96 100644 --- a/core/java/android/widget/ToggleButton.java +++ b/core/java/android/widget/ToggleButton.java @@ -154,16 +154,6 @@ public class ToggleButton extends CompoundButton { } @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); - if (isChecked()) { - event.getText().add(mContext.getString(R.string.togglebutton_pressed)); - } else { - event.getText().add(mContext.getString(R.string.togglebutton_not_pressed)); - } - } - - @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(ToggleButton.class.getName()); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 6a99a2b..998c037 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -348,6 +348,7 @@ public class ZygoteInit { TypedArray ar = mResources.obtainTypedArray( com.android.internal.R.array.preloaded_drawables); int N = preloadDrawables(runtime, ar); + ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " + (SystemClock.uptimeMillis()-startTime) + "ms."); @@ -355,6 +356,7 @@ public class ZygoteInit { ar = mResources.obtainTypedArray( com.android.internal.R.array.preloaded_color_state_lists); N = preloadColorStateLists(runtime, ar); + ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " + (SystemClock.uptimeMillis()-startTime) + "ms."); } diff --git a/core/java/com/android/internal/util/AsyncService.java b/core/java/com/android/internal/util/AsyncService.java index 54d3c42..e39a2bf 100644 --- a/core/java/com/android/internal/util/AsyncService.java +++ b/core/java/com/android/internal/util/AsyncService.java @@ -106,7 +106,7 @@ abstract public class AsyncService extends Service { /** * Called when service is destroyed. After returning the - * service is dead an no more processing should be expected + * service is dead and no more processing should be expected * to occur. */ @Override diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index 61c0c8e..da189f1 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -1195,7 +1195,7 @@ public class StateMachine { } /** - * Constructor creates an StateMachine using the looper. + * Constructor creates a StateMachine using the looper. * * @param name of the state machine */ diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index b227700..15d11d8 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -49,6 +49,9 @@ public class BaseIWindow extends IWindow.Stub { public void dispatchGetNewSurface() { } + public void dispatchScreenState(boolean on) { + } + public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index a10d241..d5c2018 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -27,28 +27,26 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; /** * @hide */ -public class ActionMenuItemView extends LinearLayout +public class ActionMenuItemView extends TextView implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener, ActionMenuView.ActionMenuChildView { private static final String TAG = "ActionMenuItemView"; private MenuItemImpl mItemData; private CharSequence mTitle; + private Drawable mIcon; private MenuBuilder.ItemInvoker mItemInvoker; - private ImageButton mImageButton; - private Button mTextButton; private boolean mAllowTextWithIcon; private boolean mExpandedFormat; private int mMinWidth; + private int mSavedPaddingLeft; public ActionMenuItemView(Context context) { this(context, null); @@ -68,17 +66,12 @@ public class ActionMenuItemView extends LinearLayout mMinWidth = a.getDimensionPixelSize( com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0); a.recycle(); - } - @Override - public void onFinishInflate() { - mImageButton = (ImageButton) findViewById(com.android.internal.R.id.imageButton); - mTextButton = (Button) findViewById(com.android.internal.R.id.textButton); - mImageButton.setOnClickListener(this); - mTextButton.setOnClickListener(this); - mImageButton.setOnLongClickListener(this); setOnClickListener(this); setOnLongClickListener(this); + + // Save the inflated padding for later, we'll need it. + mSavedPaddingLeft = getPaddingLeft(); } public MenuItemImpl getItemData() { @@ -96,13 +89,6 @@ public class ActionMenuItemView extends LinearLayout setEnabled(itemData.isEnabled()); } - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - mImageButton.setEnabled(enabled); - mTextButton.setEnabled(enabled); - } - public void onClick(View v) { if (mItemInvoker != null) { mItemInvoker.invokeItem(mItemData); @@ -135,26 +121,22 @@ public class ActionMenuItemView extends LinearLayout } private void updateTextButtonVisibility() { - boolean visible = !TextUtils.isEmpty(mTextButton.getText()); - visible &= mImageButton.getDrawable() == null || + boolean visible = !TextUtils.isEmpty(mTitle); + visible &= mIcon == null || (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat)); - mTextButton.setVisibility(visible ? VISIBLE : GONE); + setText(visible ? mTitle : null); } public void setIcon(Drawable icon) { - mImageButton.setImageDrawable(icon); - if (icon != null) { - mImageButton.setVisibility(VISIBLE); - } else { - mImageButton.setVisibility(GONE); - } + mIcon = icon; + setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); updateTextButtonVisibility(); } public boolean hasText() { - return mTextButton.getVisibility() != GONE; + return !TextUtils.isEmpty(getText()); } public void setShortcut(boolean showShortcut, char shortcutKey) { @@ -164,8 +146,6 @@ public class ActionMenuItemView extends LinearLayout public void setTitle(CharSequence title) { mTitle = title; - mTextButton.setText(mTitle); - setContentDescription(mTitle); updateTextButtonVisibility(); } @@ -236,12 +216,17 @@ public class ActionMenuItemView extends LinearLayout @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final boolean textVisible = hasText(); + if (textVisible) { + setPadding(mSavedPaddingLeft, getPaddingTop(), getPaddingRight(), getPaddingBottom()); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int specSize = MeasureSpec.getSize(widthMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); final int oldMeasuredWidth = getMeasuredWidth(); - final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, mMinWidth) + final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(widthSize, mMinWidth) : mMinWidth; if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) { @@ -249,5 +234,13 @@ public class ActionMenuItemView extends LinearLayout super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), heightMeasureSpec); } + + if (!textVisible && mIcon != null) { + // TextView won't center compound drawables in both dimensions without + // a little coercion. Pad in to center the icon after we've measured. + final int w = getMeasuredWidth(); + final int dw = mIcon.getIntrinsicWidth(); + setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom()); + } } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 530809b..dca45a9 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -116,9 +116,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (!mMaxItemsSet) { mMaxItems = mContext.getResources().getInteger( com.android.internal.R.integer.max_action_buttons); - if (mMenu != null) { - mMenu.onItemsChanged(true); - } + } + if (mMenu != null) { + mMenu.onItemsChanged(true); } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java index 8d8c72c..e00fe9f 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuView.java @@ -96,6 +96,13 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo if (mFormatItems) { onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec); } else { + // Previous measurement at exact format may have set margins - reset them. + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.leftMargin = lp.rightMargin = 0; + } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 2f325bf..8c05459 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -324,13 +324,31 @@ public class ActionBarView extends AbsActionBarView { if (mSplitView != null) { mSplitView.addView(mMenuView); } + mMenuView.getLayoutParams().width = LayoutParams.MATCH_PARENT; } else { addView(mMenuView); + mMenuView.getLayoutParams().width = LayoutParams.WRAP_CONTENT; } + mMenuView.requestLayout(); } if (mSplitView != null) { mSplitView.setVisibility(splitActionBar ? VISIBLE : GONE); } + + if (mActionMenuPresenter != null) { + if (!splitActionBar) { + mActionMenuPresenter.setExpandedActionViewsExclusive( + getResources().getBoolean( + com.android.internal.R.bool.action_bar_expanded_action_views_exclusive)); + } else { + mActionMenuPresenter.setExpandedActionViewsExclusive(false); + // Allow full screen width in split mode. + mActionMenuPresenter.setWidthLimit( + getContext().getResources().getDisplayMetrics().widthPixels, true); + // No limit to the item count; use whatever will fit. + mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); + } + } super.setSplitActionBar(splitActionBar); } } diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java index 3865510..f535a08 100644 --- a/core/java/com/android/internal/widget/SlidingTab.java +++ b/core/java/com/android/internal/widget/SlidingTab.java @@ -416,7 +416,7 @@ public class SlidingTab extends ViewGroup { } /** - * Start animating the slider. Note we need two animations since an ValueAnimator + * Start animating the slider. Note we need two animations since a ValueAnimator * keeps internal state of the invalidation region which is just the view being animated. * * @param anim1 |
