diff options
Diffstat (limited to 'core/java/android')
397 files changed, 27394 insertions, 21979 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 81ee192..b0bad07 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -323,7 +323,7 @@ public abstract class AccessibilityService extends Service { public static final int GLOBAL_ACTION_HOME = 2; /** - * Action to open the recents. + * Action to open the recent apps. */ public static final int GLOBAL_ACTION_RECENTS = 3; @@ -332,6 +332,11 @@ public abstract class AccessibilityService extends Service { */ public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; + /** + * Action to open the quick settings. + */ + public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; + private static final String LOG_TAG = "AccessibilityService"; interface Callbacks { diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 10ea0fe..75a4f83 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -85,6 +85,11 @@ public class AccessibilityServiceInfo implements Parcelable { public static final int FEEDBACK_GENERIC = 0x0000010; /** + * Denotes braille feedback. + */ + public static final int FEEDBACK_BRAILLE = 0x0000020; + + /** * Mask for all feedback types. * * @see #FEEDBACK_SPOKEN @@ -92,6 +97,7 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #FEEDBACK_AUDIBLE * @see #FEEDBACK_VISUAL * @see #FEEDBACK_GENERIC + * @see #FEEDBACK_BRAILLE */ public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF; @@ -186,6 +192,7 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #FEEDBACK_HAPTIC * @see #FEEDBACK_SPOKEN * @see #FEEDBACK_VISUAL + * @see #FEEDBACK_BRAILLE */ public int feedbackType; @@ -591,6 +598,12 @@ public class AccessibilityServiceInfo implements Parcelable { } builder.append("FEEDBACK_VISUAL"); break; + case FEEDBACK_BRAILLE: + if (builder.length() > 1) { + builder.append(", "); + } + builder.append("FEEDBACK_BRAILLE"); + break; } } builder.append("]"); diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 22e454f..fc569e0 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -50,7 +50,8 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserId; +import android.os.UserHandle; +import android.os.UserManager; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -92,6 +93,7 @@ public class AccountManagerService private final Context mContext; private final PackageManager mPackageManager; + private UserManager mUserManager; private HandlerThread mMessageThread; private final MessageHandler mMessageHandler; @@ -245,6 +247,13 @@ public class AccountManagerService initUser(0); } + private UserManager getUserManager() { + if (mUserManager == null) { + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + return mUserManager; + } + private UserAccounts initUser(int userId) { synchronized (mUsers) { UserAccounts accounts = mUsers.get(userId); @@ -345,7 +354,7 @@ public class AccountManagerService } private UserAccounts getUserAccountsForCaller() { - return getUserAccounts(UserId.getCallingUserId()); + return getUserAccounts(UserHandle.getCallingUserId()); } protected UserAccounts getUserAccounts(int userId) { @@ -360,7 +369,7 @@ public class AccountManagerService } private void onUserRemoved(Intent intent) { - int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1); + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userId < 1) return; UserAccounts accounts; @@ -382,12 +391,7 @@ public class AccountManagerService } private List<UserInfo> getAllUsers() { - try { - return AppGlobals.getPackageManager().getUsers(); - } catch (RemoteException re) { - // Local to system process, shouldn't happen - } - return null; + return getUserManager().getUsers(); } public void onServiceChanged(AuthenticatorDescription desc, boolean removed) { @@ -643,16 +647,17 @@ public class AccountManagerService if (response == null) throw new IllegalArgumentException("response is null"); if (account == null) throw new IllegalArgumentException("account is null"); checkManageAccountsPermission(); + UserHandle user = Binder.getCallingUserHandle(); UserAccounts accounts = getUserAccountsForCaller(); long identityToken = clearCallingIdentity(); - cancelNotification(getSigninRequiredNotificationId(accounts, account)); + cancelNotification(getSigninRequiredNotificationId(accounts, account), user); synchronized(accounts.credentialsPermissionNotificationIds) { for (Pair<Pair<Account, String>, Integer> pair: accounts.credentialsPermissionNotificationIds.keySet()) { if (account.equals(pair.first.first)) { int id = accounts.credentialsPermissionNotificationIds.get(pair); - cancelNotification(id); + cancelNotification(id, user); } } } @@ -785,7 +790,8 @@ public class AccountManagerService if (account == null || type == null) { return false; } - cancelNotification(getSigninRequiredNotificationId(accounts, account)); + cancelNotification(getSigninRequiredNotificationId(accounts, account), + new UserHandle(accounts.userId)); synchronized (accounts.cacheLock) { final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); @@ -896,7 +902,7 @@ public class AccountManagerService private void sendAccountsChangedBroadcast(int userId) { Log.i(TAG, "the accounts changed, sending broadcast of " + ACCOUNTS_CHANGED_INTENT.getAction()); - mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT, userId); + mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId)); } public void clearPassword(Account account) { @@ -1000,7 +1006,7 @@ public class AccountManagerService if (callingUid != android.os.Process.SYSTEM_UID) { throw new SecurityException("can only call from system"); } - UserAccounts accounts = getUserAccounts(UserId.getUserId(callingUid)); + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); long identityToken = clearCallingIdentity(); try { new Session(accounts, response, accountType, false, @@ -1048,7 +1054,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(); + final UserAccounts accounts = getUserAccountsForCaller(); AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo = mAuthenticatorCache.getServiceInfo( AuthenticatorDescription.newKey(account.type)); @@ -1137,7 +1143,7 @@ public class AccountManagerService if (intent != null && notifyOnAuthFailure && !customTokens) { doNotification(mAccounts, account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE), - intent); + intent, accounts.userId); } } super.onResult(result); @@ -1148,7 +1154,8 @@ public class AccountManagerService } } - private void createNoCredentialsPermissionNotification(Account account, Intent intent) { + private void createNoCredentialsPermissionNotification(Account account, Intent intent, + int userId) { int uid = intent.getIntExtra( GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1); String authTokenType = intent.getStringExtra( @@ -1168,10 +1175,12 @@ public class AccountManagerService title = titleAndSubtitle.substring(0, index); subtitle = titleAndSubtitle.substring(index + 1); } - n.setLatestEventInfo(mContext, - title, subtitle, - PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); - installNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), n); + UserHandle user = new UserHandle(userId); + n.setLatestEventInfo(mContext, title, subtitle, + PendingIntent.getActivityAsUser(mContext, 0, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, user)); + installNotification(getCredentialPermissionNotificationId( + account, authTokenType, uid), n, user); } String getAccountLabel(String accountType) { @@ -1218,7 +1227,7 @@ public class AccountManagerService private Integer getCredentialPermissionNotificationId(Account account, String authTokenType, int uid) { Integer id; - UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid)); synchronized (accounts.credentialsPermissionNotificationIds) { final Pair<Pair<Account, String>, Integer> key = new Pair<Pair<Account, String>, Integer>( @@ -1757,7 +1766,8 @@ 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(mAccounts, account)); + cancelNotification(getSigninRequiredNotificationId(mAccounts, account), + new UserHandle(mAccounts.userId)); } } IAccountManagerResponse response; @@ -1875,7 +1885,7 @@ public class AccountManagerService private static String getDatabaseName(int userId) { File systemDir = Environment.getSystemSecureDirectory(); - File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME); + File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME); if (userId == 0) { // Migrate old file, if it exists, to the new location. // Make sure the new file doesn't already exist. A dummy file could have been @@ -1884,7 +1894,7 @@ public class AccountManagerService File oldFile = new File(systemDir, DATABASE_NAME); if (oldFile.exists() && !databaseFile.exists()) { // Check for use directory; create if it doesn't exist, else renameTo will fail - File userDir = new File(systemDir, "users/" + userId); + File userDir = Environment.getUserSystemDirectory(userId); if (!userDir.exists()) { if (!userDir.mkdirs()) { throw new IllegalStateException("User dir cannot be created: " + userDir); @@ -2079,7 +2089,7 @@ public class AccountManagerService } private void doNotification(UserAccounts accounts, Account account, CharSequence message, - Intent intent) { + Intent intent, int userId) { long identityToken = clearCallingIdentity(); try { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -2089,35 +2099,38 @@ public class AccountManagerService if (intent.getComponent() != null && GrantCredentialsPermissionActivity.class.getName().equals( intent.getComponent().getClassName())) { - createNoCredentialsPermissionNotification(account, intent); + createNoCredentialsPermissionNotification(account, intent, userId); } else { final Integer notificationId = getSigninRequiredNotificationId(accounts, account); intent.addCategory(String.valueOf(notificationId)); Notification n = new Notification(android.R.drawable.stat_sys_warning, null, 0 /* when */); + UserHandle user = new UserHandle(userId); final String notificationTitleFormat = mContext.getText(R.string.notification_title).toString(); n.setLatestEventInfo(mContext, String.format(notificationTitleFormat, account.name), - message, PendingIntent.getActivity( - mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); - installNotification(notificationId, n); + message, PendingIntent.getActivityAsUser( + mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, + null, user)); + installNotification(notificationId, n, user); } } finally { restoreCallingIdentity(identityToken); } } - protected void installNotification(final int notificationId, final Notification n) { + protected void installNotification(final int notificationId, final Notification n, + UserHandle user) { ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) - .notify(notificationId, n); + .notifyAsUser(null, notificationId, n, user); } - protected void cancelNotification(int id) { + protected void cancelNotification(int id, UserHandle user) { long identityToken = clearCallingIdentity(); try { ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) - .cancel(id); + .cancelAsUser(null, id, user); } finally { restoreCallingIdentity(identityToken); } @@ -2265,7 +2278,7 @@ public class AccountManagerService Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception()); return; } - UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid)); synchronized (accounts.cacheLock) { final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); @@ -2282,7 +2295,8 @@ public class AccountManagerService } finally { db.endTransaction(); } - cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid)); + cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), + new UserHandle(accounts.userId)); } } @@ -2299,7 +2313,7 @@ public class AccountManagerService Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception()); return; } - UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid)); synchronized (accounts.cacheLock) { final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); db.beginTransaction(); @@ -2316,7 +2330,8 @@ public class AccountManagerService } finally { db.endTransaction(); } - cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid)); + cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), + new UserHandle(accounts.userId)); } } diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 6b3b7fd..5358bc7 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -15,6 +15,8 @@ */ package android.accounts; +import com.google.android.collect.Sets; + import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -105,6 +107,13 @@ public class ChooseTypeAndAccountActivity extends Activity private static final int SELECTED_ITEM_NONE = -1; + private Set<Account> mSetOfAllowableAccounts; + private Set<String> mSetOfRelevantAccountTypes; + private String mSelectedAccountName = null; + private boolean mSelectedAddNewAccount = false; + private boolean mAlwaysPromptForAccount = false; + private String mDescriptionOverride; + private ArrayList<Account> mAccounts; private int mPendingRequest = REQUEST_NULL; private Parcelable[] mExistingAccounts = null; @@ -120,22 +129,18 @@ public class ChooseTypeAndAccountActivity extends Activity } // save some items we use frequently - final AccountManager accountManager = AccountManager.get(this); final Intent intent = getIntent(); - String selectedAccountName = null; - boolean selectedAddNewAccount = false; - if (savedInstanceState != null) { mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST); mExistingAccounts = savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS); // Makes sure that any user selection is preserved across orientation changes. - selectedAccountName = savedInstanceState.getString( + mSelectedAccountName = savedInstanceState.getString( KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME); - selectedAddNewAccount = savedInstanceState.getBoolean( + mSelectedAddNewAccount = savedInstanceState.getBoolean( KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false); } else { mPendingRequest = REQUEST_NULL; @@ -144,85 +149,38 @@ public class ChooseTypeAndAccountActivity extends Activity // show is as pre-selected. Account selectedAccount = (Account) intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT); if (selectedAccount != null) { - selectedAccountName = selectedAccount.name; + mSelectedAccountName = selectedAccount.name; } } if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "selected account name is " + selectedAccountName); + Log.v(TAG, "selected account name is " + mSelectedAccountName); } - // build an efficiently queryable map of account types to authenticator descriptions - final HashMap<String, AuthenticatorDescription> typeToAuthDescription = - new HashMap<String, AuthenticatorDescription>(); - for(AuthenticatorDescription desc : accountManager.getAuthenticatorTypes()) { - typeToAuthDescription.put(desc.type, desc); - } - - // Read the validAccounts, if present, and add them to the setOfAllowableAccounts - Set<Account> setOfAllowableAccounts = null; - final ArrayList<Parcelable> validAccounts = - intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST); - if (validAccounts != null) { - setOfAllowableAccounts = new HashSet<Account>(validAccounts.size()); - for (Parcelable parcelable : validAccounts) { - setOfAllowableAccounts.add((Account)parcelable); - } - } - // An account type is relevant iff it is allowed by the caller and supported by the account - // manager. - Set<String> setOfRelevantAccountTypes = null; - final String[] allowedAccountTypes = - intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); - if (allowedAccountTypes != null) { - - setOfRelevantAccountTypes = new HashSet<String>(allowedAccountTypes.length); - Set<String> setOfAllowedAccountTypes = new HashSet<String>(allowedAccountTypes.length); - for (String type : allowedAccountTypes) { - setOfAllowedAccountTypes.add(type); - } - - AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes(); - Set<String> supportedAccountTypes = new HashSet<String>(descs.length); - for (AuthenticatorDescription desc : descs) { - supportedAccountTypes.add(desc.type); - } + mSetOfAllowableAccounts = getAllowableAccountSet(intent); + mSetOfRelevantAccountTypes = getReleventAccountTypes(intent); + mAlwaysPromptForAccount = intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false); + mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); + } - for (String acctType : setOfAllowedAccountTypes) { - if (supportedAccountTypes.contains(acctType)) { - setOfRelevantAccountTypes.add(acctType); - } - } - } + @Override + protected void onResume() { + super.onResume(); + final AccountManager accountManager = AccountManager.get(this); - // Create a list of AccountInfo objects for each account that is allowable. Filter out - // accounts that don't match the allowable types, if provided, or that don't match the - // allowable accounts, if provided. - final Account[] accounts = accountManager.getAccounts(); - mAccounts = new ArrayList<Account>(accounts.length); - mSelectedItemIndex = SELECTED_ITEM_NONE; - for (Account account : accounts) { - if (setOfAllowableAccounts != null - && !setOfAllowableAccounts.contains(account)) { - continue; - } - if (setOfRelevantAccountTypes != null - && !setOfRelevantAccountTypes.contains(account.type)) { - continue; - } - if (account.name.equals(selectedAccountName)) { - mSelectedItemIndex = mAccounts.size(); - } - mAccounts.add(account); - } + mAccounts = getAcceptableAccountChoices(accountManager); + // In cases where the activity does not need to show an account picker, cut the chase + // and return the result directly. Eg: + // Single account -> select it directly + // No account -> launch add account activity directly if (mPendingRequest == REQUEST_NULL) { // If there are no relevant accounts and only one relevant account type go directly to // add account. Otherwise let the user choose. if (mAccounts.isEmpty()) { - if (setOfRelevantAccountTypes.size() == 1) { - runAddAccountForAuthenticator(setOfRelevantAccountTypes.iterator().next()); + if (mSetOfRelevantAccountTypes.size() == 1) { + runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next()); } else { startChooseAccountTypeActivity(); } @@ -230,61 +188,22 @@ public class ChooseTypeAndAccountActivity extends Activity } // if there is only one allowable account return it - if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false) - && mAccounts.size() == 1) { + if (!mAlwaysPromptForAccount && mAccounts.size() == 1) { Account account = mAccounts.get(0); setResultAndFinish(account.name, account.type); return; } } + String[] listItems = getListOfDisplayableOptions(mAccounts); + mSelectedItemIndex = getItemIndexToSelect( + mAccounts, mSelectedAccountName, mSelectedAddNewAccount); + // Cannot set content view until we know that mPendingRequest is not null, otherwise // would cause screen flicker. setContentView(R.layout.choose_type_and_account); - - // Override the description text if supplied - final String descriptionOverride = - intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); - TextView descriptionView = (TextView) findViewById(R.id.description); - if (!TextUtils.isEmpty(descriptionOverride)) { - descriptionView.setText(descriptionOverride); - } else { - descriptionView.setVisibility(View.GONE); - } - - // List of options includes all accounts found together with "Add new account" as the - // last item in the list. - String[] listItems = new String[mAccounts.size() + 1]; - for (int i = 0; i < mAccounts.size(); i++) { - listItems[i] = mAccounts.get(i).name; - } - listItems[mAccounts.size()] = getResources().getString( - R.string.add_account_button_label); - - ListView list = (ListView) findViewById(android.R.id.list); - list.setAdapter(new ArrayAdapter<String>(this, - android.R.layout.simple_list_item_single_choice, listItems)); - list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - list.setItemsCanFocus(false); - list.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View v, int position, long id) { - mSelectedItemIndex = position; - mOkButton.setEnabled(true); - } - }); - - // If "Add account" option was previously selected by user, preserve it across - // orientation changes. - if (selectedAddNewAccount) { - mSelectedItemIndex = mAccounts.size(); - } - if (mSelectedItemIndex != SELECTED_ITEM_NONE) { - list.setItemChecked(mSelectedItemIndex, true); - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected"); - } - } + overrideDescriptionIfSupplied(mDescriptionOverride); + populateUIAccountList(listItems); // Only enable "OK" button if something has been selected. mOkButton = (Button) findViewById(android.R.id.button2); @@ -480,4 +399,137 @@ public class ChooseTypeAndAccountActivity extends Activity startActivityForResult(intent, REQUEST_CHOOSE_TYPE); mPendingRequest = REQUEST_CHOOSE_TYPE; } + + /** + * @return a value between 0 (inclusive) and accounts.size() (inclusive) or SELECTED_ITEM_NONE. + * An index value of accounts.size() indicates 'Add account' option. + */ + private int getItemIndexToSelect(ArrayList<Account> accounts, String selectedAccountName, + boolean selectedAddNewAccount) { + // If "Add account" option was previously selected by user, preserve it across + // orientation changes. + if (selectedAddNewAccount) { + return accounts.size(); + } + // search for the selected account name if present + for (int i = 0; i < accounts.size(); i++) { + if (accounts.get(i).name.equals(selectedAccountName)) { + return i; + } + } + // no account selected. + return SELECTED_ITEM_NONE; + } + + private String[] getListOfDisplayableOptions(ArrayList<Account> accounts) { + // List of options includes all accounts found together with "Add new account" as the + // last item in the list. + String[] listItems = new String[accounts.size() + 1]; + for (int i = 0; i < accounts.size(); i++) { + listItems[i] = accounts.get(i).name; + } + listItems[accounts.size()] = getResources().getString( + R.string.add_account_button_label); + return listItems; + } + + /** + * Create a list of Account objects for each account that is acceptable. Filter out + * accounts that don't match the allowable types, if provided, or that don't match the + * allowable accounts, if provided. + */ + private ArrayList<Account> getAcceptableAccountChoices(AccountManager accountManager) { + final Account[] accounts = accountManager.getAccounts(); + ArrayList<Account> accountsToPopulate = new ArrayList<Account>(accounts.length); + for (Account account : accounts) { + if (mSetOfAllowableAccounts != null + && !mSetOfAllowableAccounts.contains(account)) { + continue; + } + if (mSetOfRelevantAccountTypes != null + && !mSetOfRelevantAccountTypes.contains(account.type)) { + continue; + } + accountsToPopulate.add(account); + } + return accountsToPopulate; + } + + /** + * Return a set of account types speficied by the intent as well as supported by the + * AccountManager. + */ + private Set<String> getReleventAccountTypes(final Intent intent) { + // An account type is relevant iff it is allowed by the caller and supported by the account + // manager. + Set<String> setOfRelevantAccountTypes = null; + final String[] allowedAccountTypes = + intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); + if (allowedAccountTypes != null) { + setOfRelevantAccountTypes = Sets.newHashSet(allowedAccountTypes); + AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes(); + Set<String> supportedAccountTypes = new HashSet<String>(descs.length); + for (AuthenticatorDescription desc : descs) { + supportedAccountTypes.add(desc.type); + } + setOfRelevantAccountTypes.retainAll(supportedAccountTypes); + } + return setOfRelevantAccountTypes; + } + + /** + * Returns a set of whitelisted accounts given by the intent or null if none specified by the + * intent. + */ + private Set<Account> getAllowableAccountSet(final Intent intent) { + Set<Account> setOfAllowableAccounts = null; + final ArrayList<Parcelable> validAccounts = + intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST); + if (validAccounts != null) { + setOfAllowableAccounts = new HashSet<Account>(validAccounts.size()); + for (Parcelable parcelable : validAccounts) { + setOfAllowableAccounts.add((Account)parcelable); + } + } + return setOfAllowableAccounts; + } + + /** + * Overrides the description text view for the picker activity if specified by the intent. + * If not specified then makes the description invisible. + */ + private void overrideDescriptionIfSupplied(String descriptionOverride) { + TextView descriptionView = (TextView) findViewById(R.id.description); + if (!TextUtils.isEmpty(descriptionOverride)) { + descriptionView.setText(descriptionOverride); + } else { + descriptionView.setVisibility(View.GONE); + } + } + + /** + * Populates the UI ListView with the given list of items and selects an item + * based on {@code mSelectedItemIndex} member variable. + */ + private final void populateUIAccountList(String[] listItems) { + ListView list = (ListView) findViewById(android.R.id.list); + list.setAdapter(new ArrayAdapter<String>(this, + android.R.layout.simple_list_item_single_choice, listItems)); + list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + list.setItemsCanFocus(false); + list.setOnItemClickListener( + new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { + mSelectedItemIndex = position; + mOkButton.setEnabled(true); + } + }); + if (mSelectedItemIndex != SELECTED_ITEM_NONE) { + list.setItemChecked(mSelectedItemIndex, true); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected"); + } + } + } } diff --git a/core/java/android/animation/ArgbEvaluator.java b/core/java/android/animation/ArgbEvaluator.java index c3875be..717a3d9 100644 --- a/core/java/android/animation/ArgbEvaluator.java +++ b/core/java/android/animation/ArgbEvaluator.java @@ -40,13 +40,13 @@ public class ArgbEvaluator implements TypeEvaluator { */ public Object evaluate(float fraction, Object startValue, Object endValue) { int startInt = (Integer) startValue; - int startA = (startInt >> 24); + int startA = (startInt >> 24) & 0xff; int startR = (startInt >> 16) & 0xff; int startG = (startInt >> 8) & 0xff; int startB = startInt & 0xff; int endInt = (Integer) endValue; - int endA = (endInt >> 24); + int endA = (endInt >> 24) & 0xff; int endR = (endInt >> 16) & 0xff; int endG = (endInt >> 8) & 0xff; int endB = endInt & 0xff; diff --git a/core/java/android/animation/Keyframe.java b/core/java/android/animation/Keyframe.java index e98719a..dc8538f 100644 --- a/core/java/android/animation/Keyframe.java +++ b/core/java/android/animation/Keyframe.java @@ -261,7 +261,7 @@ public abstract class Keyframe implements Cloneable { @Override public ObjectKeyframe clone() { - ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mValue); + ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mHasValue ? mValue : null); kfClone.setInterpolator(getInterpolator()); return kfClone; } @@ -306,7 +306,9 @@ public abstract class Keyframe implements Cloneable { @Override public IntKeyframe clone() { - IntKeyframe kfClone = new IntKeyframe(getFraction(), mValue); + IntKeyframe kfClone = mHasValue ? + new IntKeyframe(getFraction(), mValue) : + new IntKeyframe(getFraction()); kfClone.setInterpolator(getInterpolator()); return kfClone; } @@ -350,7 +352,9 @@ public abstract class Keyframe implements Cloneable { @Override public FloatKeyframe clone() { - FloatKeyframe kfClone = new FloatKeyframe(getFraction(), mValue); + FloatKeyframe kfClone = mHasValue ? + new FloatKeyframe(getFraction(), mValue) : + new FloatKeyframe(getFraction()); kfClone.setInterpolator(getInterpolator()); return kfClone; } diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java index 088d20d..f9aa00e 100644 --- a/core/java/android/animation/TimeAnimator.java +++ b/core/java/android/animation/TimeAnimator.java @@ -13,6 +13,12 @@ public class TimeAnimator extends ValueAnimator { private long mPreviousTime = -1; @Override + public void start() { + mPreviousTime = -1; + super.start(); + } + + @Override boolean animationFrame(long currentTime) { if (mListener != null) { long totalTime = currentTime - mStartTime; diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index f3a442a..f7460c4 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -232,6 +232,13 @@ public class ValueAnimator extends Animator { } /** + * @hide + */ + public static float getDurationScale() { + return sDurationScale; + } + + /** * Creates a new ValueAnimator object. This default constructor is primarily for * use internally; the factory methods which take parameters are more generally * useful. @@ -529,6 +536,9 @@ public class ValueAnimator extends Animator { // The per-thread list of all active animations private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>(); + // Used in doAnimationFrame() to avoid concurrent modifications of mAnimations + private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>(); + // The per-thread set of animations to be started on the next animation frame private final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>(); @@ -598,28 +608,18 @@ public class ValueAnimator extends Animator { // Now process all active animations. The return value from animationFrame() // tells the handler whether it should now be ended int numAnims = mAnimations.size(); - int i = 0; - while (i < numAnims) { - ValueAnimator anim = mAnimations.get(i); - if (anim.doAnimationFrame(frameTime)) { + for (int i = 0; i < numAnims; ++i) { + mTmpAnimations.add(mAnimations.get(i)); + } + for (int i = 0; i < numAnims; ++i) { + ValueAnimator anim = mTmpAnimations.get(i); + if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) { mEndingAnims.add(anim); } - if (mAnimations.size() == numAnims) { - ++i; - } else { - // An animation might be canceled or ended by client code - // during the animation frame. Check to see if this happened by - // seeing whether the current index is the same as it was before - // calling animationFrame(). Another approach would be to copy - // animations to a temporary list and process that list instead, - // but that entails garbage and processing overhead that would - // be nice to avoid. - --numAnims; - mEndingAnims.remove(anim); - } } + mTmpAnimations.clear(); if (mEndingAnims.size() > 0) { - for (i = 0; i < mEndingAnims.size(); ++i) { + for (int i = 0; i < mEndingAnims.size(); ++i) { mEndingAnims.get(i).endAnimation(this); } mEndingAnims.clear(); @@ -958,13 +958,7 @@ public class ValueAnimator extends Animator { } else if (!mInitialized) { initAnimation(); } - // The final value set on the target varies, depending on whether the animation - // was supposed to repeat an odd number of times - if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) { - animateValue(0f); - } else { - animateValue(1f); - } + animateValue(mPlayingBackwards ? 0f : 1f); endAnimation(handler); } diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index cff16ff..2337790 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -918,6 +918,8 @@ public abstract class ActionBar { @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"), @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"), @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"), + @ViewDebug.IntToString(from = Gravity.START, to = "START"), + @ViewDebug.IntToString(from = Gravity.END, to = "END"), @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"), @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"), @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"), @@ -925,7 +927,7 @@ public abstract class ActionBar { @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"), @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL") }) - public int gravity = -1; + public int gravity = Gravity.NO_GRAVITY; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); @@ -933,13 +935,14 @@ public abstract class ActionBar { TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ActionBar_LayoutParams); gravity = a.getInt( - com.android.internal.R.styleable.ActionBar_LayoutParams_layout_gravity, -1); + com.android.internal.R.styleable.ActionBar_LayoutParams_layout_gravity, + Gravity.NO_GRAVITY); a.recycle(); } public LayoutParams(int width, int height) { super(width, height); - this.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT; + this.gravity = Gravity.CENTER_VERTICAL | Gravity.START; } public LayoutParams(int width, int height, int gravity) { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f20fd33..7606d5e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -48,6 +48,7 @@ import android.os.Looper; import android.os.Parcelable; import android.os.RemoteException; import android.os.StrictMode; +import android.os.UserHandle; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -74,7 +75,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewManager; import android.view.Window; import android.view.WindowManager; -import android.view.WindowManagerImpl; +import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; @@ -652,8 +653,9 @@ public class Activity extends ContextThemeWrapper /** Start of user-defined activity results. */ public static final int RESULT_FIRST_USER = 1; + static final String FRAGMENTS_TAG = "android:fragments"; + private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState"; - private static final String FRAGMENTS_TAG = "android:fragments"; private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds"; private static final String SAVED_DIALOGS_TAG = "android:savedDialogs"; private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; @@ -696,7 +698,7 @@ public class Activity extends ContextThemeWrapper Object activity; HashMap<String, Object> children; ArrayList<Fragment> fragments; - SparseArray<LoaderManagerImpl> loaders; + HashMap<String, LoaderManagerImpl> loaders; } /* package */ NonConfigurationInstances mLastNonConfigurationInstances; @@ -714,8 +716,14 @@ public class Activity extends ContextThemeWrapper private int mTitleColor = 0; final FragmentManagerImpl mFragments = new FragmentManagerImpl(); + final FragmentContainer mContainer = new FragmentContainer() { + @Override + public View findViewById(int id) { + return Activity.this.findViewById(id); + } + }; - SparseArray<LoaderManagerImpl> mAllLoaderManagers; + HashMap<String, LoaderManagerImpl> mAllLoaderManagers; LoaderManagerImpl mLoaderManager; private static final class ManagedCursor { @@ -743,6 +751,7 @@ public class Activity extends ContextThemeWrapper protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused}; + @SuppressWarnings("unused") private final Object mInstanceTracker = StrictMode.trackActivity(this); private Thread mUiThread; @@ -807,19 +816,19 @@ public class Activity extends ContextThemeWrapper return mLoaderManager; } mCheckedForLoaderManager = true; - mLoaderManager = getLoaderManager(-1, mLoadersStarted, true); + mLoaderManager = getLoaderManager(null, mLoadersStarted, true); return mLoaderManager; } - LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create) { + LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) { if (mAllLoaderManagers == null) { - mAllLoaderManagers = new SparseArray<LoaderManagerImpl>(); + mAllLoaderManagers = new HashMap<String, LoaderManagerImpl>(); } - LoaderManagerImpl lm = mAllLoaderManagers.get(index); + LoaderManagerImpl lm = mAllLoaderManagers.get(who); if (lm == null) { if (create) { - lm = new LoaderManagerImpl(this, started); - mAllLoaderManagers.put(index, lm); + lm = new LoaderManagerImpl(who, this, started); + mAllLoaderManagers.put(who, lm); } } else { lm.updateActivity(this); @@ -1024,7 +1033,7 @@ public class Activity extends ContextThemeWrapper if (mLoaderManager != null) { mLoaderManager.doStart(); } else if (!mCheckedForLoaderManager) { - mLoaderManager = getLoaderManager(-1, mLoadersStarted, false); + mLoaderManager = getLoaderManager(null, mLoadersStarted, false); } mCheckedForLoaderManager = true; } @@ -1600,13 +1609,17 @@ public class Activity extends ContextThemeWrapper if (mAllLoaderManagers != null) { // prune out any loader managers that were already stopped and so // have nothing useful to retain. - for (int i=mAllLoaderManagers.size()-1; i>=0; i--) { - LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i); - if (lm.mRetaining) { - retainLoaders = true; - } else { - lm.doDestroy(); - mAllLoaderManagers.removeAt(i); + LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()]; + mAllLoaderManagers.values().toArray(loaders); + if (loaders != null) { + for (int i=0; i<loaders.length; i++) { + LoaderManagerImpl lm = loaders[i]; + if (lm.mRetaining) { + retainLoaders = true; + } else { + lm.doDestroy(); + mAllLoaderManagers.remove(lm.mWho); + } } } } @@ -1642,13 +1655,13 @@ public class Activity extends ContextThemeWrapper return mFragments; } - void invalidateFragmentIndex(int index) { + void invalidateFragment(String who) { //Log.v(TAG, "invalidateFragmentIndex: index=" + index); if (mAllLoaderManagers != null) { - LoaderManagerImpl lm = mAllLoaderManagers.get(index); + LoaderManagerImpl lm = mAllLoaderManagers.get(who); if (lm != null && !lm.mRetaining) { lm.doDestroy(); - mAllLoaderManagers.remove(index); + mAllLoaderManagers.remove(who); } } } @@ -2708,7 +2721,12 @@ public class Activity extends ContextThemeWrapper // metadata is available. Intent upIntent = getParentActivityIntent(); if (upIntent != null) { - if (shouldUpRecreateTask(upIntent)) { + if (mActivityInfo.taskAffinity == null) { + // Activities with a null affinity are special; they really shouldn't + // specify a parent activity intent in the first place. Just finish + // the current activity and call it a day. + finish(); + } else if (shouldUpRecreateTask(upIntent)) { TaskStackBuilder b = TaskStackBuilder.create(this); onCreateNavigateUpTaskStack(b); onPrepareNavigateUpTaskStack(b); @@ -3379,6 +3397,31 @@ public class Activity extends ContextThemeWrapper } /** + * @hide Implement to provide correct calling token. + */ + public void startActivityAsUser(Intent intent, UserHandle user) { + startActivityAsUser(intent, null, user); + } + + /** + * @hide Implement to provide correct calling token. + */ + public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { + if (mParent != null) { + throw new RuntimeException("Called be called from a child"); + } + Instrumentation.ActivityResult ar = + mInstrumentation.execStartActivity( + this, mMainThread.getApplicationThread(), mToken, this, + intent, -1, options, user); + if (ar != null) { + mMainThread.sendActivityResult( + mToken, mEmbeddedID, -1, ar.getResultCode(), + ar.getResultData()); + } + } + + /** * Same as calling {@link #startIntentSenderForResult(IntentSender, int, * Intent, int, int, int, Bundle)} with no options. * @@ -4247,7 +4290,8 @@ public class Activity extends ContextThemeWrapper ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, mParent == null ? mToken : mParent.mToken, - mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null); + mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null, + UserHandle.myUserId()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { // Empty @@ -4707,6 +4751,10 @@ public class Activity extends ContextThemeWrapper * @param args additional arguments to the dump request. */ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + dumpInner(prefix, fd, writer, args); + } + + void dumpInner(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { writer.print(prefix); writer.print("Local Activity "); writer.print(Integer.toHexString(System.identityHashCode(this))); writer.println(" State:"); @@ -4728,6 +4776,29 @@ public class Activity extends ContextThemeWrapper mLoaderManager.dump(prefix + " ", fd, writer, args); } mFragments.dump(prefix, fd, writer, args); + writer.print(prefix); writer.println("View Hierarchy:"); + dumpViewHierarchy(prefix + " ", writer, getWindow().getDecorView()); + } + + private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) { + writer.print(prefix); + if (view == null) { + writer.println("null"); + return; + } + writer.println(view.toString()); + if (!(view instanceof ViewGroup)) { + return; + } + ViewGroup grp = (ViewGroup)view; + final int N = grp.getChildCount(); + if (N <= 0) { + return; + } + prefix = prefix + " "; + for (int i=0; i<N; i++) { + dumpViewHierarchy(prefix, writer, grp.getChildAt(i)); + } } /** @@ -4939,7 +5010,21 @@ public class Activity extends ContextThemeWrapper if (TextUtils.isEmpty(parentName)) { return null; } - return new Intent().setClassName(this, parentName); + + // If the parent itself has no parent, generate a main activity intent. + final ComponentName target = new ComponentName(this, parentName); + try { + final ActivityInfo parentInfo = getPackageManager().getActivityInfo(target, 0); + final String parentActivity = parentInfo.parentActivityName; + final Intent parentIntent = parentActivity == null + ? Intent.makeMainActivity(target) + : new Intent().setComponent(target); + return parentIntent; + } catch (NameNotFoundException e) { + Log.e(TAG, "getParentActivityIntent: bad parentActivityName '" + parentName + + "' in manifest"); + return null; + } } // ------------------ Internal API ------------------ @@ -4964,7 +5049,7 @@ public class Activity extends ContextThemeWrapper Configuration config) { attachBaseContext(context); - mFragments.attachActivity(this); + mFragments.attachActivity(this, mContainer, null); mWindow = PolicyManager.makeNewWindow(this); mWindow.setCallback(this); @@ -4990,7 +5075,9 @@ public class Activity extends ContextThemeWrapper mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; - mWindow.setWindowManager(null, mToken, mComponent.flattenToString(), + mWindow.setWindowManager( + (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), + mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); @@ -5023,10 +5110,14 @@ public class Activity extends ContextThemeWrapper } mFragments.dispatchStart(); if (mAllLoaderManagers != null) { - for (int i=mAllLoaderManagers.size()-1; i>=0; i--) { - LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i); - lm.finishRetain(); - lm.doReportStart(); + LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()]; + mAllLoaderManagers.values().toArray(loaders); + if (loaders != null) { + for (int i=0; i<loaders.length; i++) { + LoaderManagerImpl lm = loaders[i]; + lm.finishRetain(); + lm.doReportStart(); + } } } } @@ -5037,7 +5128,7 @@ public class Activity extends ContextThemeWrapper if (mStopped) { mStopped = false; if (mToken != null && mParent == null) { - WindowManagerImpl.getDefault().setStoppedState(mToken, false); + WindowManagerGlobal.getInstance().setStoppedState(mToken, false); } synchronized (mManagedCursors) { @@ -5137,7 +5228,7 @@ public class Activity extends ContextThemeWrapper } if (mToken != null && mParent == null) { - WindowManagerImpl.getDefault().setStoppedState(mToken, true); + WindowManagerGlobal.getInstance().setStoppedState(mToken, true); } mFragments.dispatchStop(); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 92b6f72..0eda6b4 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -27,9 +27,12 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -40,7 +43,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; -import android.os.UserId; +import android.os.UserHandle; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; @@ -209,6 +212,15 @@ public class ActivityManager { */ public static final int INTENT_SENDER_SERVICE = 4; + /** @hide User operation call: success! */ + public static final int USER_OP_SUCCESS = 0; + + /** @hide User operation call: given user id is not known. */ + public static final int USER_OP_UNKNOWN_USER = -1; + + /** @hide User operation call: given user id is the current user, can't be stopped. */ + public static final int USER_OP_IS_CURRENT = -2; + /*package*/ ActivityManager(Context context, Handler handler) { mContext = context; mHandler = handler; @@ -366,7 +378,7 @@ public class ActivityManager { * (which tends to consume a lot more RAM). * @hide */ - static public boolean isHighEndGfx(Display display) { + static public boolean isHighEndGfx() { MemInfoReader reader = new MemInfoReader(); reader.readMemInfo(); if (reader.getTotalSize() >= (512*1024*1024)) { @@ -374,6 +386,9 @@ public class ActivityManager { // we can afford the overhead of graphics acceleration. return true; } + + Display display = DisplayManagerGlobal.getInstance().getRealDisplay( + Display.DEFAULT_DISPLAY); Point p = new Point(); display.getRealSize(p); int pixels = p.x * p.y; @@ -529,7 +544,36 @@ public class ActivityManager { throws SecurityException { try { return ActivityManagerNative.getDefault().getRecentTasks(maxNum, - flags); + flags, UserHandle.myUserId()); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Same as {@link #getRecentTasks(int, int)} but returns the recent tasks for a + * specific user. It requires holding + * the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many tasks the + * user has started and the maximum number the system can remember. + * @param flags Information about what to return. May be any combination + * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}. + * + * @return Returns a list of RecentTaskInfo records describing each of + * the recent tasks. + * + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#GET_TASKS} or the + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permissions. + * @hide + */ + public List<RecentTaskInfo> getRecentTasksForUser(int maxNum, int flags, int userId) + throws SecurityException { + try { + return ActivityManagerNative.getDefault().getRecentTasks(maxNum, + flags, userId); } catch (RemoteException e) { // System dead, we will be dead too soon! return null; @@ -820,7 +864,17 @@ public class ActivityManager { return null; } } - + + /** @hide */ + public Bitmap getTaskTopThumbnail(int id) throws SecurityException { + try { + return ActivityManagerNative.getDefault().getTaskTopThumbnail(id); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + /** * Flag for {@link #moveTaskToFront(int, int)}: also move the "home" * activity along with the task, so it is positioned immediately behind @@ -1182,7 +1236,7 @@ public class ActivityManager { public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) { try { return ActivityManagerNative.getDefault().clearApplicationUserData(packageName, - observer, Binder.getOrigCallingUser()); + observer, UserHandle.myUserId()); } catch (RemoteException e) { return false; } @@ -1346,6 +1400,13 @@ public class ActivityManager { public static final int FLAG_PERSISTENT = 1<<1; /** + * Constant for {@link #flags}: this process is associated with a + * persistent system app. + * @hide + */ + public static final int FLAG_HAS_ACTIVITIES = 1<<2; + + /** * Flags of information. May be any of * {@link #FLAG_CANT_SAVE_STATE}. * @hide @@ -1642,7 +1703,8 @@ public class ActivityManager { */ public void killBackgroundProcesses(String packageName) { try { - ActivityManagerNative.getDefault().killBackgroundProcesses(packageName); + ActivityManagerNative.getDefault().killBackgroundProcesses(packageName, + UserHandle.myUserId()); } catch (RemoteException e) { } } @@ -1667,7 +1729,8 @@ public class ActivityManager { */ public void forceStopPackage(String packageName) { try { - ActivityManagerNative.getDefault().forceStopPackage(packageName); + ActivityManagerNative.getDefault().forceStopPackage(packageName, + UserHandle.myUserId()); } catch (RemoteException e) { } } @@ -1814,12 +1877,12 @@ public class ActivityManager { return PackageManager.PERMISSION_GRANTED; } // Isolated processes don't get any permissions. - if (UserId.isIsolated(uid)) { + if (UserHandle.isIsolated(uid)) { return PackageManager.PERMISSION_DENIED; } // If there is a uid that owns whatever is being accessed, it has // blanket access to it regardless of the permissions it requires. - if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) { + if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) { return PackageManager.PERMISSION_GRANTED; } // If the target is not exported, then nobody else can get to it. @@ -1840,6 +1903,43 @@ public class ActivityManager { return PackageManager.PERMISSION_DENIED; } + /** @hide */ + public static int checkUidPermission(String permission, int uid) { + try { + return AppGlobals.getPackageManager() + .checkUidPermission(permission, uid); + } catch (RemoteException e) { + // Should never happen, but if it does... deny! + Slog.e(TAG, "PackageManager is dead?!?", e); + } + return PackageManager.PERMISSION_DENIED; + } + + /** @hide */ + public static int handleIncomingUser(int callingPid, int callingUid, int userId, + boolean allowAll, boolean requireFull, String name, String callerPackage) { + if (UserHandle.getUserId(callingUid) == userId) { + return userId; + } + try { + return ActivityManagerNative.getDefault().handleIncomingUser(callingPid, + callingUid, userId, allowAll, requireFull, name, callerPackage); + } catch (RemoteException e) { + throw new SecurityException("Failed calling activity manager", e); + } + } + + /** @hide */ + public static int getCurrentUser() { + UserInfo ui; + try { + ui = ActivityManagerNative.getDefault().getCurrentUser(); + return ui != null ? ui.id : 0; + } catch (RemoteException e) { + return 0; + } + } + /** * Returns the usage statistics of each installed package. * @@ -1869,4 +1969,21 @@ public class ActivityManager { return false; } } + + /** + * Return whether the given user is actively running. This means that + * the user is in the "started" state, not "stopped" -- it is currently + * allowed to run code through scheduled alarms, receiving broadcasts, + * etc. A started user may be either the current foreground user or a + * background user; the result here does not distinguish between the two. + * @param userid the user's id. Zero indicates the default user. + * @hide + */ + public boolean isUserRunning(int userid) { + try { + return ActivityManagerNative.getDefault().isUserRunning(userid); + } catch (RemoteException e) { + return false; + } + } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index e12fa19..83acb4d 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -39,6 +39,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.Singleton; @@ -88,11 +89,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM * Convenience for sending a sticky broadcast. For internal use only. * If you don't care about permission, use null. */ - static public void broadcastStickyIntent(Intent intent, String permission) { + static public void broadcastStickyIntent(Intent intent, String permission, int userId) { try { getDefault().broadcastIntent( null, intent, null, null, Activity.RESULT_OK, null, null, - null /*permission*/, false, true, Binder.getOrigCallingUser()); + null /*permission*/, false, true, userId); } catch (RemoteException ex) { } } @@ -135,6 +136,31 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case START_ACTIVITY_AS_USER_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + Intent intent = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + IBinder resultTo = data.readStrongBinder(); + String resultWho = data.readString(); + int requestCode = data.readInt(); + int startFlags = data.readInt(); + String profileFile = data.readString(); + ParcelFileDescriptor profileFd = data.readInt() != 0 + ? data.readFileDescriptor() : null; + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; + int userId = data.readInt(); + int result = startActivityAsUser(app, intent, resolvedType, + resultTo, resultWho, requestCode, startFlags, + profileFile, profileFd, options, userId); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + case START_ACTIVITY_AND_WAIT_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); @@ -151,9 +177,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM ? data.readFileDescriptor() : null; Bundle options = data.readInt() != 0 ? Bundle.CREATOR.createFromParcel(data) : null; + int userId = data.readInt(); WaitResult result = startActivityAndWait(app, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, - profileFile, profileFd, options); + profileFile, profileFd, options, userId); reply.writeNoException(); result.writeToParcel(reply, 0); return true; @@ -173,8 +200,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM Configuration config = Configuration.CREATOR.createFromParcel(data); Bundle options = data.readInt() != 0 ? Bundle.CREATOR.createFromParcel(data) : null; + int userId = data.readInt(); int result = startActivityWithConfig(app, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, config, options); + resultTo, resultWho, requestCode, startFlags, config, options, userId); reply.writeNoException(); reply.writeInt(result); return true; @@ -273,7 +301,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM = b != null ? IIntentReceiver.Stub.asInterface(b) : null; IntentFilter filter = IntentFilter.CREATOR.createFromParcel(data); String perm = data.readString(); - Intent intent = registerReceiver(app, packageName, rec, filter, perm); + int userId = data.readInt(); + Intent intent = registerReceiver(app, packageName, rec, filter, perm, userId); reply.writeNoException(); if (intent != null) { reply.writeInt(1); @@ -375,6 +404,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case ACTIVITY_RESUMED_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + activityResumed(token); + reply.writeNoException(); + return true; + } + case ACTIVITY_PAUSED_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -454,13 +491,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); int maxNum = data.readInt(); int fl = data.readInt(); + int userId = data.readInt(); List<ActivityManager.RecentTaskInfo> list = getRecentTasks(maxNum, - fl); + fl, userId); reply.writeNoException(); reply.writeTypedList(list); return true; } - + case GET_TASK_THUMBNAILS_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int id = data.readInt(); @@ -474,7 +512,21 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM } return true; } - + + case GET_TASK_TOP_THUMBNAIL_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int id = data.readInt(); + Bitmap bm = getTaskTopThumbnail(id); + reply.writeNoException(); + if (bm != null) { + reply.writeInt(1); + bm.writeToParcel(reply, 0); + } else { + reply.writeInt(0); + } + return true; + } + case GET_SERVICES_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int maxNum = data.readInt(); @@ -580,8 +632,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IBinder b = data.readStrongBinder(); IApplicationThread app = ApplicationThreadNative.asInterface(b); String name = data.readString(); + int userId = data.readInt(); boolean stable = data.readInt() != 0; - ContentProviderHolder cph = getContentProvider(app, name, stable); + ContentProviderHolder cph = getContentProvider(app, name, userId, stable); reply.writeNoException(); if (cph != null) { reply.writeInt(1); @@ -595,8 +648,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String name = data.readString(); + int userId = data.readInt(); IBinder token = data.readStrongBinder(); - ContentProviderHolder cph = getContentProviderExternal(name, token); + ContentProviderHolder cph = getContentProviderExternal(name, userId, token); reply.writeNoException(); if (cph != null) { reply.writeInt(1); @@ -670,7 +724,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IApplicationThread app = ApplicationThreadNative.asInterface(b); Intent service = Intent.CREATOR.createFromParcel(data); String resolvedType = data.readString(); - ComponentName cn = startService(app, service, resolvedType); + int userId = data.readInt(); + ComponentName cn = startService(app, service, resolvedType, userId); reply.writeNoException(); ComponentName.writeToParcel(cn, reply); return true; @@ -682,7 +737,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IApplicationThread app = ApplicationThreadNative.asInterface(b); Intent service = Intent.CREATOR.createFromParcel(data); String resolvedType = data.readString(); - int res = stopService(app, service, resolvedType); + int userId = data.readInt(); + int res = stopService(app, service, resolvedType, userId); reply.writeNoException(); reply.writeInt(res); return true; @@ -780,7 +836,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM Bundle arguments = data.readBundle(); IBinder b = data.readStrongBinder(); IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b); - boolean res = startInstrumentation(className, profileFile, fl, arguments, w); + int userId = data.readInt(); + boolean res = startInstrumentation(className, profileFile, fl, arguments, w, userId); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; @@ -868,9 +925,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM int fl = data.readInt(); Bundle options = data.readInt() != 0 ? Bundle.CREATOR.createFromParcel(data) : null; + int userId = data.readInt(); IIntentSender res = getIntentSender(type, packageName, token, resultWho, requestCode, requestIntents, - requestResolvedTypes, fl, options); + requestResolvedTypes, fl, options, userId); reply.writeNoException(); reply.writeStrongBinder(res != null ? res.asBinder() : null); return true; @@ -905,6 +963,22 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case HANDLE_INCOMING_USER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int callingPid = data.readInt(); + int callingUid = data.readInt(); + int userId = data.readInt(); + boolean allowAll = data.readInt() != 0 ; + boolean requireFull = data.readInt() != 0; + String name = data.readString(); + String callerPackage = data.readString(); + int res = handleIncomingUser(callingPid, callingUid, userId, allowAll, + requireFull, name, callerPackage); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + case SET_PROCESS_LIMIT_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int max = data.readInt(); @@ -1164,7 +1238,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case KILL_BACKGROUND_PROCESSES_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String packageName = data.readString(); - killBackgroundProcesses(packageName); + int userId = data.readInt(); + killBackgroundProcesses(packageName, userId); reply.writeNoException(); return true; } @@ -1179,7 +1254,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case FORCE_STOP_PACKAGE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String packageName = data.readString(); - forceStopPackage(packageName); + int userId = data.readInt(); + forceStopPackage(packageName, userId); reply.writeNoException(); return true; } @@ -1205,12 +1281,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case PROFILE_CONTROL_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String process = data.readString(); + int userId = data.readInt(); boolean start = data.readInt() != 0; int profileType = data.readInt(); String path = data.readString(); ParcelFileDescriptor fd = data.readInt() != 0 ? data.readFileDescriptor() : null; - boolean res = profileControl(process, start, path, fd, profileType); + boolean res = profileControl(process, userId, start, path, fd, profileType); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; @@ -1275,30 +1352,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case START_ACTIVITY_IN_PACKAGE_TRANSACTION: - { - data.enforceInterface(IActivityManager.descriptor); - int uid = data.readInt(); - Intent intent = Intent.CREATOR.createFromParcel(data); - String resolvedType = data.readString(); - IBinder resultTo = data.readStrongBinder(); - String resultWho = data.readString(); - int requestCode = data.readInt(); - int startFlags = data.readInt(); - Bundle options = data.readInt() != 0 - ? Bundle.CREATOR.createFromParcel(data) : null; - int result = startActivityInPackage(uid, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, options); - reply.writeNoException(); - reply.writeInt(result); - return true; - } - - case KILL_APPLICATION_WITH_UID_TRANSACTION: { + case KILL_APPLICATION_WITH_APPID_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String pkg = data.readString(); - int uid = data.readInt(); - killApplicationWithUid(pkg, uid); + int appid = data.readInt(); + killApplicationWithAppId(pkg, appid); reply.writeNoException(); return true; } @@ -1395,7 +1453,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case GET_PROVIDER_MIME_TYPE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); Uri uri = Uri.CREATOR.createFromParcel(data); - String type = getProviderMimeType(uri); + int userId = data.readInt(); + String type = getProviderMimeType(uri, userId); reply.writeNoException(); reply.writeString(type); return true; @@ -1450,32 +1509,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case DUMP_HEAP_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String process = data.readString(); + int userId = data.readInt(); boolean managed = data.readInt() != 0; String path = data.readString(); ParcelFileDescriptor fd = data.readInt() != 0 ? data.readFileDescriptor() : null; - boolean res = dumpHeap(process, managed, path, fd); + boolean res = dumpHeap(process, userId, managed, path, fd); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; } - case START_ACTIVITIES_IN_PACKAGE_TRANSACTION: - { - data.enforceInterface(IActivityManager.descriptor); - int uid = data.readInt(); - Intent[] intents = data.createTypedArray(Intent.CREATOR); - String[] resolvedTypes = data.createStringArray(); - IBinder resultTo = data.readStrongBinder(); - Bundle options = data.readInt() != 0 - ? Bundle.CREATOR.createFromParcel(data) : null; - int result = startActivitiesInPackage(uid, intents, resolvedTypes, - resultTo, options); - reply.writeNoException(); - reply.writeInt(result); - return true; - } - case START_ACTIVITIES_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); @@ -1486,8 +1530,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IBinder resultTo = data.readStrongBinder(); Bundle options = data.readInt() != 0 ? Bundle.CREATOR.createFromParcel(data) : null; + int userId = data.readInt(); int result = startActivities(app, intents, resolvedTypes, resultTo, - options); + options, userId); reply.writeNoException(); reply.writeInt(result); return true; @@ -1541,6 +1586,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case STOP_USER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int userid = data.readInt(); + IStopUserCallback callback = IStopUserCallback.Stub.asInterface( + data.readStrongBinder()); + int result = stopUser(userid, callback); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + case GET_CURRENT_USER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); UserInfo userInfo = getCurrentUser(); @@ -1549,6 +1605,23 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case IS_USER_RUNNING_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int userid = data.readInt(); + boolean result = isUserRunning(userid); + reply.writeNoException(); + reply.writeInt(result ? 1 : 0); + return true; + } + + case GET_RUNNING_USER_IDS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int[] result = getRunningUserIds(); + reply.writeNoException(); + reply.writeIntArray(result); + return true; + } + case REMOVE_SUB_TASK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); @@ -1694,6 +1767,28 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case REGISTER_USER_SWITCH_OBSERVER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface( + data.readStrongBinder()); + registerUserSwitchObserver(observer); + return true; + } + + case UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface( + data.readStrongBinder()); + unregisterUserSwitchObserver(observer); + return true; + } + + case REQUEST_BUG_REPORT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + requestBugReport(); + return true; + } + } return super.onTransact(code, data, reply, flags); @@ -1764,10 +1859,46 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); return result; } + + public int startActivityAsUser(IApplicationThread caller, Intent intent, + String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + intent.writeToParcel(data, 0); + data.writeString(resolvedType); + data.writeStrongBinder(resultTo); + data.writeString(resultWho); + data.writeInt(requestCode); + data.writeInt(startFlags); + data.writeString(profileFile); + if (profileFd != null) { + data.writeInt(1); + profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + } else { + data.writeInt(0); + } + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + data.writeInt(userId); + mRemote.transact(START_ACTIVITY_AS_USER_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, - ParcelFileDescriptor profileFd, Bundle options) throws RemoteException { + ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -1791,6 +1922,7 @@ class ActivityManagerProxy implements IActivityManager } else { data.writeInt(0); } + data.writeInt(userId); mRemote.transact(START_ACTIVITY_AND_WAIT_TRANSACTION, data, reply, 0); reply.readException(); WaitResult result = WaitResult.CREATOR.createFromParcel(reply); @@ -1801,7 +1933,7 @@ class ActivityManagerProxy implements IActivityManager public int startActivityWithConfig(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Configuration config, - Bundle options) throws RemoteException { + Bundle options, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -1819,6 +1951,7 @@ class ActivityManagerProxy implements IActivityManager } else { data.writeInt(0); } + data.writeInt(userId); mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); @@ -1939,7 +2072,7 @@ class ActivityManagerProxy implements IActivityManager } public Intent registerReceiver(IApplicationThread caller, String packageName, IIntentReceiver receiver, - IntentFilter filter, String perm) throws RemoteException + IntentFilter filter, String perm, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -1949,6 +2082,7 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(receiver != null ? receiver.asBinder() : null); filter.writeToParcel(data, 0); data.writeString(perm); + data.writeInt(userId); mRemote.transact(REGISTER_RECEIVER_TRANSACTION, data, reply, 0); reply.readException(); Intent intent = null; @@ -2057,6 +2191,17 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } + public void activityResumed(IBinder token) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(ACTIVITY_RESUMED_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } public void activityPaused(IBinder token) throws RemoteException { Parcel data = Parcel.obtain(); @@ -2163,12 +2308,13 @@ class ActivityManagerProxy implements IActivityManager return list; } public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, - int flags) throws RemoteException { + int flags, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(maxNum); data.writeInt(flags); + data.writeInt(userId); mRemote.transact(GET_RECENT_TASKS_TRANSACTION, data, reply, 0); reply.readException(); ArrayList<ActivityManager.RecentTaskInfo> list @@ -2192,6 +2338,21 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return bm; } + public Bitmap getTaskTopThumbnail(int id) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(id); + mRemote.transact(GET_TASK_TOP_THUMBNAIL_TRANSACTION, data, reply, 0); + reply.readException(); + Bitmap bm = null; + if (reply.readInt() != 0) { + bm = Bitmap.CREATOR.createFromParcel(reply); + } + data.recycle(); + reply.recycle(); + return bm; + } public List getServices(int maxNum, int flags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2343,12 +2504,13 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } public ContentProviderHolder getContentProvider(IApplicationThread caller, - String name, boolean stable) throws RemoteException { + String name, int userId, boolean stable) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); data.writeString(name); + data.writeInt(userId); data.writeInt(stable ? 1 : 0); mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); reply.readException(); @@ -2361,13 +2523,13 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return cph; } - public ContentProviderHolder getContentProviderExternal(String name, IBinder token) - throws RemoteException - { + public ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(name); + data.writeInt(userId); data.writeStrongBinder(token); mRemote.transact(GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0); reply.readException(); @@ -2459,7 +2621,7 @@ class ActivityManagerProxy implements IActivityManager } public ComponentName startService(IApplicationThread caller, Intent service, - String resolvedType) throws RemoteException + String resolvedType, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2467,6 +2629,7 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(caller != null ? caller.asBinder() : null); service.writeToParcel(data, 0); data.writeString(resolvedType); + data.writeInt(userId); mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0); reply.readException(); ComponentName res = ComponentName.readFromParcel(reply); @@ -2475,7 +2638,7 @@ class ActivityManagerProxy implements IActivityManager return res; } public int stopService(IApplicationThread caller, Intent service, - String resolvedType) throws RemoteException + String resolvedType, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2483,6 +2646,7 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(caller != null ? caller.asBinder() : null); service.writeToParcel(data, 0); data.writeString(resolvedType); + data.writeInt(userId); mRemote.transact(STOP_SERVICE_TRANSACTION, data, reply, 0); reply.readException(); int res = reply.readInt(); @@ -2654,7 +2818,7 @@ class ActivityManagerProxy implements IActivityManager } public boolean startInstrumentation(ComponentName className, String profileFile, - int flags, Bundle arguments, IInstrumentationWatcher watcher) + int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2664,6 +2828,7 @@ class ActivityManagerProxy implements IActivityManager data.writeInt(flags); data.writeBundle(arguments); data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); + data.writeInt(userId); mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0); reply.readException(); boolean res = reply.readInt() != 0; @@ -2761,7 +2926,7 @@ class ActivityManagerProxy implements IActivityManager public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, - Bundle options) throws RemoteException { + Bundle options, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -2784,6 +2949,7 @@ class ActivityManagerProxy implements IActivityManager } else { data.writeInt(0); } + data.writeInt(userId); mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0); reply.readException(); IIntentSender res = IIntentSender.Stub.asInterface( @@ -2826,6 +2992,25 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } + public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, + boolean requireFull, String name, String callerPackage) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(callingPid); + data.writeInt(callingUid); + data.writeInt(userId); + data.writeInt(allowAll ? 1 : 0); + data.writeInt(requireFull ? 1 : 0); + data.writeString(name); + data.writeString(callerPackage); + mRemote.transact(HANDLE_INCOMING_USER_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } public void setProcessLimit(int max) throws RemoteException { Parcel data = Parcel.obtain(); @@ -3165,11 +3350,12 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public void killBackgroundProcesses(String packageName) throws RemoteException { + public void killBackgroundProcesses(String packageName, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(packageName); + data.writeInt(userId); mRemote.transact(KILL_BACKGROUND_PROCESSES_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -3186,11 +3372,12 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public void forceStopPackage(String packageName) throws RemoteException { + public void forceStopPackage(String packageName, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(packageName); + data.writeInt(userId); mRemote.transact(FORCE_STOP_PACKAGE_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -3223,13 +3410,14 @@ class ActivityManagerProxy implements IActivityManager return res; } - public boolean profileControl(String process, boolean start, + public boolean profileControl(String process, int userId, boolean start, String path, ParcelFileDescriptor fd, int profileType) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(process); + data.writeInt(userId); data.writeInt(start ? 1 : 0); data.writeInt(profileType); data.writeString(path); @@ -3281,41 +3469,13 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); } - public int startActivityInPackage(int uid, - Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, Bundle options) - throws RemoteException { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - data.writeInt(uid); - intent.writeToParcel(data, 0); - data.writeString(resolvedType); - data.writeStrongBinder(resultTo); - data.writeString(resultWho); - data.writeInt(requestCode); - data.writeInt(startFlags); - if (options != null) { - data.writeInt(1); - options.writeToParcel(data, 0); - } else { - data.writeInt(0); - } - mRemote.transact(START_ACTIVITY_IN_PACKAGE_TRANSACTION, data, reply, 0); - reply.readException(); - int result = reply.readInt(); - reply.recycle(); - data.recycle(); - return result; - } - - public void killApplicationWithUid(String pkg, int uid) throws RemoteException { + public void killApplicationWithAppId(String pkg, int appid) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(pkg); - data.writeInt(uid); - mRemote.transact(KILL_APPLICATION_WITH_UID_TRANSACTION, data, reply, 0); + data.writeInt(appid); + mRemote.transact(KILL_APPLICATION_WITH_APPID_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -3450,12 +3610,12 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public String getProviderMimeType(Uri uri) - throws RemoteException { + public String getProviderMimeType(Uri uri, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); uri.writeToParcel(data, 0); + data.writeInt(userId); mRemote.transact(GET_PROVIDER_MIME_TYPE_TRANSACTION, data, reply, 0); reply.readException(); String res = reply.readString(); @@ -3530,12 +3690,13 @@ class ActivityManagerProxy implements IActivityManager return res; } - public boolean dumpHeap(String process, boolean managed, + public boolean dumpHeap(String process, int userId, boolean managed, String path, ParcelFileDescriptor fd) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(process); + data.writeInt(userId); data.writeInt(managed ? 1 : 0); data.writeString(path); if (fd != null) { @@ -3554,7 +3715,7 @@ class ActivityManagerProxy implements IActivityManager public int startActivities(IApplicationThread caller, Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle options) throws RemoteException { + Bundle options, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3568,6 +3729,7 @@ class ActivityManagerProxy implements IActivityManager } else { data.writeInt(0); } + data.writeInt(userId); mRemote.transact(START_ACTIVITIES_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); @@ -3576,30 +3738,6 @@ class ActivityManagerProxy implements IActivityManager return result; } - public int startActivitiesInPackage(int uid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle options) throws RemoteException { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - data.writeInt(uid); - data.writeTypedArray(intents, 0); - data.writeStringArray(resolvedTypes); - data.writeStrongBinder(resultTo); - if (options != null) { - data.writeInt(1); - options.writeToParcel(data, 0); - } else { - data.writeInt(0); - } - mRemote.transact(START_ACTIVITIES_IN_PACKAGE_TRANSACTION, data, reply, 0); - reply.readException(); - int result = reply.readInt(); - reply.recycle(); - data.recycle(); - return result; - } - public int getFrontActivityScreenCompatMode() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -3688,11 +3826,25 @@ class ActivityManagerProxy implements IActivityManager return result; } + public int stopUser(int userid, IStopUserCallback callback) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(userid); + data.writeStrongInterface(callback); + mRemote.transact(STOP_USER_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + public UserInfo getCurrentUser() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); - mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0); + mRemote.transact(GET_CURRENT_USER_TRANSACTION, data, reply, 0); reply.readException(); UserInfo userInfo = UserInfo.CREATOR.createFromParcel(reply); reply.recycle(); @@ -3700,6 +3852,31 @@ class ActivityManagerProxy implements IActivityManager return userInfo; } + public boolean isUserRunning(int userid) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(userid); + mRemote.transact(IS_USER_RUNNING_TRANSACTION, data, reply, 0); + reply.readException(); + boolean result = reply.readInt() != 0; + reply.recycle(); + data.recycle(); + return result; + } + + public int[] getRunningUserIds() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_RUNNING_USER_IDS_TRANSACTION, data, reply, 0); + reply.readException(); + int[] result = reply.createIntArray(); + reply.recycle(); + data.recycle(); + return result; + } + public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -3873,5 +4050,37 @@ class ActivityManagerProxy implements IActivityManager return result; } + public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(observer != null ? observer.asBinder() : null); + mRemote.transact(REGISTER_USER_SWITCH_OBSERVER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(observer != null ? observer.asBinder() : null); + mRemote.transact(UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void requestBugReport() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(REQUEST_BUG_REPORT_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4edfdfb..87b1e24 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -97,9 +97,9 @@ public class ActivityOptions { /** @hide */ public static final int ANIM_SCALE_UP = 2; /** @hide */ - public static final int ANIM_THUMBNAIL = 3; + public static final int ANIM_THUMBNAIL_SCALE_UP = 3; /** @hide */ - public static final int ANIM_THUMBNAIL_DELAYED = 4; + public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4; private String mPackageName; private int mAnimationType = ANIM_NONE; @@ -262,20 +262,19 @@ public class ActivityOptions { */ public static ActivityOptions makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { - return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, listener, false); + return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, true); } /** - * Create an ActivityOptions specifying an animation where a thumbnail - * is scaled from a given position to the new activity window that is - * being started. Before the animation, there is a short delay. + * Create an ActivityOptions specifying an animation where an activity window + * is scaled from a given position to a thumbnail at a specified location. * - * @param source The View that this thumbnail is animating from. This + * @param source The View that this thumbnail is animating to. This * defines the coordinate space for <var>startX</var> and <var>startY</var>. - * @param thumbnail The bitmap that will be shown as the initial thumbnail + * @param thumbnail The bitmap that will be shown as the final thumbnail * of the animation. - * @param startX The x starting location of the bitmap, relative to <var>source</var>. - * @param startY The y starting location of the bitmap, relative to <var>source</var>. + * @param startX The x end location of the bitmap, relative to <var>source</var>. + * @param startY The y end location of the bitmap, relative to <var>source</var>. * @param listener Optional OnAnimationStartedListener to find out when the * requested animation has started running. If for some reason the animation * is not executed, the callback will happen immediately. @@ -283,17 +282,17 @@ public class ActivityOptions { * supply these options as the options Bundle when starting an activity. * @hide */ - public static ActivityOptions makeDelayedThumbnailScaleUpAnimation(View source, + public static ActivityOptions makeThumbnailScaleDownAnimation(View source, Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { - return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, listener, true); + return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, false); } - private static ActivityOptions makeThumbnailScaleUpAnimation(View source, + private static ActivityOptions makeThumbnailAnimation(View source, Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener, - boolean delayed) { + boolean scaleUp) { ActivityOptions opts = new ActivityOptions(); opts.mPackageName = source.getContext().getPackageName(); - opts.mAnimationType = delayed ? ANIM_THUMBNAIL_DELAYED : ANIM_THUMBNAIL; + opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN; opts.mThumbnail = thumbnail; int[] pts = new int[2]; source.getLocationOnScreen(pts); @@ -320,8 +319,8 @@ public class ActivityOptions { mStartY = opts.getInt(KEY_ANIM_START_Y, 0); mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0); mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0); - } else if (mAnimationType == ANIM_THUMBNAIL || - mAnimationType == ANIM_THUMBNAIL_DELAYED) { + } else if (mAnimationType == ANIM_THUMBNAIL_SCALE_UP || + mAnimationType == ANIM_THUMBNAIL_SCALE_DOWN) { mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL); mStartX = opts.getInt(KEY_ANIM_START_X, 0); mStartY = opts.getInt(KEY_ANIM_START_Y, 0); @@ -434,8 +433,8 @@ public class ActivityOptions { } mAnimationStartedListener = null; break; - case ANIM_THUMBNAIL: - case ANIM_THUMBNAIL_DELAYED: + case ANIM_THUMBNAIL_SCALE_UP: + case ANIM_THUMBNAIL_SCALE_DOWN: mAnimationType = otherOptions.mAnimationType; mThumbnail = otherOptions.mThumbnail; mStartX = otherOptions.mStartX; @@ -479,8 +478,8 @@ public class ActivityOptions { b.putInt(KEY_ANIM_START_WIDTH, mStartWidth); b.putInt(KEY_ANIM_START_HEIGHT, mStartHeight); break; - case ANIM_THUMBNAIL: - case ANIM_THUMBNAIL_DELAYED: + case ANIM_THUMBNAIL_SCALE_UP: + case ANIM_THUMBNAIL_SCALE_DOWN: b.putInt(KEY_ANIM_TYPE, mAnimationType); b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail); b.putInt(KEY_ANIM_START_X, mStartX); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b7e0683..ef9f6d4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -42,6 +42,8 @@ import android.database.sqlite.SQLiteDebug; import android.database.sqlite.SQLiteDebug.DbStats; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; import android.net.IConnectivityManager; import android.net.Proxy; import android.net.ProxyProperties; @@ -50,6 +52,8 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.Debug; +import android.os.DropBoxManager; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -61,8 +65,9 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.Trace; -import android.os.UserId; +import android.os.UserHandle; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.EventLog; @@ -70,6 +75,7 @@ import android.util.Log; import android.util.LogPrinter; import android.util.PrintWriterPrinter; import android.util.Slog; +import android.view.CompatibilityInfoHolder; import android.view.Display; import android.view.HardwareRenderer; import android.view.View; @@ -78,12 +84,13 @@ import android.view.ViewManager; import android.view.ViewRootImpl; import android.view.Window; import android.view.WindowManager; -import android.view.WindowManagerImpl; +import android.view.WindowManagerGlobal; import android.renderscript.RenderScript; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.os.SamplingProfilerIntegration; +import com.android.internal.util.Objects; import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; @@ -94,6 +101,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.net.InetAddress; +import java.security.Security; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -103,6 +111,8 @@ import java.util.Map; import java.util.TimeZone; import java.util.regex.Pattern; +import libcore.io.DropBox; +import libcore.io.EventLogger; import libcore.io.IoUtils; import dalvik.system.CloseGuard; @@ -165,6 +175,8 @@ public final class ActivityThread { = new HashMap<IBinder, Service>(); AppBindData mBoundApplication; Profiler mProfiler; + int mCurDefaultDisplayDpi; + boolean mDensityCompatMode; Configuration mConfiguration; Configuration mCompatConfiguration; Configuration mResConfiguration; @@ -196,7 +208,7 @@ public final class ActivityThread { = new HashMap<String, WeakReference<LoadedApk>>(); final HashMap<String, WeakReference<LoadedApk>> mResourcePackages = new HashMap<String, WeakReference<LoadedApk>>(); - final HashMap<CompatibilityInfo, DisplayMetrics> mDisplayMetrics + final HashMap<CompatibilityInfo, DisplayMetrics> mDefaultDisplayMetrics = new HashMap<CompatibilityInfo, DisplayMetrics>(); final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources = new HashMap<ResourcesKey, WeakReference<Resources> >(); @@ -204,9 +216,33 @@ public final class ActivityThread { = new ArrayList<ActivityClientRecord>(); Configuration mPendingConfiguration = null; + private static final class ProviderKey { + final String authority; + final int userId; + + public ProviderKey(String authority, int userId) { + this.authority = authority; + this.userId = userId; + } + + @Override + public boolean equals(Object o) { + if (o instanceof ProviderKey) { + final ProviderKey other = (ProviderKey) o; + return Objects.equal(authority, other.authority) && userId == other.userId; + } + return false; + } + + @Override + public int hashCode() { + return ((authority != null) ? authority.hashCode() : 0) ^ userId; + } + } + // The lock of mProviderMap protects the following variables. - final HashMap<String, ProviderClientRecord> mProviderMap - = new HashMap<String, ProviderClientRecord>(); + final HashMap<ProviderKey, ProviderClientRecord> mProviderMap + = new HashMap<ProviderKey, ProviderClientRecord>(); final HashMap<IBinder, ProviderRefCount> mProviderRefCountMap = new HashMap<IBinder, ProviderRefCount>(); final HashMap<IBinder, ProviderClientRecord> mLocalProviders @@ -313,8 +349,9 @@ public final class ActivityThread { static final class ReceiverData extends BroadcastReceiver.PendingResult { public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras, - boolean ordered, boolean sticky, IBinder token) { - super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, token); + boolean ordered, boolean sticky, IBinder token, int sendingUser) { + super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, + token, sendingUser); this.intent = intent; } @@ -606,9 +643,9 @@ public final class ActivityThread { public final void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, - boolean sync) { + boolean sync, int sendingUser) { ReceiverData r = new ReceiverData(intent, resultCode, data, extras, - sync, false, mAppThread.asBinder()); + sync, false, mAppThread.asBinder(), sendingUser); r.info = info; r.compatInfo = compatInfo; queueOrSendMessage(H.RECEIVER, r); @@ -767,8 +804,9 @@ public final class ActivityThread { // applies transaction ordering per object for such calls. public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode, String dataStr, Bundle extras, boolean ordered, - boolean sticky) throws RemoteException { - receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky); + boolean sticky, int sendingUser) throws RemoteException { + receiver.performReceive(intent, resultCode, dataStr, extras, ordered, + sticky, sendingUser); } public void scheduleLowMemory() { @@ -1052,7 +1090,7 @@ public final class ActivityThread { @Override public void dumpGfxInfo(FileDescriptor fd, String[] args) { dumpGraphicsInfo(fd); - WindowManagerImpl.getDefault().dumpGfxInfo(fd); + WindowManagerGlobal.getInstance().dumpGfxInfo(fd); } @Override @@ -1235,7 +1273,7 @@ public final class ActivityThread { case RESUME_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); handleResumeActivity((IBinder)msg.obj, true, - msg.arg1 != 0); + msg.arg1 != 0, true); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case SEND_RESULT: @@ -1305,6 +1343,7 @@ public final class ActivityThread { break; case CONFIGURATION_CHANGED: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); + mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi; handleConfigurationChanged((Configuration)msg.obj, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; @@ -1467,13 +1506,28 @@ public final class ActivityThread { private static class ResourcesKey { final private String mResDir; + final private int mDisplayId; + final private Configuration mOverrideConfiguration; final private float mScale; final private int mHash; - ResourcesKey(String resDir, float scale) { + ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) { mResDir = resDir; + mDisplayId = displayId; + if (overrideConfiguration != null) { + if (Configuration.EMPTY.equals(overrideConfiguration)) { + overrideConfiguration = null; + } + } + mOverrideConfiguration = overrideConfiguration; mScale = scale; - mHash = mResDir.hashCode() << 2 + (int) (mScale * 2); + int hash = 17; + hash = 31 * hash + mResDir.hashCode(); + hash = 31 * hash + mDisplayId; + hash = 31 * hash + (mOverrideConfiguration != null + ? mOverrideConfiguration.hashCode() : 0); + hash = 31 * hash + Float.floatToIntBits(mScale); + mHash = hash; } @Override @@ -1487,7 +1541,24 @@ public final class ActivityThread { return false; } ResourcesKey peer = (ResourcesKey) obj; - return mResDir.equals(peer.mResDir) && mScale == peer.mScale; + if (!mResDir.equals(peer.mResDir)) { + return false; + } + if (mDisplayId != peer.mDisplayId) { + return false; + } + if (mOverrideConfiguration != peer.mOverrideConfiguration) { + if (mOverrideConfiguration == null || peer.mOverrideConfiguration == null) { + return false; + } + if (!mOverrideConfiguration.equals(peer.mOverrideConfiguration)) { + return false; + } + } + if (mScale != peer.mScale) { + return false; + } + return true; } } @@ -1518,17 +1589,41 @@ public final class ActivityThread { return sPackageManager; } - DisplayMetrics getDisplayMetricsLocked(CompatibilityInfo ci, boolean forceUpdate) { - DisplayMetrics dm = mDisplayMetrics.get(ci); - if (dm != null && !forceUpdate) { + private void flushDisplayMetricsLocked() { + mDefaultDisplayMetrics.clear(); + } + + DisplayMetrics getDisplayMetricsLocked(int displayId, CompatibilityInfo ci) { + boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(ci) : null; + if (dm != null) { + return dm; + } + dm = new DisplayMetrics(); + + DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance(); + if (displayManager == null) { + // may be null early in system startup + dm.setToDefaults(); return dm; } - if (dm == null) { - dm = new DisplayMetrics(); - mDisplayMetrics.put(ci, dm); + + if (isDefaultDisplay) { + mDefaultDisplayMetrics.put(ci, dm); + } + + CompatibilityInfoHolder cih = new CompatibilityInfoHolder(); + cih.set(ci); + Display d = displayManager.getCompatibleDisplay(displayId, cih); + if (d != null) { + d.getMetrics(dm); + } else { + // Display no longer exists + // FIXME: This would not be a problem if we kept the Display object around + // instead of using the raw display id everywhere. The Display object caches + // its information even after the display has been removed. + dm.setToDefaults(); } - Display d = WindowManagerImpl.getDefault(ci).getDefaultDisplay(); - d.getMetrics(dm); //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h=" // + metrics.heightPixels + " den=" + metrics.density // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi); @@ -1536,14 +1631,15 @@ public final class ActivityThread { } private Configuration mMainThreadConfig = new Configuration(); - Configuration applyConfigCompatMainThread(Configuration config, CompatibilityInfo compat) { + Configuration applyConfigCompatMainThread(int displayDensity, Configuration config, + CompatibilityInfo compat) { if (config == null) { return null; } if (compat != null && !compat.supportsScreen()) { mMainThreadConfig.setTo(config); config = mMainThreadConfig; - compat.applyToConfiguration(config); + compat.applyToConfiguration(displayDensity, config); } return config; } @@ -1555,8 +1651,12 @@ public final class ActivityThread { * @param compInfo the compability info. It will use the default compatibility info when it's * null. */ - Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { - ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); + Resources getTopLevelResources(String resDir, + int displayId, Configuration overrideConfiguration, + CompatibilityInfo compInfo) { + ResourcesKey key = new ResourcesKey(resDir, + displayId, overrideConfiguration, + compInfo.applicationScale); Resources r; synchronized (mPackages) { // Resources is app scale dependent. @@ -1587,14 +1687,27 @@ public final class ActivityThread { } //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); - DisplayMetrics metrics = getDisplayMetricsLocked(null, false); - r = new Resources(assets, metrics, getConfiguration(), compInfo); + DisplayMetrics dm = getDisplayMetricsLocked(displayId, null); + Configuration config; + boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + if (!isDefaultDisplay || key.mOverrideConfiguration != null) { + config = new Configuration(getConfiguration()); + if (!isDefaultDisplay) { + applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config); + } + if (key.mOverrideConfiguration != null) { + config.updateFrom(key.mOverrideConfiguration); + } + } else { + config = getConfiguration(); + } + r = new Resources(assets, dm, config, compInfo); if (false) { Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale); } - + synchronized (mPackages) { WeakReference<Resources> wr = mActiveResources.get(key); Resources existing = wr != null ? wr.get() : null; @@ -1614,8 +1727,11 @@ public final class ActivityThread { /** * Creates the top level resources for the given package. */ - Resources getTopLevelResources(String resDir, LoadedApk pkgInfo) { - return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo.get()); + Resources getTopLevelResources(String resDir, + int displayId, Configuration overrideConfiguration, + LoadedApk pkgInfo) { + return getTopLevelResources(resDir, displayId, overrideConfiguration, + pkgInfo.mCompatibilityInfo.get()); } final Handler getHandler() { @@ -1624,6 +1740,11 @@ public final class ActivityThread { public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, int flags) { + return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId()); + } + + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags, int userId) { synchronized (mPackages) { WeakReference<LoadedApk> ref; if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) { @@ -1652,7 +1773,7 @@ public final class ActivityThread { ApplicationInfo ai = null; try { ai = getPackageManager().getApplicationInfo(packageName, - PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId()); + PackageManager.GET_SHARED_LIBRARY_FILES, userId); } catch (RemoteException e) { // Ignore } @@ -1669,7 +1790,7 @@ public final class ActivityThread { boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; boolean securityViolation = includeCode && ai.uid != 0 && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null - ? !UserId.isSameApp(ai.uid, mBoundApplication.appInfo.uid) + ? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid) : true); if ((flags&(Context.CONTEXT_INCLUDE_CODE |Context.CONTEXT_IGNORE_SECURITY)) @@ -1786,7 +1907,8 @@ public final class ActivityThread { context.init(info, null, this); context.getResources().updateConfiguration( getConfiguration(), getDisplayMetricsLocked( - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, false)); + Display.DEFAULT_DISPLAY, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)); mSystemContext = context; //Slog.i(TAG, "Created system resources " + context.getResources() // + ": " + context.getResources().getConfiguration()); @@ -1998,9 +2120,7 @@ public final class ActivityThread { + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { - ContextImpl appContext = new ContextImpl(); - appContext.init(r.packageInfo, r.token, this); - appContext.setOuterContext(activity); + Context appContext = createBaseContextForActivity(r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " @@ -2065,6 +2185,31 @@ public final class ActivityThread { return activity; } + private Context createBaseContextForActivity(ActivityClientRecord r, + final Activity activity) { + ContextImpl appContext = new ContextImpl(); + appContext.init(r.packageInfo, r.token, this); + appContext.setOuterContext(activity); + + // For debugging purposes, if the activity's package name contains the value of + // the "debug.use-second-display" system property as a substring, then show + // its content on a secondary display if there is one. + Context baseContext = appContext; + String pkgName = SystemProperties.get("debug.second-display.pkg"); + if (pkgName != null && !pkgName.isEmpty() + && r.packageInfo.mPackageName.contains(pkgName)) { + DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); + for (int displayId : dm.getDisplayIds()) { + if (displayId != Display.DEFAULT_DISPLAY) { + Display display = dm.getRealDisplay(displayId); + baseContext = appContext.createDisplayContext(display); + break; + } + } + } + return baseContext; + } + private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. @@ -2086,7 +2231,8 @@ public final class ActivityThread { if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; - handleResumeActivity(r.token, false, r.isForward); + handleResumeActivity(r.token, false, r.isForward, + !r.activity.mFinished && !r.startsNotResumed); if (!r.activity.mFinished && r.startsNotResumed) { // The activity manager actually wants this one to start out @@ -2554,6 +2700,7 @@ public final class ActivityThread { r.activity.mStartedActivity = false; } try { + r.activity.mFragments.noteStateNotSaved(); if (r.pendingIntents != null) { deliverNewIntents(r, r.pendingIntents); r.pendingIntents = null; @@ -2565,7 +2712,7 @@ public final class ActivityThread { r.activity.performResume(); EventLog.writeEvent(LOG_ON_RESUME_CALLED, - r.activity.getComponentName().getClassName()); + UserHandle.myUserId(), r.activity.getComponentName().getClassName()); r.paused = false; r.stopped = false; @@ -2587,7 +2734,7 @@ public final class ActivityThread { r.mPendingRemoveWindowManager.removeViewImmediate(r.mPendingRemoveWindow); IBinder wtoken = r.mPendingRemoveWindow.getWindowToken(); if (wtoken != null) { - WindowManagerImpl.getDefault().closeAll(wtoken, + WindowManagerGlobal.getInstance().closeAll(wtoken, r.activity.getClass().getName(), "Activity"); } } @@ -2595,7 +2742,8 @@ public final class ActivityThread { r.mPendingRemoveWindowManager = null; } - final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { + final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, + boolean reallyResume) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); @@ -2692,6 +2840,14 @@ public final class ActivityThread { } r.onlyLocalRequest = false; + // Tell the activity manager we have resumed. + if (reallyResume) { + try { + ActivityManagerNative.getDefault().activityResumed(token); + } catch (RemoteException ex) { + } + } + } else { // If an exception was thrown when trying to resume, then // just end this activity. @@ -2727,7 +2883,8 @@ public final class ActivityThread { // On platforms where we don't want thumbnails, set dims to (0,0) if ((w > 0) && (h > 0)) { - thumbnail = Bitmap.createBitmap(w, h, THUMBNAIL_FORMAT); + thumbnail = Bitmap.createBitmap(r.activity.getResources().getDisplayMetrics(), + w, h, THUMBNAIL_FORMAT); thumbnail.eraseColor(0); } } @@ -2775,7 +2932,7 @@ public final class ActivityThread { if (r.isPreHoneycomb()) { QueuedWork.waitToFinish(); } - + // Tell the activity manager we have paused. try { ActivityManagerNative.getDefault().activityPaused(token); @@ -2823,7 +2980,8 @@ public final class ActivityThread { // Now we are idle. r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); - EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName()); + EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(), + r.activity.getComponentName().getClassName()); if (!r.activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + @@ -3121,7 +3279,7 @@ public final class ActivityThread { apk.mCompatibilityInfo.set(data.info); } handleConfigurationChanged(mConfiguration, data.info); - WindowManagerImpl.getDefault().reportNewConfiguration(mConfiguration); + WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration); } private void deliverResults(ActivityClientRecord r, List<ResultInfo> results) { @@ -3208,7 +3366,7 @@ public final class ActivityThread { try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); - EventLog.writeEvent(LOG_ON_PAUSE_CALLED, + EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName()); if (!r.activity.mCalled) { throw new SuperNotCalledException( @@ -3310,7 +3468,7 @@ public final class ActivityThread { } } if (wtoken != null && r.mPendingRemoveWindow == null) { - WindowManagerImpl.getDefault().closeAll(wtoken, + WindowManagerGlobal.getInstance().closeAll(wtoken, r.activity.getClass().getName(), "Activity"); } r.activity.mDecor = null; @@ -3322,7 +3480,7 @@ public final class ActivityThread { // by the app will leak. Well we try to warning them a lot // about leaking windows, because that is a bug, so if they are // using this recreate facility then they get to live with leaks. - WindowManagerImpl.getDefault().closeAll(token, + WindowManagerGlobal.getInstance().closeAll(token, r.activity.getClass().getName(), "Activity"); } @@ -3461,6 +3619,8 @@ public final class ActivityThread { // If there was a pending configuration change, execute it first. if (changedConfig != null) { + mCurDefaultDisplayDpi = changedConfig.densityDpi; + updateDefaultDensity(); handleConfigurationChanged(changedConfig, null); } @@ -3534,39 +3694,45 @@ public final class ActivityThread { } } - ArrayList<ComponentCallbacks2> collectComponentCallbacksLocked( + ArrayList<ComponentCallbacks2> collectComponentCallbacks( boolean allActivities, Configuration newConfig) { ArrayList<ComponentCallbacks2> callbacks = new ArrayList<ComponentCallbacks2>(); - if (mActivities.size() > 0) { - for (ActivityClientRecord ar : mActivities.values()) { - Activity a = ar.activity; - if (a != null) { - Configuration thisConfig = applyConfigCompatMainThread(newConfig, - ar.packageInfo.mCompatibilityInfo.getIfNeeded()); - if (!ar.activity.mFinished && (allActivities || !ar.paused)) { - // If the activity is currently resumed, its configuration - // needs to change right now. - callbacks.add(a); - } else if (thisConfig != null) { - // Otherwise, we will tell it about the change - // the next time it is resumed or shown. Note that - // the activity manager may, before then, decide the - // activity needs to be destroyed to handle its new - // configuration. - if (DEBUG_CONFIGURATION) { - Slog.v(TAG, "Setting activity " - + ar.activityInfo.name + " newConfig=" + thisConfig); + synchronized (mPackages) { + final int N = mAllApplications.size(); + for (int i=0; i<N; i++) { + callbacks.add(mAllApplications.get(i)); + } + if (mActivities.size() > 0) { + for (ActivityClientRecord ar : mActivities.values()) { + Activity a = ar.activity; + if (a != null) { + Configuration thisConfig = applyConfigCompatMainThread(mCurDefaultDisplayDpi, + newConfig, ar.packageInfo.mCompatibilityInfo.getIfNeeded()); + if (!ar.activity.mFinished && (allActivities || !ar.paused)) { + // If the activity is currently resumed, its configuration + // needs to change right now. + callbacks.add(a); + } else if (thisConfig != null) { + // Otherwise, we will tell it about the change + // the next time it is resumed or shown. Note that + // the activity manager may, before then, decide the + // activity needs to be destroyed to handle its new + // configuration. + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Setting activity " + + ar.activityInfo.name + " newConfig=" + thisConfig); + } + ar.newConfig = thisConfig; } - ar.newConfig = thisConfig; } } } - } - if (mServices.size() > 0) { - for (Service service : mServices.values()) { - callbacks.add(service); + if (mServices.size() > 0) { + for (Service service : mServices.values()) { + callbacks.add(service); + } } } synchronized (mProviderMap) { @@ -3576,10 +3742,6 @@ public final class ActivityThread { } } } - final int N = mAllApplications.size(); - for (int i=0; i<N; i++) { - callbacks.add(mAllApplications.get(i)); - } return callbacks; } @@ -3646,7 +3808,9 @@ public final class ActivityThread { return false; } int changes = mResConfiguration.updateFrom(config); - DisplayMetrics dm = getDisplayMetricsLocked(null, true); + flushDisplayMetricsLocked(); + DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked( + Display.DEFAULT_DISPLAY, null); if (compat != null && (mResCompatibilityInfo == null || !mResCompatibilityInfo.equals(compat))) { @@ -3661,22 +3825,41 @@ public final class ActivityThread { Locale.setDefault(config.locale); } - Resources.updateSystemConfiguration(config, dm, compat); + Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); ApplicationPackageManager.configurationChanged(); //Slog.i(TAG, "Configuration changed in " + currentPackageName()); - - Iterator<WeakReference<Resources>> it = - mActiveResources.values().iterator(); - //Iterator<Map.Entry<String, WeakReference<Resources>>> it = - // mActiveResources.entrySet().iterator(); + + Configuration tmpConfig = null; + + Iterator<Map.Entry<ResourcesKey, WeakReference<Resources>>> it = + mActiveResources.entrySet().iterator(); while (it.hasNext()) { - WeakReference<Resources> v = it.next(); - Resources r = v.get(); + Map.Entry<ResourcesKey, WeakReference<Resources>> entry = it.next(); + Resources r = entry.getValue().get(); if (r != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + r + " config to: " + config); - r.updateConfiguration(config, dm, compat); + int displayId = entry.getKey().mDisplayId; + boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + DisplayMetrics dm = defaultDisplayMetrics; + Configuration overrideConfig = entry.getKey().mOverrideConfiguration; + if (!isDefaultDisplay || overrideConfig != null) { + if (tmpConfig == null) { + tmpConfig = new Configuration(); + } + tmpConfig.setTo(config); + if (!isDefaultDisplay) { + dm = getDisplayMetricsLocked(displayId, null); + applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig); + } + if (overrideConfig != null) { + tmpConfig.updateFrom(overrideConfig); + } + r.updateConfiguration(tmpConfig, dm, compat); + } else { + r.updateConfiguration(config, dm, compat); + } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { @@ -3688,14 +3871,36 @@ public final class ActivityThread { return changes != 0; } - final Configuration applyCompatConfiguration() { + final void applyNonDefaultDisplayMetricsToConfigurationLocked( + DisplayMetrics dm, Configuration config) { + config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; + config.densityDpi = dm.densityDpi; + config.screenWidthDp = (int)(dm.widthPixels / dm.density); + config.screenHeightDp = (int)(dm.heightPixels / dm.density); + int sl = Configuration.resetScreenLayout(config.screenLayout); + if (dm.widthPixels > dm.heightPixels) { + config.orientation = Configuration.ORIENTATION_LANDSCAPE; + config.screenLayout = Configuration.reduceScreenLayout(sl, + config.screenWidthDp, config.screenHeightDp); + } else { + config.orientation = Configuration.ORIENTATION_PORTRAIT; + config.screenLayout = Configuration.reduceScreenLayout(sl, + config.screenHeightDp, config.screenWidthDp); + } + config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate + config.compatScreenWidthDp = config.screenWidthDp; + config.compatScreenHeightDp = config.screenHeightDp; + config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; + } + + final Configuration applyCompatConfiguration(int displayDensity) { Configuration config = mConfiguration; if (mCompatConfiguration == null) { mCompatConfiguration = new Configuration(); } mCompatConfiguration.setTo(mConfiguration); if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { - mResCompatibilityInfo.applyToConfiguration(mCompatConfiguration); + mResCompatibilityInfo.applyToConfiguration(displayDensity, mCompatConfiguration); config = mCompatConfiguration; } return config; @@ -3703,13 +3908,14 @@ public final class ActivityThread { final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { - ArrayList<ComponentCallbacks2> callbacks = null; int configDiff = 0; synchronized (mPackages) { if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { config = mPendingConfiguration; + mCurDefaultDisplayDpi = config.densityDpi; + updateDefaultDensity(); } mPendingConfiguration = null; } @@ -3731,12 +3937,13 @@ public final class ActivityThread { } configDiff = mConfiguration.diff(config); mConfiguration.updateFrom(config); - config = applyCompatConfiguration(); - callbacks = collectComponentCallbacksLocked(false, config); + config = applyCompatConfiguration(mCurDefaultDisplayDpi); } - + + ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config); + // Cleanup hardware accelerated stuff - WindowManagerImpl.getDefault().trimLocalMemory(); + WindowManagerGlobal.getInstance().trimLocalMemory(); freeTextLayoutCachesIfNeeded(configDiff); @@ -3847,11 +4054,7 @@ public final class ActivityThread { } final void handleLowMemory() { - ArrayList<ComponentCallbacks2> callbacks; - - synchronized (mPackages) { - callbacks = collectComponentCallbacksLocked(true, null); - } + ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null); final int N = callbacks.size(); for (int i=0; i<N; i++) { @@ -3876,20 +4079,17 @@ public final class ActivityThread { final void handleTrimMemory(int level) { if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); - final WindowManagerImpl windowManager = WindowManagerImpl.getDefault(); + final WindowManagerGlobal windowManager = WindowManagerGlobal.getInstance(); windowManager.startTrimMemory(level); - ArrayList<ComponentCallbacks2> callbacks; - synchronized (mPackages) { - callbacks = collectComponentCallbacksLocked(true, null); - } + ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null); final int N = callbacks.size(); for (int i = 0; i < N; i++) { callbacks.get(i).onTrimMemory(level); } - windowManager.endTrimMemory(); + windowManager.endTrimMemory(); } private void setupGraphicsSupport(LoadedApk info, File cacheDir) { @@ -3910,8 +4110,20 @@ public final class ActivityThread { } catch (RemoteException e) { // Ignore } - } - + } + + private void updateDefaultDensity() { + if (mCurDefaultDisplayDpi != Configuration.DENSITY_DPI_UNDEFINED + && mCurDefaultDisplayDpi != DisplayMetrics.DENSITY_DEVICE + && !mDensityCompatMode) { + Slog.i(TAG, "Switching default density from " + + DisplayMetrics.DENSITY_DEVICE + " to " + + mCurDefaultDisplayDpi); + DisplayMetrics.DENSITY_DEVICE = mCurDefaultDisplayDpi; + Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT); + } + } + private void handleBindApplication(AppBindData data) { mBoundApplication = data; mConfiguration = new Configuration(data.config); @@ -3924,14 +4136,14 @@ public final class ActivityThread { // send up app name; do this *before* waiting for debugger Process.setArgV0(data.processName); - android.ddm.DdmHandleAppName.setAppName(data.processName); + android.ddm.DdmHandleAppName.setAppName(data.processName, + UserHandle.myUserId()); if (data.persistent) { // Persistent processes on low-memory devices do not get to // use hardware accelerated drawing, since this can add too much // overhead to the process. - Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); - if (!ActivityManager.isHighEndGfx(display)) { + if (!ActivityManager.isHighEndGfx()) { HardwareRenderer.disable(false); } } @@ -3967,19 +4179,35 @@ public final class ActivityThread { * in AppBindData can be safely assumed to be up to date */ applyConfigurationToResourcesLocked(data.config, data.compatInfo); - applyCompatConfiguration(); + mCurDefaultDisplayDpi = data.config.densityDpi; + applyCompatConfiguration(mCurDefaultDisplayDpi); data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + /** + * Switch this process to density compatibility mode if needed. + */ + if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) + == 0) { + mDensityCompatMode = true; + Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT); + } + updateDefaultDensity(); + final ContextImpl appContext = new ContextImpl(); appContext.init(data.info, null, this); - final File cacheDir = appContext.getCacheDir(); - - // Provide a usable directory for temporary files - System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath()); - - setupGraphicsSupport(data.info, cacheDir); + if (!Process.isIsolated()) { + final File cacheDir = appContext.getCacheDir(); + if (cacheDir != null) { + // Provide a usable directory for temporary files + System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath()); + + setupGraphicsSupport(data.info, cacheDir); + } else { + Log.e(TAG, "Unable to setupGraphicsSupport due to missing cache directory"); + } + } /** * For system applications on userdebug/eng builds, log stack * traces of disk and network access to dropbox for analysis. @@ -4001,14 +4229,6 @@ public final class ActivityThread { StrictMode.enableDeathOnNetwork(); } - /** - * Switch this process to density compatibility mode if needed. - */ - if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) - == 0) { - Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT); - } - if (data.debugMode != IApplicationThread.DEBUG_OFF) { // XXX should have option to change the port. Debug.changeDebugPort(8100); @@ -4202,8 +4422,9 @@ public final class ActivityThread { } } - public final IContentProvider acquireProvider(Context c, String name, boolean stable) { - IContentProvider provider = acquireExistingProvider(c, name, stable); + public final IContentProvider acquireProvider( + Context c, String auth, int userId, boolean stable) { + final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); if (provider != null) { return provider; } @@ -4217,11 +4438,11 @@ public final class ActivityThread { IActivityManager.ContentProviderHolder holder = null; try { holder = ActivityManagerNative.getDefault().getContentProvider( - getApplicationThread(), name, stable); + getApplicationThread(), auth, userId, stable); } catch (RemoteException ex) { } if (holder == null) { - Slog.e(TAG, "Failed to find provider info for " + name); + Slog.e(TAG, "Failed to find provider info for " + auth); return null; } @@ -4298,10 +4519,11 @@ public final class ActivityThread { } } - public final IContentProvider acquireExistingProvider(Context c, String name, - boolean stable) { + public final IContentProvider acquireExistingProvider( + Context c, String auth, int userId, boolean stable) { synchronized (mProviderMap) { - ProviderClientRecord pr = mProviderMap.get(name); + final ProviderKey key = new ProviderKey(auth, userId); + final ProviderClientRecord pr = mProviderMap.get(key); if (pr == null) { return null; } @@ -4481,17 +4703,20 @@ public final class ActivityThread { } private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider, - ContentProvider localProvider,IActivityManager.ContentProviderHolder holder) { - String names[] = PATTERN_SEMICOLON.split(holder.info.authority); - ProviderClientRecord pcr = new ProviderClientRecord(names, provider, - localProvider, holder); - for (int i = 0; i < names.length; i++) { - ProviderClientRecord existing = mProviderMap.get(names[i]); + ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) { + final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority); + final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid); + + final ProviderClientRecord pcr = new ProviderClientRecord( + auths, provider, localProvider, holder); + for (String auth : auths) { + final ProviderKey key = new ProviderKey(auth, userId); + final ProviderClientRecord existing = mProviderMap.get(key); if (existing != null) { Slog.w(TAG, "Content provider " + pcr.mHolder.info.name - + " already published as " + names[i]); + + " already published as " + auth); } else { - mProviderMap.put(names[i], pcr); + mProviderMap.put(key, pcr); } } return pcr; @@ -4643,7 +4868,8 @@ public final class ActivityThread { ensureJitEnabled(); } }); - android.ddm.DdmHandleAppName.setAppName("<pre-initialized>"); + android.ddm.DdmHandleAppName.setAppName("<pre-initialized>", + UserHandle.myUserId()); RuntimeInit.setApplicationObject(mAppThread.asBinder()); IActivityManager mgr = ActivityManagerNative.getDefault(); try { @@ -4654,7 +4880,8 @@ public final class ActivityThread { } else { // Don't set application object here -- if the system crashes, // we can't display an alert, we just want to die die die. - android.ddm.DdmHandleAppName.setAppName("system_process"); + android.ddm.DdmHandleAppName.setAppName("system_process", + UserHandle.myUserId()); try { mInstrumentation = new Instrumentation(); ContextImpl context = new ContextImpl(); @@ -4668,7 +4895,10 @@ public final class ActivityThread { "Unable to instantiate Application():" + e.toString(), e); } } - + + // add dropbox logging to libcore + DropBox.setReporter(new DropBoxReporter()); + ViewRootImpl.addConfigCallback(new ComponentCallbacks2() { public void onConfigurationChanged(Configuration newConfig) { synchronized (mPackages) { @@ -4717,6 +4947,32 @@ public final class ActivityThread { } } + private static class EventLoggingReporter implements EventLogger.Reporter { + @Override + public void report (int code, Object... list) { + EventLog.writeEvent(code, list); + } + } + + private class DropBoxReporter implements DropBox.Reporter { + + private DropBoxManager dropBox; + + public DropBoxReporter() { + dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE); + } + + @Override + public void addData(String tag, byte[] data, int flags) { + dropBox.addData(tag, data, flags); + } + + @Override + public void addText(String tag, String data) { + dropBox.addText(tag, data); + } + } + public static void main(String[] args) { SamplingProfilerIntegration.start(); @@ -4725,6 +4981,11 @@ public final class ActivityThread { // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); + Environment.initForCurrentUser(); + + // Set the reporter for event logging in libcore + EventLogger.setReporter(new EventLoggingReporter()); + Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java index e37b3fa..6ab2bd1 100644 --- a/core/java/android/app/AlertDialog.java +++ b/core/java/android/app/AlertDialog.java @@ -110,8 +110,9 @@ public class AlertDialog extends Dialog implements DialogInterface { this(context, theme, true); } - AlertDialog(Context context, int theme, boolean createContextWrapper) { - super(context, resolveDialogTheme(context, theme), createContextWrapper); + AlertDialog(Context context, int theme, boolean createThemeContextWrapper) { + super(context, resolveDialogTheme(context, theme), createThemeContextWrapper); + mWindow.alwaysReadCloseOnTouchAttr(); mAlert = new AlertController(getContext(), this, getWindow()); } @@ -566,7 +567,14 @@ public class AlertDialog extends Dialog implements DialogInterface { /** * Sets the callback that will be called if the dialog is canceled. + * + * <p>Even in a cancelable dialog, the dialog may be dismissed for reasons other than + * being canceled or one of the supplied choices being selected. + * If you are interested in listening for all cases where the dialog is dismissed + * and not just when it is canceled, see + * {@link #setOnDismissListener(android.content.DialogInterface.OnDismissListener) setOnDismissListener}.</p> * @see #setCancelable(boolean) + * @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener) * * @return This Builder object to allow for chaining of calls to set methods */ @@ -576,6 +584,16 @@ public class AlertDialog extends Dialog implements DialogInterface { } /** + * Sets the callback that will be called when the dialog is dismissed for any reason. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setOnDismissListener(OnDismissListener onDismissListener) { + P.mOnDismissListener = onDismissListener; + return this; + } + + /** * Sets the callback that will be called if a key is dispatched to the dialog. * * @return This Builder object to allow for chaining of calls to set methods @@ -917,6 +935,7 @@ public class AlertDialog extends Dialog implements DialogInterface { dialog.setCanceledOnTouchOutside(true); } dialog.setOnCancelListener(P.mOnCancelListener); + dialog.setOnDismissListener(P.mOnDismissListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); } diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index ebf4261..954476d 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -158,8 +158,8 @@ public class ApplicationErrorReport implements Parcelable { public static ComponentName getErrorReportReceiver(Context context, String packageName, int appFlags) { // check if error reporting is enabled in secure settings - int enabled = Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.SEND_ACTION_APP_ERROR, 0); + int enabled = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.SEND_ACTION_APP_ERROR, 0); if (enabled == 0) { return null; } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 191a696..7431765 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -41,8 +41,8 @@ import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; import android.content.pm.ManifestDigest; +import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.content.res.Resources; import android.content.res.XmlResourceParser; @@ -50,8 +50,8 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Process; import android.os.RemoteException; -import android.os.UserId; import android.util.Log; +import android.view.Display; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -69,7 +69,7 @@ final class ApplicationPackageManager extends PackageManager { public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException { try { - PackageInfo pi = mPM.getPackageInfo(packageName, flags, UserId.myUserId()); + PackageInfo pi = mPM.getPackageInfo(packageName, flags, mContext.getUserId()); if (pi != null) { return pi; } @@ -199,7 +199,7 @@ final class ApplicationPackageManager extends PackageManager { public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException { try { - ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags, UserId.myUserId()); + ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags, mContext.getUserId()); if (ai != null) { return ai; } @@ -214,7 +214,7 @@ final class ApplicationPackageManager extends PackageManager { public ActivityInfo getActivityInfo(ComponentName className, int flags) throws NameNotFoundException { try { - ActivityInfo ai = mPM.getActivityInfo(className, flags, UserId.myUserId()); + ActivityInfo ai = mPM.getActivityInfo(className, flags, mContext.getUserId()); if (ai != null) { return ai; } @@ -229,7 +229,7 @@ final class ApplicationPackageManager extends PackageManager { public ActivityInfo getReceiverInfo(ComponentName className, int flags) throws NameNotFoundException { try { - ActivityInfo ai = mPM.getReceiverInfo(className, flags, UserId.myUserId()); + ActivityInfo ai = mPM.getReceiverInfo(className, flags, mContext.getUserId()); if (ai != null) { return ai; } @@ -244,7 +244,7 @@ final class ApplicationPackageManager extends PackageManager { public ServiceInfo getServiceInfo(ComponentName className, int flags) throws NameNotFoundException { try { - ServiceInfo si = mPM.getServiceInfo(className, flags, UserId.myUserId()); + ServiceInfo si = mPM.getServiceInfo(className, flags, mContext.getUserId()); if (si != null) { return si; } @@ -259,7 +259,7 @@ final class ApplicationPackageManager extends PackageManager { public ProviderInfo getProviderInfo(ComponentName className, int flags) throws NameNotFoundException { try { - ProviderInfo pi = mPM.getProviderInfo(className, flags, UserId.myUserId()); + ProviderInfo pi = mPM.getProviderInfo(className, flags, mContext.getUserId()); if (pi != null) { return pi; } @@ -404,6 +404,12 @@ final class ApplicationPackageManager extends PackageManager { @SuppressWarnings("unchecked") @Override public List<PackageInfo> getInstalledPackages(int flags) { + return getInstalledPackages(flags, mContext.getUserId()); + } + + /** @hide */ + @Override + public List<PackageInfo> getInstalledPackages(int flags, int userId) { try { final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>(); PackageInfo lastItem = null; @@ -411,7 +417,7 @@ final class ApplicationPackageManager extends PackageManager { do { final String lastKey = lastItem != null ? lastItem.packageName : null; - slice = mPM.getInstalledPackages(flags, lastKey); + slice = mPM.getInstalledPackages(flags, lastKey, userId); lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR); } while (!slice.isLastSlice()); @@ -424,7 +430,7 @@ final class ApplicationPackageManager extends PackageManager { @SuppressWarnings("unchecked") @Override public List<ApplicationInfo> getInstalledApplications(int flags) { - int userId = UserId.getUserId(Process.myUid()); + final int userId = mContext.getUserId(); try { final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>(); ApplicationInfo lastItem = null; @@ -444,11 +450,17 @@ final class ApplicationPackageManager extends PackageManager { @Override public ResolveInfo resolveActivity(Intent intent, int flags) { + return resolveActivityAsUser(intent, flags, mContext.getUserId()); + } + + @Override + public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { try { return mPM.resolveIntent( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags, UserId.myUserId()); + flags, + userId); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -457,12 +469,19 @@ final class ApplicationPackageManager extends PackageManager { @Override public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { + return queryIntentActivitiesAsUser(intent, flags, mContext.getUserId()); + } + + /** @hide Same as above but for a specific user */ + @Override + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, + int flags, int userId) { try { return mPM.queryIntentActivities( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, - UserId.myUserId()); + userId); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -494,56 +513,69 @@ final class ApplicationPackageManager extends PackageManager { try { return mPM.queryIntentActivityOptions(caller, specifics, specificTypes, intent, intent.resolveTypeIfNeeded(resolver), - flags, UserId.myUserId()); + flags, mContext.getUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } + /** + * @hide + */ @Override - public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) { + public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags, int userId) { try { return mPM.queryIntentReceivers( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, - UserId.myUserId()); + userId); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } @Override + public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) { + return queryBroadcastReceivers(intent, flags, mContext.getUserId()); + } + + @Override public ResolveInfo resolveService(Intent intent, int flags) { try { return mPM.resolveService( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, - UserId.myUserId()); + mContext.getUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } @Override - public List<ResolveInfo> queryIntentServices(Intent intent, int flags) { + public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { try { return mPM.queryIntentServices( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, - UserId.myUserId()); + userId); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } @Override + public List<ResolveInfo> queryIntentServices(Intent intent, int flags) { + return queryIntentServicesAsUser(intent, flags, mContext.getUserId()); + } + + @Override public ProviderInfo resolveContentProvider(String name, int flags) { try { - return mPM.resolveContentProvider(name, flags, UserId.myUserId()); + return mPM.resolveContentProvider(name, flags, mContext.getUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -712,8 +744,8 @@ final class ApplicationPackageManager extends PackageManager { return mContext.mMainThread.getSystemContext().getResources(); } Resources r = mContext.mMainThread.getTopLevelResources( - app.uid == Process.myUid() ? app.sourceDir - : app.publicSourceDir, mContext.mPackageInfo); + app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir, + Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo); if (r != null) { return r; } @@ -726,6 +758,28 @@ final class ApplicationPackageManager extends PackageManager { getApplicationInfo(appPackageName, 0)); } + /** @hide */ + @Override + public Resources getResourcesForApplicationAsUser(String appPackageName, int userId) + throws NameNotFoundException { + if (userId < 0) { + throw new IllegalArgumentException( + "Call does not support special user #" + userId); + } + if ("system".equals(appPackageName)) { + return mContext.mMainThread.getSystemContext().getResources(); + } + try { + ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, 0, userId); + if (ai != null) { + return getResourcesForApplication(ai); + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + throw new NameNotFoundException("Package " + appPackageName + " doesn't exist"); + } + int mCachedSafeMode = -1; @Override public boolean isSafeMode() { try { @@ -984,6 +1038,33 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public void installPackageWithVerificationAndEncryption(Uri packageURI, + IPackageInstallObserver observer, int flags, String installerPackageName, + VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { + try { + mPM.installPackageWithVerificationAndEncryption(packageURI, observer, flags, + installerPackageName, verificationParams, encryptionParams); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public int installExistingPackage(String packageName) + throws NameNotFoundException { + try { + int res = mPM.installExistingPackage(packageName); + if (res == INSTALL_FAILED_INVALID_URI) { + throw new NameNotFoundException("Package " + packageName + " doesn't exist"); + } + return res; + } catch (RemoteException e) { + // Should never happen! + throw new NameNotFoundException("Package " + packageName + " doesn't exist"); + } + } + + @Override public void verifyPendingInstall(int id, int response) { try { mPM.verifyPendingInstall(id, response); @@ -993,6 +1074,16 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, + long millisecondsToDelay) { + try { + mPM.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override public void setInstallerPackageName(String targetPackage, String installerPackageName) { try { @@ -1033,7 +1124,7 @@ final class ApplicationPackageManager extends PackageManager { public void clearApplicationUserData(String packageName, IPackageDataObserver observer) { try { - mPM.clearApplicationUserData(packageName, observer, UserId.myUserId()); + mPM.clearApplicationUserData(packageName, observer, mContext.getUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1066,10 +1157,10 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public void getPackageSizeInfo(String packageName, - IPackageStatsObserver observer) { + public void getPackageSizeInfo(String packageName, int userHandle, + IPackageStatsObserver observer) { try { - mPM.getPackageSizeInfo(packageName, observer); + mPM.getPackageSizeInfo(packageName, userHandle, observer); } catch (RemoteException e) { // Should never happen! } @@ -1106,7 +1197,17 @@ final class ApplicationPackageManager extends PackageManager { public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { try { - mPM.addPreferredActivity(filter, match, set, activity); + mPM.addPreferredActivity(filter, match, set, activity, mContext.getUserId()); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public void addPreferredActivity(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity, int userId) { + try { + mPM.addPreferredActivity(filter, match, set, activity, userId); } catch (RemoteException e) { // Should never happen! } @@ -1146,7 +1247,7 @@ final class ApplicationPackageManager extends PackageManager { public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) { try { - mPM.setComponentEnabledSetting(componentName, newState, flags, UserId.myUserId()); + mPM.setComponentEnabledSetting(componentName, newState, flags, mContext.getUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1155,7 +1256,7 @@ final class ApplicationPackageManager extends PackageManager { @Override public int getComponentEnabledSetting(ComponentName componentName) { try { - return mPM.getComponentEnabledSetting(componentName, UserId.myUserId()); + return mPM.getComponentEnabledSetting(componentName, mContext.getUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1166,7 +1267,7 @@ final class ApplicationPackageManager extends PackageManager { public void setApplicationEnabledSetting(String packageName, int newState, int flags) { try { - mPM.setApplicationEnabledSetting(packageName, newState, flags, UserId.myUserId()); + mPM.setApplicationEnabledSetting(packageName, newState, flags, mContext.getUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1175,86 +1276,13 @@ final class ApplicationPackageManager extends PackageManager { @Override public int getApplicationEnabledSetting(String packageName) { try { - return mPM.getApplicationEnabledSetting(packageName, UserId.myUserId()); + return mPM.getApplicationEnabledSetting(packageName, mContext.getUserId()); } catch (RemoteException e) { // Should never happen! } return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; } - // Multi-user support - - /** - * @hide - */ - @Override - public UserInfo createUser(String name, int flags) { - try { - return mPM.createUser(name, flags); - } catch (RemoteException e) { - // Should never happen! - } - return null; - } - - /** - * @hide - */ - @Override - public List<UserInfo> getUsers() { - try { - return mPM.getUsers(); - } catch (RemoteException re) { - ArrayList<UserInfo> users = new ArrayList<UserInfo>(); - UserInfo primary = new UserInfo(0, "Root!", UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); - users.add(primary); - return users; - } - } - - /** - * @hide - */ - @Override - public UserInfo getUser(int userId) { - try { - return mPM.getUser(userId); - } catch (RemoteException re) { - return null; - } - } - - /** - * @hide - */ - @Override - public boolean removeUser(int id) { - try { - return mPM.removeUser(id); - } catch (RemoteException e) { - return false; - } - } - - /** - * @hide - */ - @Override - public void updateUserName(int id, String name) { - try { - mPM.updateUserName(id, name); - } catch (RemoteException re) { - } - } - - /** - * @hide - */ - @Override - public void updateUserFlags(int id, int flags) { - // TODO: - } - /** * @hide */ diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index 8e6278d..63aa5f9 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -193,8 +193,9 @@ public abstract class ApplicationThreadNative extends Binder String resultData = data.readString(); Bundle resultExtras = data.readBundle(); boolean sync = data.readInt() != 0; + int sendingUser = data.readInt(); scheduleReceiver(intent, info, compatInfo, resultCode, resultData, - resultExtras, sync); + resultExtras, sync, sendingUser); return true; } @@ -378,8 +379,9 @@ public abstract class ApplicationThreadNative extends Binder Bundle extras = data.readBundle(); boolean ordered = data.readInt() != 0; boolean sticky = data.readInt() != 0; + int sendingUser = data.readInt(); scheduleRegisteredReceiver(receiver, intent, - resultCode, dataStr, extras, ordered, sticky); + resultCode, dataStr, extras, ordered, sticky, sendingUser); return true; } @@ -755,7 +757,7 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String resultData, - Bundle map, boolean sync) throws RemoteException { + Bundle map, boolean sync, int sendingUser) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); intent.writeToParcel(data, 0); @@ -765,6 +767,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeString(resultData); data.writeBundle(map); data.writeInt(sync ? 1 : 0); + data.writeInt(sendingUser); mRemote.transact(SCHEDULE_RECEIVER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); @@ -991,8 +994,8 @@ class ApplicationThreadProxy implements IApplicationThread { } public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, - int resultCode, String dataStr, Bundle extras, boolean ordered, boolean sticky) - throws RemoteException { + int resultCode, String dataStr, Bundle extras, boolean ordered, + boolean sticky, int sendingUser) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(receiver.asBinder()); @@ -1002,6 +1005,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeBundle(extras); data.writeInt(ordered ? 1 : 0); data.writeInt(sticky ? 1 : 0); + data.writeInt(sendingUser); mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 96814b7..1b1d341 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -20,6 +20,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; +import android.util.LogWriter; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -94,11 +95,12 @@ final class BackStackState implements Parcelable { public BackStackRecord instantiate(FragmentManagerImpl fm) { BackStackRecord bse = new BackStackRecord(fm); int pos = 0; + int num = 0; while (pos < mOps.length) { BackStackRecord.Op op = new BackStackRecord.Op(); op.cmd = mOps[pos++]; if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, - "BSE " + bse + " set base fragment #" + mOps[pos]); + "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]); int findex = mOps[pos++]; if (findex >= 0) { Fragment f = fm.mActive.get(findex); @@ -115,12 +117,13 @@ final class BackStackState implements Parcelable { op.removed = new ArrayList<Fragment>(N); for (int i=0; i<N; i++) { if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, - "BSE " + bse + " set remove fragment #" + mOps[pos]); + "Instantiate " + bse + " set remove fragment #" + mOps[pos]); Fragment r = fm.mActive.get(mOps[pos++]); op.removed.add(r); } } bse.addOp(op); + num++; } bse.mTransition = mTransition; bse.mTransitionStyle = mTransitionStyle; @@ -168,7 +171,7 @@ final class BackStackState implements Parcelable { */ final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, Runnable { - static final String TAG = "BackStackEntry"; + static final String TAG = FragmentManagerImpl.TAG; final FragmentManagerImpl mManager; @@ -206,46 +209,69 @@ final class BackStackRecord extends FragmentTransaction implements boolean mAllowAddToBackStack = true; String mName; boolean mCommitted; - int mIndex; + int mIndex = -1; int mBreadCrumbTitleRes; CharSequence mBreadCrumbTitleText; int mBreadCrumbShortTitleRes; CharSequence mBreadCrumbShortTitleText; - public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { - writer.print(prefix); writer.print("mName="); writer.print(mName); - writer.print(" mIndex="); writer.print(mIndex); - writer.print(" mCommitted="); writer.println(mCommitted); - if (mTransition != FragmentTransaction.TRANSIT_NONE) { - writer.print(prefix); writer.print("mTransition=#"); - writer.print(Integer.toHexString(mTransition)); - writer.print(" mTransitionStyle=#"); - writer.println(Integer.toHexString(mTransitionStyle)); - } - if (mEnterAnim != 0 || mExitAnim !=0) { - writer.print(prefix); writer.print("mEnterAnim=#"); - writer.print(Integer.toHexString(mEnterAnim)); - writer.print(" mExitAnim=#"); - writer.println(Integer.toHexString(mExitAnim)); - } - if (mPopEnterAnim != 0 || mPopExitAnim !=0) { - writer.print(prefix); writer.print("mPopEnterAnim=#"); - writer.print(Integer.toHexString(mPopEnterAnim)); - writer.print(" mPopExitAnim=#"); - writer.println(Integer.toHexString(mPopExitAnim)); + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("BackStackEntry{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + if (mIndex >= 0) { + sb.append(" #"); + sb.append(mIndex); } - if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { - writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); - writer.print(Integer.toHexString(mBreadCrumbTitleRes)); - writer.print(" mBreadCrumbTitleText="); - writer.println(mBreadCrumbTitleText); + if (mName != null) { + sb.append(" "); + sb.append(mName); } - if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { - writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#"); - writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); - writer.print(" mBreadCrumbShortTitleText="); - writer.println(mBreadCrumbShortTitleText); + sb.append("}"); + return sb.toString(); + } + + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + dump(prefix, writer, true); + } + + void dump(String prefix, PrintWriter writer, boolean full) { + if (full) { + writer.print(prefix); writer.print("mName="); writer.print(mName); + writer.print(" mIndex="); writer.print(mIndex); + writer.print(" mCommitted="); writer.println(mCommitted); + if (mTransition != FragmentTransaction.TRANSIT_NONE) { + writer.print(prefix); writer.print("mTransition=#"); + writer.print(Integer.toHexString(mTransition)); + writer.print(" mTransitionStyle=#"); + writer.println(Integer.toHexString(mTransitionStyle)); + } + if (mEnterAnim != 0 || mExitAnim !=0) { + writer.print(prefix); writer.print("mEnterAnim=#"); + writer.print(Integer.toHexString(mEnterAnim)); + writer.print(" mExitAnim=#"); + writer.println(Integer.toHexString(mExitAnim)); + } + if (mPopEnterAnim != 0 || mPopExitAnim !=0) { + writer.print(prefix); writer.print("mPopEnterAnim=#"); + writer.print(Integer.toHexString(mPopEnterAnim)); + writer.print(" mPopExitAnim=#"); + writer.println(Integer.toHexString(mPopExitAnim)); + } + if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { + writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); + writer.print(Integer.toHexString(mBreadCrumbTitleRes)); + writer.print(" mBreadCrumbTitleText="); + writer.println(mBreadCrumbTitleText); + } + if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { + writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#"); + writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); + writer.print(" mBreadCrumbShortTitleText="); + writer.println(mBreadCrumbShortTitleText); + } } if (mHead != null) { @@ -254,21 +280,34 @@ final class BackStackRecord extends FragmentTransaction implements Op op = mHead; int num = 0; while (op != null) { - writer.print(prefix); writer.print(" Op #"); writer.print(num); - writer.println(":"); - writer.print(innerPrefix); writer.print("cmd="); writer.print(op.cmd); - writer.print(" fragment="); writer.println(op.fragment); - if (op.enterAnim != 0 || op.exitAnim != 0) { - writer.print(prefix); writer.print("enterAnim=#"); - writer.print(Integer.toHexString(op.enterAnim)); - writer.print(" exitAnim=#"); - writer.println(Integer.toHexString(op.exitAnim)); + String cmdStr; + switch (op.cmd) { + case OP_NULL: cmdStr="NULL"; break; + case OP_ADD: cmdStr="ADD"; break; + case OP_REPLACE: cmdStr="REPLACE"; break; + case OP_REMOVE: cmdStr="REMOVE"; break; + case OP_HIDE: cmdStr="HIDE"; break; + case OP_SHOW: cmdStr="SHOW"; break; + case OP_DETACH: cmdStr="DETACH"; break; + case OP_ATTACH: cmdStr="ATTACH"; break; + default: cmdStr="cmd=" + op.cmd; break; } - if (op.popEnterAnim != 0 || op.popExitAnim != 0) { - writer.print(prefix); writer.print("popEnterAnim=#"); - writer.print(Integer.toHexString(op.popEnterAnim)); - writer.print(" popExitAnim=#"); - writer.println(Integer.toHexString(op.popExitAnim)); + writer.print(prefix); writer.print(" Op #"); writer.print(num); + writer.print(": "); writer.print(cmdStr); + writer.print(" "); writer.println(op.fragment); + if (full) { + if (op.enterAnim != 0 || op.exitAnim != 0) { + writer.print(innerPrefix); writer.print("enterAnim=#"); + writer.print(Integer.toHexString(op.enterAnim)); + writer.print(" exitAnim=#"); + writer.println(Integer.toHexString(op.exitAnim)); + } + if (op.popEnterAnim != 0 || op.popExitAnim != 0) { + writer.print(innerPrefix); writer.print("popEnterAnim=#"); + writer.print(Integer.toHexString(op.popEnterAnim)); + writer.print(" popExitAnim=#"); + writer.println(Integer.toHexString(op.popExitAnim)); + } } if (op.removed != null && op.removed.size() > 0) { for (int i=0; i<op.removed.size(); i++) { @@ -276,14 +315,17 @@ final class BackStackRecord extends FragmentTransaction implements if (op.removed.size() == 1) { writer.print("Removed: "); } else { - writer.println("Removed:"); - writer.print(innerPrefix); writer.print(" #"); writer.print(num); + if (i == 0) { + writer.println("Removed:"); + } + writer.print(innerPrefix); writer.print(" #"); writer.print(i); writer.print(": "); } writer.println(op.removed.get(i)); } } op = op.next; + num++; } } } @@ -538,7 +580,12 @@ final class BackStackRecord extends FragmentTransaction implements int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Commit: " + this); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Commit: " + this); + LogWriter logw = new LogWriter(Log.VERBOSE, TAG); + PrintWriter pw = new PrintWriter(logw); + dump(" ", null, pw, null); + } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); @@ -641,7 +688,12 @@ final class BackStackRecord extends FragmentTransaction implements } public void popFromBackStack(boolean doStateMove) { - if (FragmentManagerImpl.DEBUG) Log.v(TAG, "popFromBackStack: " + this); + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "popFromBackStack: " + this); + LogWriter logw = new LogWriter(Log.VERBOSE, TAG); + PrintWriter pw = new PrintWriter(logw); + dump(" ", null, pw, null); + } bumpBackStackNesting(-1); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 411f6d0..c41405b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -17,7 +17,9 @@ package android.app; import com.android.internal.policy.PolicyManager; +import com.android.internal.util.Preconditions; +import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -34,8 +36,10 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -46,6 +50,7 @@ import android.hardware.ISerialManager; import android.hardware.SensorManager; import android.hardware.SerialManager; import android.hardware.SystemSensorManager; +import android.hardware.display.DisplayManager; import android.hardware.input.IInputManager; import android.hardware.input.InputManager; import android.hardware.usb.IUsbManager; @@ -78,19 +83,23 @@ import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; +import android.os.IUserManager; import android.os.Looper; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserId; +import android.os.UserHandle; import android.os.SystemVibrator; +import android.os.UserManager; import android.os.storage.StorageManager; import android.telephony.TelephonyManager; import android.content.ClipboardManager; import android.util.AndroidRuntimeException; import android.util.Log; +import android.view.CompatibilityInfoHolder; import android.view.ContextThemeWrapper; +import android.view.Display; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; @@ -122,21 +131,33 @@ class ReceiverRestrictedContext extends ContextWrapper { @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { - throw new ReceiverCallNotAllowedException( - "IntentReceiver components are not allowed to register to receive intents"); - //ex.fillInStackTrace(); - //Log.e("IntentReceiver", ex.getMessage(), ex); - //return mContext.registerReceiver(receiver, filter, broadcastPermission, - // scheduler); + if (receiver == null) { + // Allow retrieving current sticky broadcast; this is safe since we + // aren't actually registering a receiver. + return super.registerReceiver(null, filter, broadcastPermission, scheduler); + } else { + throw new ReceiverCallNotAllowedException( + "BroadcastReceiver components are not allowed to register to receive intents"); + } + } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + if (receiver == null) { + // Allow retrieving current sticky broadcast; this is safe since we + // aren't actually registering a receiver. + return super.registerReceiverAsUser(null, user, filter, broadcastPermission, scheduler); + } else { + throw new ReceiverCallNotAllowedException( + "BroadcastReceiver components are not allowed to register to receive intents"); + } } @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { throw new ReceiverCallNotAllowedException( - "IntentReceiver components are not allowed to bind to services"); - //ex.fillInStackTrace(); - //Log.e("IntentReceiver", ex.getMessage(), ex); - //return mContext.bindService(service, interfaceName, conn, flags); + "BroadcastReceiver components are not allowed to bind to services"); } } @@ -161,8 +182,10 @@ class ContextImpl extends Context { private int mThemeResource = 0; private Resources.Theme mTheme = null; private PackageManager mPackageManager; + private Display mDisplay; // may be null if default display private Context mReceiverRestrictedContext = null; private boolean mRestricted; + private UserHandle mUser; private final Object mSync = new Object(); @@ -294,6 +317,11 @@ class ContextImpl extends Context { return new MediaRouter(ctx); }}); + registerService(BLUETOOTH_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return BluetoothAdapter.getDefaultAdapter(); + }}); + registerService(CLIPBOARD_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new ClipboardManager(ctx.getOuterContext(), @@ -337,6 +365,12 @@ class ContextImpl extends Context { return InputManager.getInstance(); }}); + registerService(DISPLAY_SERVICE, new ServiceFetcher() { + @Override + public Object createService(ContextImpl ctx) { + return new DisplayManager(ctx.getOuterContext()); + }}); + registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return InputMethodManager.getInstance(ctx); @@ -403,7 +437,8 @@ class ContextImpl extends Context { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(POWER_SERVICE); IPowerManager service = IPowerManager.Stub.asInterface(b); - return new PowerManager(service, ctx.mMainThread.getHandler()); + return new PowerManager(ctx.getOuterContext(), + service, ctx.mMainThread.getHandler()); }}); registerService(SEARCH_SERVICE, new ServiceFetcher() { @@ -471,7 +506,7 @@ class ContextImpl extends Context { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(WIFI_SERVICE); IWifiManager service = IWifiManager.Stub.asInterface(b); - return new WifiManager(service, ctx.mMainThread.getHandler()); + return new WifiManager(ctx.getOuterContext(), service); }}); registerService(WIFI_P2P_SERVICE, new ServiceFetcher() { @@ -483,8 +518,21 @@ class ContextImpl extends Context { registerService(WINDOW_SERVICE, new ServiceFetcher() { public Object getService(ContextImpl ctx) { - return WindowManagerImpl.getDefault(ctx.mPackageInfo.mCompatibilityInfo); + Display display = ctx.mDisplay; + if (display == null) { + DisplayManager dm = (DisplayManager)ctx.getOuterContext().getSystemService( + Context.DISPLAY_SERVICE); + display = dm.getDisplay(Display.DEFAULT_DISPLAY); + } + return new WindowManagerImpl(display); }}); + + registerService(USER_SERVICE, new ServiceFetcher() { + public Object getService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(USER_SERVICE); + IUserManager service = IUserManager.Stub.asInterface(b); + return new UserManager(ctx, service); + }}); } static ContextImpl getImpl(Context context) { @@ -503,7 +551,7 @@ class ContextImpl extends Context { @Override public AssetManager getAssets() { - return mResources.getAssets(); + return getResources().getAssets(); } @Override @@ -743,7 +791,7 @@ class ContextImpl extends Context { } if (!mCacheDir.exists()) { if(!mCacheDir.mkdirs()) { - Log.w(TAG, "Unable to create cache directory"); + Log.w(TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath()); return null; } FileUtils.setPermissions( @@ -880,6 +928,12 @@ class ContextImpl extends Context { startActivity(intent, null); } + /** @hide */ + @Override + public void startActivityAsUser(Intent intent, UserHandle user) { + startActivityAsUser(intent, null, user); + } + @Override public void startActivity(Intent intent, Bundle options) { if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { @@ -893,11 +947,38 @@ class ContextImpl extends Context { (Activity)null, intent, -1, options); } + /** @hide */ + @Override + public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { + try { + ActivityManagerNative.getDefault().startActivityAsUser( + mMainThread.getApplicationThread(), intent, + intent.resolveTypeIfNeeded(getContentResolver()), + null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null, options, + user.getIdentifier()); + } catch (RemoteException re) { + } + } + @Override public void startActivities(Intent[] intents) { startActivities(intents, null); } + /** @hide */ + @Override + public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + throw new AndroidRuntimeException( + "Calling startActivities() from outside of an Activity " + + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent." + + " Is this really what you want?"); + } + mMainThread.getInstrumentation().execStartActivitiesAsUser( + getOuterContext(), mMainThread.getApplicationThread(), null, + (Activity)null, intents, options, userHandle.getIdentifier()); + } + @Override public void startActivities(Intent[] intents, Bundle options) { if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { @@ -948,56 +1029,101 @@ class ContextImpl extends Context { ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false, - Binder.getOrigCallingUser()); + getUserId()); } catch (RemoteException e) { } } - /** @hide */ @Override - public void sendBroadcast(Intent intent, int userId) { + public void sendBroadcast(Intent intent, String receiverPermission) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.setAllowFds(false); - ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(), - intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false, - userId); + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, receiverPermission, false, false, + getUserId()); } catch (RemoteException e) { } } @Override - public void sendBroadcast(Intent intent, String receiverPermission) { + public void sendOrderedBroadcast(Intent intent, + String receiverPermission) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.setAllowFds(false); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, receiverPermission, false, false, - Binder.getOrigCallingUser()); + Activity.RESULT_OK, null, null, receiverPermission, true, false, + getUserId()); } catch (RemoteException e) { } } @Override public void sendOrderedBroadcast(Intent intent, + String receiverPermission, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + IIntentReceiver rd = null; + if (resultReceiver != null) { + if (mPackageInfo != null) { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = mPackageInfo.getReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, + mMainThread.getInstrumentation(), false); + } else { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = new LoadedApk.ReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver(); + } + } + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.setAllowFds(false); + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, + initialCode, initialData, initialExtras, receiverPermission, + true, false, getUserId()); + } catch (RemoteException e) { + } + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.setAllowFds(false); + ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(), + intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false, + user.getIdentifier()); + } catch (RemoteException e) { + } + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { intent.setAllowFds(false); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, receiverPermission, true, false, - Binder.getOrigCallingUser()); + Activity.RESULT_OK, null, null, receiverPermission, false, false, + user.getIdentifier()); } catch (RemoteException e) { } } @Override - public void sendOrderedBroadcast(Intent intent, - String receiverPermission, BroadcastReceiver resultReceiver, - Handler scheduler, int initialCode, String initialData, - Bundle initialExtras) { + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { if (mPackageInfo != null) { @@ -1021,7 +1147,7 @@ class ContextImpl extends Context { ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermission, - true, false, Binder.getOrigCallingUser()); + true, false, user.getIdentifier()); } catch (RemoteException e) { } } @@ -1034,7 +1160,7 @@ class ContextImpl extends Context { ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, true, - Binder.getOrigCallingUser()); + getUserId()); } catch (RemoteException e) { } } @@ -1067,7 +1193,7 @@ class ContextImpl extends Context { ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, - true, true, Binder.getOrigCallingUser()); + true, true, getUserId()); } catch (RemoteException e) { } } @@ -1082,7 +1208,67 @@ class ContextImpl extends Context { try { intent.setAllowFds(false); ActivityManagerNative.getDefault().unbroadcastIntent( - mMainThread.getApplicationThread(), intent, Binder.getOrigCallingUser()); + mMainThread.getApplicationThread(), intent, getUserId()); + } catch (RemoteException e) { + } + } + + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.setAllowFds(false); + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, null, false, true, user.getIdentifier()); + } catch (RemoteException e) { + } + } + + @Override + public void sendStickyOrderedBroadcastAsUser(Intent intent, + UserHandle user, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + IIntentReceiver rd = null; + if (resultReceiver != null) { + if (mPackageInfo != null) { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = mPackageInfo.getReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, + mMainThread.getInstrumentation(), false); + } else { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = new LoadedApk.ReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver(); + } + } + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.setAllowFds(false); + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, + initialCode, initialData, initialExtras, null, + true, true, user.getIdentifier()); + } catch (RemoteException e) { + } + } + + @Override + public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + if (resolvedType != null) { + intent = new Intent(intent); + intent.setDataAndType(intent.getData(), resolvedType); + } + try { + intent.setAllowFds(false); + ActivityManagerNative.getDefault().unbroadcastIntent( + mMainThread.getApplicationThread(), intent, user.getIdentifier()); } catch (RemoteException e) { } } @@ -1095,11 +1281,18 @@ class ContextImpl extends Context { @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { - return registerReceiverInternal(receiver, filter, broadcastPermission, - scheduler, getOuterContext()); + return registerReceiverInternal(receiver, getUserId(), + filter, broadcastPermission, scheduler, getOuterContext()); } - private Intent registerReceiverInternal(BroadcastReceiver receiver, + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + return registerReceiverInternal(receiver, user.getIdentifier(), + filter, broadcastPermission, scheduler, getOuterContext()); + } + + private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context) { IIntentReceiver rd = null; @@ -1122,7 +1315,7 @@ class ContextImpl extends Context { try { return ActivityManagerNative.getDefault().registerReceiver( mMainThread.getApplicationThread(), mBasePackageName, - rd, filter, broadcastPermission); + rd, filter, broadcastPermission, userId); } catch (RemoteException e) { return null; } @@ -1144,11 +1337,21 @@ class ContextImpl extends Context { @Override public ComponentName startService(Intent service) { + return startServiceAsUser(service, mUser); + } + + @Override + public boolean stopService(Intent service) { + return stopServiceAsUser(service, mUser); + } + + @Override + public ComponentName startServiceAsUser(Intent service, UserHandle user) { try { service.setAllowFds(false); ComponentName cn = ActivityManagerNative.getDefault().startService( mMainThread.getApplicationThread(), service, - service.resolveTypeIfNeeded(getContentResolver())); + service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier()); if (cn != null && cn.getPackageName().equals("!")) { throw new SecurityException( "Not allowed to start service " + service @@ -1161,12 +1364,12 @@ class ContextImpl extends Context { } @Override - public boolean stopService(Intent service) { + public boolean stopServiceAsUser(Intent service, UserHandle user) { try { service.setAllowFds(false); int res = ActivityManagerNative.getDefault().stopService( mMainThread.getApplicationThread(), service, - service.resolveTypeIfNeeded(getContentResolver())); + service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier()); if (res < 0) { throw new SecurityException( "Not allowed to stop service " + service); @@ -1180,13 +1383,16 @@ class ContextImpl extends Context { @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { - return bindService(service, conn, flags, UserId.getUserId(Process.myUid())); + return bindService(service, conn, flags, UserHandle.getUserId(Process.myUid())); } /** @hide */ @Override - public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) { + public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) { IServiceConnection sd; + if (conn == null) { + throw new IllegalArgumentException("connection is null"); + } if (mPackageInfo != null) { sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), mMainThread.getHandler(), flags); @@ -1204,7 +1410,7 @@ class ContextImpl extends Context { int res = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), - sd, flags, userId); + sd, flags, userHandle); if (res < 0) { throw new SecurityException( "Not allowed to bind to service " + service); @@ -1217,6 +1423,9 @@ class ContextImpl extends Context { @Override public void unbindService(ServiceConnection conn) { + if (conn == null) { + throw new IllegalArgumentException("connection is null"); + } if (mPackageInfo != null) { IServiceConnection sd = mPackageInfo.forgetServiceDispatcher( getOuterContext(), conn); @@ -1237,7 +1446,7 @@ class ContextImpl extends Context { arguments.setAllowFds(false); } return ActivityManagerNative.getDefault().startInstrumentation( - className, profileFile, 0, arguments, null); + className, profileFile, 0, arguments, null, getUserId()); } catch (RemoteException e) { // System has crashed, nothing we can do. } @@ -1312,7 +1521,7 @@ class ContextImpl extends Context { (message != null ? (message + ": ") : "") + (selfToo ? "Neither user " + uid + " nor current process has " - : "User " + uid + " does not have ") + + : "uid " + uid + " does not have ") + permission + "."); } @@ -1484,7 +1693,13 @@ class ContextImpl extends Context { @Override public Context createPackageContext(String packageName, int flags) - throws PackageManager.NameNotFoundException { + throws NameNotFoundException { + return createPackageContextAsUser(packageName, flags, Process.myUserHandle()); + } + + @Override + public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) + throws NameNotFoundException { if (packageName.equals("system") || packageName.equals("android")) { final ContextImpl context = new ContextImpl(mMainThread.getSystemContext()); context.mBasePackageName = mBasePackageName; @@ -1492,11 +1707,12 @@ class ContextImpl extends Context { } LoadedApk pi = - mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags); + mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags, + user.getIdentifier()); if (pi != null) { ContextImpl c = new ContextImpl(); c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED; - c.init(pi, null, mMainThread, mResources, mBasePackageName); + c.init(pi, null, mMainThread, mResources, mBasePackageName, user); if (c.mResources != null) { return c; } @@ -1508,10 +1724,55 @@ class ContextImpl extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + if (overrideConfiguration == null) { + throw new IllegalArgumentException("overrideConfiguration must not be null"); + } + + ContextImpl c = new ContextImpl(); + c.init(mPackageInfo, null, mMainThread); + c.mResources = mMainThread.getTopLevelResources( + mPackageInfo.getResDir(), + getDisplayId(), overrideConfiguration, + mResources.getCompatibilityInfo()); + return c; + } + + @Override + public Context createDisplayContext(Display display) { + if (display == null) { + throw new IllegalArgumentException("display must not be null"); + } + + int displayId = display.getDisplayId(); + CompatibilityInfo ci = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + CompatibilityInfoHolder cih = getCompatibilityInfo(displayId); + if (cih != null) { + ci = cih.get(); + } + + ContextImpl context = new ContextImpl(); + context.init(mPackageInfo, null, mMainThread); + context.mDisplay = display; + context.mResources = mMainThread.getTopLevelResources( + mPackageInfo.getResDir(), displayId, null, ci); + return context; + } + + private int getDisplayId() { + return mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; + } + + @Override public boolean isRestricted() { return mRestricted; } + @Override + public CompatibilityInfoHolder getCompatibilityInfo(int displayId) { + return displayId == Display.DEFAULT_DISPLAY ? mPackageInfo.mCompatibilityInfo : null; + } + private File getDataDirFile() { if (mPackageInfo != null) { return mPackageInfo.getDataDirFile(); @@ -1531,9 +1792,14 @@ class ContextImpl extends Context { return file; } + /** {@hide} */ + public int getUserId() { + return mUser.getIdentifier(); + } + static ContextImpl createSystemContext(ActivityThread mainThread) { - ContextImpl context = new ContextImpl(); - context.init(Resources.getSystem(), mainThread); + final ContextImpl context = new ContextImpl(); + context.init(Resources.getSystem(), mainThread, Process.myUserHandle()); return context; } @@ -1553,17 +1819,17 @@ class ContextImpl extends Context { mResources = context.mResources; mMainThread = context.mMainThread; mContentResolver = context.mContentResolver; + mUser = context.mUser; + mDisplay = context.mDisplay; mOuterContext = this; } - final void init(LoadedApk packageInfo, - IBinder activityToken, ActivityThread mainThread) { - init(packageInfo, activityToken, mainThread, null, null); + final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) { + init(packageInfo, activityToken, mainThread, null, null, Process.myUserHandle()); } - final void init(LoadedApk packageInfo, - IBinder activityToken, ActivityThread mainThread, - Resources container, String basePackageName) { + final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread, + Resources container, String basePackageName, UserHandle user) { mPackageInfo = packageInfo; mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName; mResources = mPackageInfo.getResources(mainThread); @@ -1576,20 +1842,22 @@ class ContextImpl extends Context { " compatiblity info:" + container.getDisplayMetrics()); } mResources = mainThread.getTopLevelResources( - mPackageInfo.getResDir(), container.getCompatibilityInfo()); + mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY, + null, container.getCompatibilityInfo()); } mMainThread = mainThread; - mContentResolver = new ApplicationContentResolver(this, mainThread); - - setActivityToken(activityToken); + mActivityToken = activityToken; + mContentResolver = new ApplicationContentResolver(this, mainThread, user); + mUser = user; } - final void init(Resources resources, ActivityThread mainThread) { + final void init(Resources resources, ActivityThread mainThread, UserHandle user) { mPackageInfo = null; mBasePackageName = null; mResources = resources; mMainThread = mainThread; - mContentResolver = new ApplicationContentResolver(this, mainThread); + mContentResolver = new ApplicationContentResolver(this, mainThread, user); + mUser = user; } final void scheduleFinalCleanup(String who, String what) { @@ -1608,10 +1876,6 @@ class ContextImpl extends Context { return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext()); } - final void setActivityToken(IBinder token) { - mActivityToken = token; - } - final void setOuterContext(Context context) { mOuterContext = context; } @@ -1678,19 +1942,24 @@ class ContextImpl extends Context { // ---------------------------------------------------------------------- private static final class ApplicationContentResolver extends ContentResolver { - public ApplicationContentResolver(Context context, ActivityThread mainThread) { + private final ActivityThread mMainThread; + private final UserHandle mUser; + + public ApplicationContentResolver( + Context context, ActivityThread mainThread, UserHandle user) { super(context); - mMainThread = mainThread; + mMainThread = Preconditions.checkNotNull(mainThread); + mUser = Preconditions.checkNotNull(user); } @Override - protected IContentProvider acquireProvider(Context context, String name) { - return mMainThread.acquireProvider(context, name, true); + protected IContentProvider acquireProvider(Context context, String auth) { + return mMainThread.acquireProvider(context, auth, mUser.getIdentifier(), true); } @Override - protected IContentProvider acquireExistingProvider(Context context, String name) { - return mMainThread.acquireExistingProvider(context, name, true); + protected IContentProvider acquireExistingProvider(Context context, String auth) { + return mMainThread.acquireExistingProvider(context, auth, mUser.getIdentifier(), true); } @Override @@ -1699,8 +1968,8 @@ class ContextImpl extends Context { } @Override - protected IContentProvider acquireUnstableProvider(Context c, String name) { - return mMainThread.acquireProvider(c, name, false); + protected IContentProvider acquireUnstableProvider(Context c, String auth) { + return mMainThread.acquireProvider(c, auth, mUser.getIdentifier(), false); } @Override @@ -1712,7 +1981,5 @@ class ContextImpl extends Context { public void unstableProviderDied(IContentProvider icp) { mMainThread.handleUnstableProviderDied(icp.asBinder(), true); } - - private final ActivityThread mMainThread; } } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 2cc3b02..b3d99c5 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -147,15 +147,19 @@ public class Dialog implements DialogInterface, Window.Callback, this(context, theme, true); } - Dialog(Context context, int theme, boolean createContextWrapper) { - if (theme == 0) { - TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, - outValue, true); - theme = outValue.resourceId; + Dialog(Context context, int theme, boolean createContextThemeWrapper) { + if (createContextThemeWrapper) { + if (theme == 0) { + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, + outValue, true); + theme = outValue.resourceId; + } + mContext = new ContextThemeWrapper(context, theme); + } else { + mContext = context; } - mContext = createContextWrapper ? new ContextThemeWrapper(context, theme) : context; mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; @@ -164,7 +168,7 @@ public class Dialog implements DialogInterface, Window.Callback, w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } - + /** * @deprecated * @hide @@ -1106,10 +1110,12 @@ public class Dialog implements DialogInterface, Window.Callback, /** * Set a listener to be invoked when the dialog is canceled. - * <p> - * This will only be invoked when the dialog is canceled, if the creator - * needs to know when it is dismissed in general, use - * {@link #setOnDismissListener}. + * + * <p>This will only be invoked when the dialog is canceled. + * Cancel events alone will not capture all ways that + * the dialog might be dismissed. If the creator needs + * to know when a dialog is dismissed in general, use + * {@link #setOnDismissListener}.</p> * * @param listener The {@link DialogInterface.OnCancelListener} to use. */ diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 0b1c524..6cf4dd0 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -1098,8 +1098,8 @@ public class DownloadManager { */ public static Long getMaxBytesOverMobile(Context context) { try { - return Settings.Secure.getLong(context.getContentResolver(), - Settings.Secure.DOWNLOAD_MAX_BYTES_OVER_MOBILE); + return Settings.Global.getLong(context.getContentResolver(), + Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE); } catch (SettingNotFoundException exc) { return null; } @@ -1116,8 +1116,8 @@ public class DownloadManager { */ public static Long getRecommendedMaxBytesOverMobile(Context context) { try { - return Settings.Secure.getLong(context.getContentResolver(), - Settings.Secure.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); + return Settings.Global.getLong(context.getContentResolver(), + Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); } catch (SettingNotFoundException exc) { return null; } diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 28876d3..c5a382d 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -85,7 +85,7 @@ final class FragmentState implements Parcelable { mSavedFragmentState = in.readBundle(); } - public Fragment instantiate(Activity activity) { + public Fragment instantiate(Activity activity, Fragment parent) { if (mInstance != null) { return mInstance; } @@ -100,7 +100,7 @@ final class FragmentState implements Parcelable { mSavedFragmentState.setClassLoader(activity.getClassLoader()); mInstance.mSavedFragmentState = mSavedFragmentState; } - mInstance.setIndex(mIndex); + mInstance.setIndex(mIndex, parent); mInstance.mFromLayout = mFromLayout; mInstance.mRestored = true; mInstance.mFragmentId = mFragmentId; @@ -207,6 +207,8 @@ final class FragmentState implements Parcelable { * with the fragment. * <li> {@link #onActivityCreated} tells the fragment that its activity has * completed its own {@link Activity#onCreate Activity.onCreate()}. + * <li> {@link #onViewStateRestored} tells the fragment that all of the saved + * state of its view hierarchy has been restored. * <li> {@link #onStart} makes the fragment visible to the user (based on its * containing activity being started). * <li> {@link #onResume} makes the fragment interacting with the user (based on its @@ -412,7 +414,13 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // Activity this fragment is attached to. Activity mActivity; - + + // Private fragment manager for child fragments inside of this one. + FragmentManagerImpl mChildFragmentManager; + + // If this Fragment is contained in another Fragment, this is that container. + Fragment mParentFragment; + // The optional identifier for this fragment -- either the container ID if it // was dynamically added to the view hierarchy, or the ID supplied in // layout. @@ -595,18 +603,28 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } } - final void restoreViewState() { + final void restoreViewState(Bundle savedInstanceState) { if (mSavedViewState != null) { mView.restoreHierarchyState(mSavedViewState); mSavedViewState = null; } + mCalled = false; + onViewStateRestored(savedInstanceState); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onViewStateRestored()"); + } } - - final void setIndex(int index) { + + final void setIndex(int index, Fragment parent) { mIndex = index; - mWho = "android:fragment:" + mIndex; - } - + if (parent != null) { + mWho = parent.mWho + ":" + mIndex; + } else { + mWho = "android:fragment:" + mIndex; + } + } + final boolean isInBackStack() { return mBackStackNesting > 0; } @@ -785,12 +803,43 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * before {@link #getActivity()}, during the time from when the fragment is * placed in a {@link FragmentTransaction} until it is committed and * attached to its activity. + * + * <p>If this Fragment is a child of another Fragment, the FragmentManager + * returned here will be the parent's {@link #getChildFragmentManager()}. */ final public FragmentManager getFragmentManager() { return mFragmentManager; } /** + * Return a private FragmentManager for placing and managing Fragments + * inside of this Fragment. + */ + final public FragmentManager getChildFragmentManager() { + if (mChildFragmentManager == null) { + instantiateChildFragmentManager(); + if (mState >= RESUMED) { + mChildFragmentManager.dispatchResume(); + } else if (mState >= STARTED) { + mChildFragmentManager.dispatchStart(); + } else if (mState >= ACTIVITY_CREATED) { + mChildFragmentManager.dispatchActivityCreated(); + } else if (mState >= CREATED) { + mChildFragmentManager.dispatchCreate(); + } + } + return mChildFragmentManager; + } + + /** + * Returns the parent Fragment containing this Fragment. If this Fragment + * is attached directly to an Activity, returns null. + */ + final public Fragment getParentFragment() { + return mParentFragment; + } + + /** * Return true if the fragment is currently added to its activity. */ final public boolean isAdded() { @@ -880,6 +929,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * </ul> */ public void setRetainInstance(boolean retain) { + if (retain && mParentFragment != null) { + throw new IllegalStateException( + "Can't retain fragements that are nested in other fragments"); + } mRetainInstance = retain; } @@ -961,7 +1014,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, true); + mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true); return mLoaderManager; } @@ -1135,20 +1188,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene public void onCreate(Bundle savedInstanceState) { mCalled = true; } - - /** - * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} - * has returned, but before any saved state has been restored in to the view. - * This gives subclasses a chance to initialize themselves once - * they know their view hierarchy has been completely created. The fragment's - * view hierarchy is not however attached to its parent at this point. - * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. - * @param savedInstanceState If non-null, this fragment is being re-constructed - * from a previous saved state as given here. - */ - public void onViewCreated(View view, Bundle savedInstanceState) { - } - + /** * Called to have the fragment instantiate its user interface view. * This is optional, and non-graphical fragments can return null (which @@ -1172,6 +1212,19 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene Bundle savedInstanceState) { return null; } + + /** + * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} + * has returned, but before any saved state has been restored in to the view. + * This gives subclasses a chance to initialize themselves once + * they know their view hierarchy has been completely created. The fragment's + * view hierarchy is not however attached to its parent at this point. + * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. + * @param savedInstanceState If non-null, this fragment is being re-constructed + * from a previous saved state as given here. + */ + public void onViewCreated(View view, Bundle savedInstanceState) { + } /** * Get the root view for the fragment's layout (the one returned by {@link #onCreateView}), @@ -1191,15 +1244,30 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * {@link #setRetainInstance(boolean)} to retain their instance, * as this callback tells the fragment when it is fully associated with * the new activity instance. This is called after {@link #onCreateView} - * and before {@link #onStart()}. - * + * and before {@link #onViewStateRestored(Bundle)}. + * * @param savedInstanceState If the fragment is being re-created from * a previous saved state, this is the state. */ public void onActivityCreated(Bundle savedInstanceState) { mCalled = true; } - + + /** + * Called when all saved state has been restored into the view hierarchy + * of the fragment. This can be used to do initialization based on saved + * state that you are letting the view hierarchy track itself, such as + * whether check box widgets are currently checked. This is called + * after {@link #onActivityCreated(Bundle)} and before + * {@link #onStart()}. + * + * @param savedInstanceState If the fragment is being re-created from + * a previous saved state, this is the state. + */ + public void onViewStateRestored(Bundle savedInstanceState) { + mCalled = true; + } + /** * Called when the Fragment is visible to the user. This is generally * tied to {@link Activity#onStart() Activity.onStart} of the containing @@ -1212,7 +1280,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene mLoadersStarted = true; if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false); + mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doStart(); @@ -1305,7 +1373,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // + " mLoaderManager=" + mLoaderManager); if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false); + mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doDestroy(); @@ -1530,6 +1598,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene writer.print(prefix); writer.print("mActivity="); writer.println(mActivity); } + if (mParentFragment != null) { + writer.print(prefix); writer.print("mParentFragment="); + writer.println(mParentFragment); + } if (mArguments != null) { writer.print(prefix); writer.print("mArguments="); writer.println(mArguments); } @@ -1564,23 +1636,244 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene writer.print(prefix); writer.println("Loader Manager:"); mLoaderManager.dump(prefix + " ", fd, writer, args); } + if (mChildFragmentManager != null) { + writer.print(prefix); writer.println("Child " + mChildFragmentManager + ":"); + mChildFragmentManager.dump(prefix + " ", fd, writer, args); + } + } + + Fragment findFragmentByWho(String who) { + if (who.equals(mWho)) { + return this; + } + if (mChildFragmentManager != null) { + return mChildFragmentManager.findFragmentByWho(who); + } + return null; + } + + void instantiateChildFragmentManager() { + mChildFragmentManager = new FragmentManagerImpl(); + mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() { + @Override + public View findViewById(int id) { + if (mView == null) { + throw new IllegalStateException("Fragment does not have a view"); + } + return mView.findViewById(id); + } + }, this); + } + + void performCreate(Bundle savedInstanceState) { + if (mChildFragmentManager != null) { + mChildFragmentManager.noteStateNotSaved(); + } + mCalled = false; + onCreate(savedInstanceState); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onCreate()"); + } + if (savedInstanceState != null) { + Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG); + if (p != null) { + if (mChildFragmentManager == null) { + instantiateChildFragmentManager(); + } + mChildFragmentManager.restoreAllState(p, null); + mChildFragmentManager.dispatchCreate(); + } + } + } + + View performCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + if (mChildFragmentManager != null) { + mChildFragmentManager.noteStateNotSaved(); + } + return onCreateView(inflater, container, savedInstanceState); + } + + void performActivityCreated(Bundle savedInstanceState) { + if (mChildFragmentManager != null) { + mChildFragmentManager.noteStateNotSaved(); + } + mCalled = false; + onActivityCreated(savedInstanceState); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onActivityCreated()"); + } + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchActivityCreated(); + } } void performStart() { + if (mChildFragmentManager != null) { + mChildFragmentManager.noteStateNotSaved(); + mChildFragmentManager.execPendingActions(); + } + mCalled = false; onStart(); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onStart()"); + } + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchStart(); + } if (mLoaderManager != null) { mLoaderManager.doReportStart(); } } + void performResume() { + if (mChildFragmentManager != null) { + mChildFragmentManager.noteStateNotSaved(); + mChildFragmentManager.execPendingActions(); + } + mCalled = false; + onResume(); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onResume()"); + } + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchResume(); + mChildFragmentManager.execPendingActions(); + } + } + + void performConfigurationChanged(Configuration newConfig) { + onConfigurationChanged(newConfig); + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchConfigurationChanged(newConfig); + } + } + + void performLowMemory() { + onLowMemory(); + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchLowMemory(); + } + } + + void performTrimMemory(int level) { + onTrimMemory(level); + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchTrimMemory(level); + } + } + + boolean performCreateOptionsMenu(Menu menu, MenuInflater inflater) { + boolean show = false; + if (!mHidden) { + if (mHasMenu && mMenuVisible) { + show = true; + onCreateOptionsMenu(menu, inflater); + } + if (mChildFragmentManager != null) { + show |= mChildFragmentManager.dispatchCreateOptionsMenu(menu, inflater); + } + } + return show; + } + + boolean performPrepareOptionsMenu(Menu menu) { + boolean show = false; + if (!mHidden) { + if (mHasMenu && mMenuVisible) { + show = true; + onPrepareOptionsMenu(menu); + } + if (mChildFragmentManager != null) { + show |= mChildFragmentManager.dispatchPrepareOptionsMenu(menu); + } + } + return show; + } + + boolean performOptionsItemSelected(MenuItem item) { + if (!mHidden) { + if (mHasMenu && mMenuVisible) { + if (onOptionsItemSelected(item)) { + return true; + } + } + if (mChildFragmentManager != null) { + if (mChildFragmentManager.dispatchOptionsItemSelected(item)) { + return true; + } + } + } + return false; + } + + boolean performContextItemSelected(MenuItem item) { + if (!mHidden) { + if (onContextItemSelected(item)) { + return true; + } + if (mChildFragmentManager != null) { + if (mChildFragmentManager.dispatchContextItemSelected(item)) { + return true; + } + } + } + return false; + } + + void performOptionsMenuClosed(Menu menu) { + if (!mHidden) { + if (mHasMenu && mMenuVisible) { + onOptionsMenuClosed(menu); + } + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchOptionsMenuClosed(menu); + } + } + } + + void performSaveInstanceState(Bundle outState) { + onSaveInstanceState(outState); + if (mChildFragmentManager != null) { + Parcelable p = mChildFragmentManager.saveAllState(); + if (p != null) { + outState.putParcelable(Activity.FRAGMENTS_TAG, p); + } + } + } + + void performPause() { + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchPause(); + } + mCalled = false; + onPause(); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onPause()"); + } + } + void performStop() { + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchStop(); + } + mCalled = false; onStop(); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onStop()"); + } if (mLoadersStarted) { mLoadersStarted = false; if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false); + mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { if (mActivity == null || !mActivity.mChangingConfigurations) { @@ -1593,9 +1886,29 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } void performDestroyView() { + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchDestroyView(); + } + mCalled = false; onDestroyView(); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onDestroyView()"); + } if (mLoaderManager != null) { mLoaderManager.doReportNextStart(); } } + + void performDestroy() { + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchDestroy(); + } + mCalled = false; + onDestroy(); + if (!mCalled) { + throw new SuperNotCalledException("Fragment " + this + + " did not call through to super.onDestroy()"); + } + } } diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 39e2423..e983299 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -22,6 +22,7 @@ import android.animation.AnimatorListenerAdapter; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Bundle; +import android.os.Debug; import android.os.Handler; import android.os.Looper; import android.os.Parcel; @@ -29,7 +30,6 @@ import android.os.Parcelable; import android.util.DebugUtils; import android.util.Log; import android.util.LogWriter; -import android.util.Slog; import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; @@ -380,6 +380,13 @@ final class FragmentManagerState implements Parcelable { } /** + * Callbacks from FragmentManagerImpl to its container. + */ +interface FragmentContainer { + public View findViewById(int id); +} + +/** * Container for fragments associated with an activity. */ final class FragmentManagerImpl extends FragmentManager { @@ -409,6 +416,8 @@ final class FragmentManagerImpl extends FragmentManager { int mCurState = Fragment.INITIALIZING; Activity mActivity; + FragmentContainer mContainer; + Fragment mParent; boolean mNeedMenuInvalidate; boolean mStateSaved; @@ -427,6 +436,28 @@ final class FragmentManagerImpl extends FragmentManager { } }; + private void throwException(RuntimeException ex) { + Log.e(TAG, ex.getMessage()); + LogWriter logw = new LogWriter(Log.ERROR, TAG); + PrintWriter pw = new PrintWriter(logw); + if (mActivity != null) { + Log.e(TAG, "Activity state:"); + try { + mActivity.dump(" ", null, pw, new String[] { }); + } catch (Exception e) { + Log.e(TAG, "Failed dumping state", e); + } + } else { + Log.e(TAG, "Fragment manager state:"); + try { + dump(" ", null, pw, new String[] { }); + } catch (Exception e) { + Log.e(TAG, "Failed dumping state", e); + } + } + throw ex; + } + @Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); @@ -519,8 +550,8 @@ final class FragmentManagerImpl extends FragmentManager { @Override public void putFragment(Bundle bundle, String key, Fragment fragment) { if (fragment.mIndex < 0) { - throw new IllegalStateException("Fragment " + fragment - + " is not currently in the FragmentManager"); + throwException(new IllegalStateException("Fragment " + fragment + + " is not currently in the FragmentManager")); } bundle.putInt(key, fragment.mIndex); } @@ -532,13 +563,13 @@ final class FragmentManagerImpl extends FragmentManager { return null; } if (index >= mActive.size()) { - throw new IllegalStateException("Fragement no longer exists for key " - + key + ": index " + index); + throwException(new IllegalStateException("Fragement no longer exists for key " + + key + ": index " + index)); } Fragment f = mActive.get(index); if (f == null) { - throw new IllegalStateException("Fragement no longer exists for key " - + key + ": index " + index); + throwException(new IllegalStateException("Fragement no longer exists for key " + + key + ": index " + index)); } return f; } @@ -546,8 +577,8 @@ final class FragmentManagerImpl extends FragmentManager { @Override public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) { if (fragment.mIndex < 0) { - throw new IllegalStateException("Fragment " + fragment - + " is not currently in the FragmentManager"); + throwException(new IllegalStateException("Fragment " + fragment + + " is not currently in the FragmentManager")); } if (fragment.mState > Fragment.INITIALIZING) { Bundle result = saveFragmentBasicState(fragment); @@ -562,7 +593,11 @@ final class FragmentManagerImpl extends FragmentManager { sb.append("FragmentManager{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" in "); - DebugUtils.buildShortClassTag(mActivity, sb); + if (mParent != null) { + DebugUtils.buildShortClassTag(mParent, sb); + } else { + DebugUtils.buildShortClassTag(mActivity, sb); + } sb.append("}}"); return sb.toString(); } @@ -658,6 +693,11 @@ final class FragmentManagerImpl extends FragmentManager { } writer.print(prefix); writer.println("FragmentManager misc state:"); + writer.print(prefix); writer.print(" mActivity="); writer.println(mActivity); + writer.print(prefix); writer.print(" mContainer="); writer.println(mContainer); + if (mParent != null) { + writer.print(prefix); writer.print(" mParent="); writer.println(mParent); + } writer.print(prefix); writer.print(" mCurState="); writer.print(mCurState); writer.print(" mStateSaved="); writer.print(mStateSaved); writer.print(" mDestroyed="); writer.println(mDestroyed); @@ -732,8 +772,12 @@ final class FragmentManagerImpl extends FragmentManager { void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { + if (DEBUG && false) Log.v(TAG, "moveToState: " + f + + " oldState=" + f.mState + " newState=" + newState + + " mRemoving=" + f.mRemoving + " Callers=" + Debug.getCallers(5)); + // Fragments that are not currently added will sit in the onCreate() state. - if (!f.mAdded && newState > Fragment.CREATED) { + if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) { newState = Fragment.CREATED; } if (f.mRemoving && newState > f.mState) { @@ -782,30 +826,29 @@ final class FragmentManagerImpl extends FragmentManager { } } f.mActivity = mActivity; - f.mFragmentManager = mActivity.mFragments; + f.mParentFragment = mParent; + f.mFragmentManager = mParent != null + ? mParent.mChildFragmentManager : mActivity.mFragments; f.mCalled = false; f.onAttach(mActivity); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onAttach()"); } - mActivity.onAttachFragment(f); - + if (f.mParentFragment == null) { + mActivity.onAttachFragment(f); + } + if (!f.mRetaining) { - f.mCalled = false; - f.onCreate(f.mSavedFragmentState); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onCreate()"); - } + f.performCreate(f.mSavedFragmentState); } f.mRetaining = false; if (f.mFromLayout) { // For fragments that are part of the content view // layout, we need to instantiate the view immediately // and the inflater will take care of adding it. - f.mView = f.onCreateView(f.getLayoutInflater(f.mSavedFragmentState), - null, f.mSavedFragmentState); + f.mView = f.performCreateView(f.getLayoutInflater( + f.mSavedFragmentState), null, f.mSavedFragmentState); if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); if (f.mHidden) f.mView.setVisibility(View.GONE); @@ -818,16 +861,18 @@ final class FragmentManagerImpl extends FragmentManager { if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { - container = (ViewGroup)mActivity.findViewById(f.mContainerId); + container = (ViewGroup)mContainer.findViewById(f.mContainerId); if (container == null && !f.mRestored) { - throw new IllegalArgumentException("No view found for id 0x" - + Integer.toHexString(f.mContainerId) - + " for fragment " + f); + throwException(new IllegalArgumentException( + "No view found for id 0x" + + Integer.toHexString(f.mContainerId) + " (" + + f.getResources().getResourceName(f.mContainerId) + + ") for fragment " + f)); } } f.mContainer = container; - f.mView = f.onCreateView(f.getLayoutInflater(f.mSavedFragmentState), - container, f.mSavedFragmentState); + f.mView = f.performCreateView(f.getLayoutInflater( + f.mSavedFragmentState), container, f.mSavedFragmentState); if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); if (container != null) { @@ -843,15 +888,10 @@ final class FragmentManagerImpl extends FragmentManager { f.onViewCreated(f.mView, f.mSavedFragmentState); } } - - f.mCalled = false; - f.onActivityCreated(f.mSavedFragmentState); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onActivityCreated()"); - } + + f.performActivityCreated(f.mSavedFragmentState); if (f.mView != null) { - f.restoreViewState(); + f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; } @@ -859,23 +899,13 @@ final class FragmentManagerImpl extends FragmentManager { case Fragment.STOPPED: if (newState > Fragment.STOPPED) { if (DEBUG) Log.v(TAG, "moveto STARTED: " + f); - f.mCalled = false; f.performStart(); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onStart()"); - } } case Fragment.STARTED: if (newState > Fragment.STARTED) { if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f); - f.mCalled = false; f.mResumed = true; - f.onResume(); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onResume()"); - } + f.performResume(); // Get rid of this in case we saved it and never needed it. f.mSavedFragmentState = null; f.mSavedViewState = null; @@ -886,23 +916,13 @@ final class FragmentManagerImpl extends FragmentManager { case Fragment.RESUMED: if (newState < Fragment.RESUMED) { if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f); - f.mCalled = false; - f.onPause(); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onPause()"); - } + f.performPause(); f.mResumed = false; } case Fragment.STARTED: if (newState < Fragment.STARTED) { if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f); - f.mCalled = false; f.performStop(); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onStop()"); - } } case Fragment.STOPPED: case Fragment.ACTIVITY_CREATED: @@ -915,12 +935,7 @@ final class FragmentManagerImpl extends FragmentManager { saveFragmentViewState(f); } } - f.mCalled = false; f.performDestroyView(); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onDestroyView()"); - } if (f.mView != null && f.mContainer != null) { Animator anim = null; if (mCurState > Fragment.INITIALIZING && !mDestroyed) { @@ -979,12 +994,7 @@ final class FragmentManagerImpl extends FragmentManager { } else { if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f); if (!f.mRetaining) { - f.mCalled = false; - f.onDestroy(); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onDestroy()"); - } + f.performDestroy(); } f.mCalled = false; @@ -998,6 +1008,7 @@ final class FragmentManagerImpl extends FragmentManager { makeInactive(f); } else { f.mActivity = null; + f.mParentFragment = null; f.mFragmentManager = null; } } @@ -1021,11 +1032,11 @@ final class FragmentManagerImpl extends FragmentManager { if (mActivity == null && newState != Fragment.INITIALIZING) { throw new IllegalStateException("No activity"); } - + if (!always && mCurState == newState) { return; } - + mCurState = newState; if (mActive != null) { boolean loadersRunning = false; @@ -1070,11 +1081,11 @@ final class FragmentManagerImpl extends FragmentManager { if (mActive == null) { mActive = new ArrayList<Fragment>(); } - f.setIndex(mActive.size()); + f.setIndex(mActive.size(), mParent); mActive.add(f); } else { - f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1)); + f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent); mActive.set(f.mIndex, f); } if (DEBUG) Log.v(TAG, "Allocated fragment index " + f); @@ -1091,7 +1102,7 @@ final class FragmentManagerImpl extends FragmentManager { mAvailIndices = new ArrayList<Integer>(); } mAvailIndices.add(f.mIndex); - mActivity.invalidateFragmentIndex(f.mIndex); + mActivity.invalidateFragment(f.mWho); f.initState(); } @@ -1102,6 +1113,9 @@ final class FragmentManagerImpl extends FragmentManager { if (DEBUG) Log.v(TAG, "add: " + fragment); makeActive(fragment); if (!fragment.mDetached) { + if (mAdded.contains(fragment)) { + throw new IllegalStateException("Fragment already added: " + fragment); + } mAdded.add(fragment); fragment.mAdded = true; fragment.mRemoving = false; @@ -1118,6 +1132,14 @@ final class FragmentManagerImpl extends FragmentManager { if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); final boolean inactive = !fragment.isInBackStack(); if (!fragment.mDetached || inactive) { + if (false) { + // Would be nice to catch a bad remove here, but we need + // time to test this to make sure we aren't crashes cases + // where it is not a problem. + if (!mAdded.contains(fragment)) { + throw new IllegalStateException("Fragment not added: " + fragment); + } + } if (mAdded != null) { mAdded.remove(fragment); } @@ -1190,6 +1212,7 @@ final class FragmentManagerImpl extends FragmentManager { if (fragment.mAdded) { // We are not already in back stack, so need to remove the fragment. if (mAdded != null) { + if (DEBUG) Log.v(TAG, "remove from detach: " + fragment); mAdded.remove(fragment); } if (fragment.mHasMenu && fragment.mMenuVisible) { @@ -1209,6 +1232,10 @@ final class FragmentManagerImpl extends FragmentManager { if (mAdded == null) { mAdded = new ArrayList<Fragment>(); } + if (mAdded.contains(fragment)) { + throw new IllegalStateException("Fragment already added: " + fragment); + } + if (DEBUG) Log.v(TAG, "add from attach: " + fragment); mAdded.add(fragment); fragment.mAdded = true; if (fragment.mHasMenu && fragment.mMenuVisible) { @@ -1267,7 +1294,7 @@ final class FragmentManagerImpl extends FragmentManager { if (mActive != null && who != null) { for (int i=mActive.size()-1; i>=0; i--) { Fragment f = mActive.get(i); - if (f != null && who.equals(f.mWho)) { + if (f != null && (f=f.findFragmentByWho(who)) != null) { return f; } } @@ -1537,7 +1564,7 @@ final class FragmentManagerImpl extends FragmentManager { if (mStateBundle == null) { mStateBundle = new Bundle(); } - f.onSaveInstanceState(mStateBundle); + f.performSaveInstanceState(mStateBundle); if (!mStateBundle.isEmpty()) { result = mStateBundle; mStateBundle = null; @@ -1583,12 +1610,9 @@ final class FragmentManagerImpl extends FragmentManager { Fragment f = mActive.get(i); if (f != null) { if (f.mIndex < 0) { - String msg = "Failure saving state: active " + f - + " has cleared index: " + f.mIndex; - Slog.e(TAG, msg); - dump(" ", null, new PrintWriter(new LogWriter( - Log.ERROR, TAG, Log.LOG_ID_SYSTEM)), new String[] { }); - throw new IllegalStateException(msg); + throwException(new IllegalStateException( + "Failure saving state: active " + f + + " has cleared index: " + f.mIndex)); } haveFragments = true; @@ -1601,12 +1625,9 @@ final class FragmentManagerImpl extends FragmentManager { if (f.mTarget != null) { if (f.mTarget.mIndex < 0) { - String msg = "Failure saving state: " + f - + " has target not in fragment manager: " + f.mTarget; - Slog.e(TAG, msg); - dump(" ", null, new PrintWriter(new LogWriter( - Log.ERROR, TAG, Log.LOG_ID_SYSTEM)), new String[] { }); - throw new IllegalStateException(msg); + throwException(new IllegalStateException( + "Failure saving state: " + f + + " has target not in fragment manager: " + f.mTarget)); } if (fs.mSavedFragmentState == null) { fs.mSavedFragmentState = new Bundle(); @@ -1645,12 +1666,9 @@ final class FragmentManagerImpl extends FragmentManager { for (int i=0; i<N; i++) { added[i] = mAdded.get(i).mIndex; if (added[i] < 0) { - String msg = "Failure saving state: active " + mAdded.get(i) - + " has cleared index: " + added[i]; - Slog.e(TAG, msg); - dump(" ", null, new PrintWriter(new LogWriter( - Log.ERROR, TAG, Log.LOG_ID_SYSTEM)), new String[] { }); - throw new IllegalStateException(msg); + throwException(new IllegalStateException( + "Failure saving state: active " + mAdded.get(i) + + " has cleared index: " + added[i])); } if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i + ": " + mAdded.get(i)); @@ -1715,20 +1733,19 @@ final class FragmentManagerImpl extends FragmentManager { for (int i=0; i<fms.mActive.length; i++) { FragmentState fs = fms.mActive[i]; if (fs != null) { - Fragment f = fs.instantiate(mActivity); - if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": " + f); + Fragment f = fs.instantiate(mActivity, mParent); + if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f); mActive.add(f); // Now that the fragment is instantiated (or came from being // retained above), clear mInstance in case we end up re-restoring // from this FragmentState again. fs.mInstance = null; } else { - if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": (null)"); mActive.add(null); if (mAvailIndices == null) { mAvailIndices = new ArrayList<Integer>(); } - if (DEBUG) Log.v(TAG, "restoreAllState: adding avail #" + i); + if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i); mAvailIndices.add(i); } } @@ -1755,11 +1772,14 @@ final class FragmentManagerImpl extends FragmentManager { for (int i=0; i<fms.mAdded.length; i++) { Fragment f = mActive.get(fms.mAdded[i]); if (f == null) { - throw new IllegalStateException( - "No instantiated fragment for index #" + fms.mAdded[i]); + throwException(new IllegalStateException( + "No instantiated fragment for index #" + fms.mAdded[i])); } f.mAdded = true; - if (DEBUG) Log.v(TAG, "restoreAllState: making added #" + i + ": " + f); + if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ": " + f); + if (mAdded.contains(f)) { + throw new IllegalStateException("Already added!"); + } mAdded.add(f); } } else { @@ -1771,8 +1791,13 @@ final class FragmentManagerImpl extends FragmentManager { mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length); for (int i=0; i<fms.mBackStack.length; i++) { BackStackRecord bse = fms.mBackStack[i].instantiate(this); - if (DEBUG) Log.v(TAG, "restoreAllState: adding bse #" + i + if (DEBUG) { + Log.v(TAG, "restoreAllState: back stack #" + i + " (index " + bse.mIndex + "): " + bse); + LogWriter logw = new LogWriter(Log.VERBOSE, TAG); + PrintWriter pw = new PrintWriter(logw); + bse.dump(" ", pw, false); + } mBackStack.add(bse); if (bse.mIndex >= 0) { setBackStackIndex(bse.mIndex, bse); @@ -1783,9 +1808,11 @@ final class FragmentManagerImpl extends FragmentManager { } } - public void attachActivity(Activity activity) { - if (mActivity != null) throw new IllegalStateException(); + public void attachActivity(Activity activity, FragmentContainer container, Fragment parent) { + if (mActivity != null) throw new IllegalStateException("Already attached"); mActivity = activity; + mContainer = container; + mParent = parent; } public void noteStateNotSaved() { @@ -1820,11 +1847,17 @@ final class FragmentManagerImpl extends FragmentManager { moveToState(Fragment.STOPPED, false); } + public void dispatchDestroyView() { + moveToState(Fragment.CREATED, false); + } + public void dispatchDestroy() { mDestroyed = true; execPendingActions(); moveToState(Fragment.INITIALIZING, false); mActivity = null; + mContainer = null; + mParent = null; } public void dispatchConfigurationChanged(Configuration newConfig) { @@ -1832,7 +1865,7 @@ final class FragmentManagerImpl extends FragmentManager { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); if (f != null) { - f.onConfigurationChanged(newConfig); + f.performConfigurationChanged(newConfig); } } } @@ -1843,7 +1876,7 @@ final class FragmentManagerImpl extends FragmentManager { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); if (f != null) { - f.onLowMemory(); + f.performLowMemory(); } } } @@ -1854,7 +1887,7 @@ final class FragmentManagerImpl extends FragmentManager { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); if (f != null) { - f.onTrimMemory(level); + f.performTrimMemory(level); } } } @@ -1866,13 +1899,14 @@ final class FragmentManagerImpl extends FragmentManager { if (mAdded != null) { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); - if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { - show = true; - f.onCreateOptionsMenu(menu, inflater); - if (newMenus == null) { - newMenus = new ArrayList<Fragment>(); + if (f != null) { + if (f.performCreateOptionsMenu(menu, inflater)) { + show = true; + if (newMenus == null) { + newMenus = new ArrayList<Fragment>(); + } + newMenus.add(f); } - newMenus.add(f); } } } @@ -1896,9 +1930,10 @@ final class FragmentManagerImpl extends FragmentManager { if (mAdded != null) { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); - if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { - show = true; - f.onPrepareOptionsMenu(menu); + if (f != null) { + if (f.performPrepareOptionsMenu(menu)) { + show = true; + } } } } @@ -1909,8 +1944,8 @@ final class FragmentManagerImpl extends FragmentManager { if (mAdded != null) { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); - if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { - if (f.onOptionsItemSelected(item)) { + if (f != null) { + if (f.performOptionsItemSelected(item)) { return true; } } @@ -1923,8 +1958,8 @@ final class FragmentManagerImpl extends FragmentManager { if (mAdded != null) { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); - if (f != null && !f.mHidden && f.mUserVisibleHint) { - if (f.onContextItemSelected(item)) { + if (f != null) { + if (f.performContextItemSelected(item)) { return true; } } @@ -1937,8 +1972,8 @@ final class FragmentManagerImpl extends FragmentManager { if (mAdded != null) { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); - if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { - f.onOptionsMenuClosed(menu); + if (f != null) { + f.performOptionsMenuClosed(menu); } } } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 031e39b..3124671 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -55,14 +55,18 @@ public interface IActivityManager extends IInterface { Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) throws RemoteException; + public int startActivityAsUser(IApplicationThread caller, + Intent intent, String resolvedType, IBinder resultTo, String resultWho, + int requestCode, int flags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException; public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags, String profileFile, - ParcelFileDescriptor profileFd, Bundle options) throws RemoteException; + ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException; public int startActivityWithConfig(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Configuration newConfig, - Bundle options) throws RemoteException; + Bundle options, int userId) throws RemoteException; public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, @@ -76,34 +80,31 @@ public interface IActivityManager extends IInterface { public boolean willActivityBeVisible(IBinder token) throws RemoteException; public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, - String requiredPermission) throws RemoteException; + String requiredPermission, int userId) throws RemoteException; public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException; public int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, boolean serialized, boolean sticky, int userId) throws RemoteException; public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) throws RemoteException; - /* oneway */ public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException; public void attachApplication(IApplicationThread app) throws RemoteException; - /* oneway */ + public void activityResumed(IBinder token) throws RemoteException; public void activityIdle(IBinder token, Configuration config, boolean stopProfiling) throws RemoteException; public void activityPaused(IBinder token) throws RemoteException; - /* oneway */ public void activityStopped(IBinder token, Bundle state, Bitmap thumbnail, CharSequence description) throws RemoteException; - /* oneway */ public void activitySlept(IBinder token) throws RemoteException; - /* oneway */ public void activityDestroyed(IBinder token) throws RemoteException; public String getCallingPackage(IBinder token) throws RemoteException; public ComponentName getCallingActivity(IBinder token) throws RemoteException; public List getTasks(int maxNum, int flags, IThumbnailReceiver receiver) throws RemoteException; public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, - int flags) throws RemoteException; + int flags, int userId) throws RemoteException; public ActivityManager.TaskThumbnails getTaskThumbnails(int taskId) throws RemoteException; + public Bitmap getTaskTopThumbnail(int taskId) throws RemoteException; public List getServices(int maxNum, int flags) throws RemoteException; public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() throws RemoteException; @@ -116,8 +117,8 @@ public interface IActivityManager extends IInterface { public void reportThumbnail(IBinder token, Bitmap thumbnail, CharSequence description) throws RemoteException; public ContentProviderHolder getContentProvider(IApplicationThread caller, - String name, boolean stable) throws RemoteException; - public ContentProviderHolder getContentProviderExternal(String name, IBinder token) + String name, int userId, boolean stable) throws RemoteException; + public ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token) throws RemoteException; public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException; public void removeContentProviderExternal(String name, IBinder token) throws RemoteException; @@ -129,9 +130,9 @@ public interface IActivityManager extends IInterface { public PendingIntent getRunningServiceControlPanel(ComponentName service) throws RemoteException; public ComponentName startService(IApplicationThread caller, Intent service, - String resolvedType) throws RemoteException; + String resolvedType, int userId) throws RemoteException; public int stopService(IApplicationThread caller, Intent service, - String resolvedType) throws RemoteException; + String resolvedType, int userId) throws RemoteException; public boolean stopServiceToken(ComponentName className, IBinder token, int startId) throws RemoteException; public void setServiceForeground(ComponentName className, IBinder token, @@ -156,7 +157,7 @@ public interface IActivityManager extends IInterface { public void killApplicationProcess(String processName, int uid) throws RemoteException; public boolean startInstrumentation(ComponentName className, String profileFile, - int flags, Bundle arguments, IInstrumentationWatcher watcher) + int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId) throws RemoteException; public void finishInstrumentation(IApplicationThread target, int resultCode, Bundle results) throws RemoteException; @@ -173,13 +174,16 @@ public interface IActivityManager extends IInterface { public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, - int flags, Bundle options) throws RemoteException; + int flags, Bundle options, int userId) throws RemoteException; public void cancelIntentSender(IIntentSender sender) throws RemoteException; public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer, int userId) throws RemoteException; public String getPackageForIntentSender(IIntentSender sender) throws RemoteException; public int getUidForIntentSender(IIntentSender sender) throws RemoteException; + public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, + boolean requireFull, String name, String callerPackage) throws RemoteException; + public void setProcessLimit(int max) throws RemoteException; public int getProcessLimit() throws RemoteException; @@ -201,9 +205,10 @@ public interface IActivityManager extends IInterface { public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException; - public void killBackgroundProcesses(final String packageName) throws RemoteException; + public void killBackgroundProcesses(final String packageName, int userId) + throws RemoteException; public void killAllBackgroundProcesses() throws RemoteException; - public void forceStopPackage(final String packageName) throws RemoteException; + public void forceStopPackage(final String packageName, int userId) throws RemoteException; // Note: probably don't want to allow applications access to these. public void goingToSleep() throws RemoteException; @@ -260,7 +265,7 @@ public interface IActivityManager extends IInterface { public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException; // Turn on/off profiling in a particular process. - public boolean profileControl(String process, boolean start, + public boolean profileControl(String process, int userId, boolean start, String path, ParcelFileDescriptor fd, int profileType) throws RemoteException; public boolean shutdown(int timeout) throws RemoteException; @@ -268,12 +273,7 @@ public interface IActivityManager extends IInterface { public void stopAppSwitches() throws RemoteException; public void resumeAppSwitches() throws RemoteException; - public int startActivityInPackage(int uid, - Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, Bundle options) - throws RemoteException; - - public void killApplicationWithUid(String pkg, int uid) throws RemoteException; + public void killApplicationWithAppId(String pkg, int appid) throws RemoteException; public void closeSystemDialogs(String reason) throws RemoteException; @@ -294,7 +294,7 @@ public interface IActivityManager extends IInterface { public void crashApplication(int uid, int initialPid, String packageName, String message) throws RemoteException; - public String getProviderMimeType(Uri uri) throws RemoteException; + public String getProviderMimeType(Uri uri, int userId) throws RemoteException; public IBinder newUriPermissionOwner(String name) throws RemoteException; public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg, @@ -306,15 +306,12 @@ public interface IActivityManager extends IInterface { Uri uri, int modeFlags) throws RemoteException; // Cause the specified process to dump the specified heap. - public boolean dumpHeap(String process, boolean managed, String path, + public boolean dumpHeap(String process, int userId, boolean managed, String path, ParcelFileDescriptor fd) throws RemoteException; public int startActivities(IApplicationThread caller, Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle options) throws RemoteException; - public int startActivitiesInPackage(int uid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle options) throws RemoteException; + Bundle options, int userId) throws RemoteException; public int getFrontActivityScreenCompatMode() throws RemoteException; public void setFrontActivityScreenCompatMode(int mode) throws RemoteException; @@ -327,7 +324,10 @@ public interface IActivityManager extends IInterface { // Multi-user APIs public boolean switchUser(int userid) throws RemoteException; + public int stopUser(int userid, IStopUserCallback callback) throws RemoteException; public UserInfo getCurrentUser() throws RemoteException; + public boolean isUserRunning(int userid) throws RemoteException; + public int[] getRunningUserIds() throws RemoteException; public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException; @@ -358,6 +358,11 @@ public interface IActivityManager extends IInterface { // manage your activity to make sure it is always the uid you expect. public int getLaunchedFromUid(IBinder activityToken) throws RemoteException; + public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException; + public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException; + + public void requestBugReport() throws RemoteException; + /* * Private non-Binder interfaces */ @@ -491,7 +496,7 @@ public interface IActivityManager extends IInterface { int BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35; int UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36; int PUBLISH_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37; - + int ACTIVITY_RESUMED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38; int GOING_TO_SLEEP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+39; int WAKING_UP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+40; int SET_DEBUG_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41; @@ -546,10 +551,9 @@ public interface IActivityManager extends IInterface { int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90; int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91; int GET_UID_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92; - - - int START_ACTIVITY_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94; - int KILL_APPLICATION_WITH_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95; + int HANDLE_INCOMING_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+93; + int GET_TASK_TOP_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94; + int KILL_APPLICATION_WITH_APPID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95; int CLOSE_SYSTEM_DIALOGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+96; int GET_PROCESS_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+97; int KILL_APPLICATION_PROCESS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+98; @@ -575,7 +579,7 @@ public interface IActivityManager extends IInterface { int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118; int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119; int START_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+120; - int START_ACTIVITIES_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121; + int IS_USER_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121; int ACTIVITY_SLEPT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+122; int GET_FRONT_ACTIVITY_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+123; int SET_FRONT_ACTIVITY_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+124; @@ -606,4 +610,10 @@ public interface IActivityManager extends IInterface { int GET_LAUNCHED_FROM_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+149; int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+150; int IS_INTENT_SENDER_AN_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+151; + int START_ACTIVITY_AS_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+152; + int STOP_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+153; + int REGISTER_USER_SWITCH_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+154; + int UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+155; + int GET_RUNNING_USER_IDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+156; + int REQUEST_BUG_REPORT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+157; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index f60cfd6..03a26d4 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -65,7 +65,8 @@ public interface IApplicationThread extends IInterface { void scheduleDestroyActivity(IBinder token, boolean finished, int configChanges) throws RemoteException; void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, - int resultCode, String data, Bundle extras, boolean sync) throws RemoteException; + int resultCode, String data, Bundle extras, boolean sync, + int sendingUser) throws RemoteException; static final int BACKUP_MODE_INCREMENTAL = 0; static final int BACKUP_MODE_FULL = 1; static final int BACKUP_MODE_RESTORE = 2; @@ -105,8 +106,8 @@ public interface IApplicationThread extends IInterface { void dumpProvider(FileDescriptor fd, IBinder servicetoken, String[] args) throws RemoteException; void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, - int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) - throws RemoteException; + int resultCode, String data, Bundle extras, boolean ordered, + boolean sticky, int sendingUser) throws RemoteException; void scheduleLowMemory() throws RemoteException; void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException; void profilerControl(boolean start, String path, ParcelFileDescriptor fd, int profileType) diff --git a/core/java/android/app/IInstrumentationWatcher.aidl b/core/java/android/app/IInstrumentationWatcher.aidl index 405a3d8..6c8c4d6 100644 --- a/core/java/android/app/IInstrumentationWatcher.aidl +++ b/core/java/android/app/IInstrumentationWatcher.aidl @@ -21,7 +21,7 @@ import android.content.ComponentName; import android.os.Bundle; /** @hide */ -oneway interface IInstrumentationWatcher +interface IInstrumentationWatcher { void instrumentationStatus(in ComponentName name, int resultCode, in Bundle results); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 6f95e26..62d4962 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -24,16 +24,13 @@ import android.content.Intent; /** {@hide} */ interface INotificationManager { - /** @deprecated use {@link #enqueueNotificationWithTag} instead */ - void enqueueNotification(String pkg, int id, in Notification notification, inout int[] idReceived); - /** @deprecated use {@link #cancelNotificationWithTag} instead */ - void cancelNotification(String pkg, int id); - void cancelAllNotifications(String pkg); + void cancelAllNotifications(String pkg, int userId); void enqueueToast(String pkg, ITransientNotification callback, int duration); void cancelToast(String pkg, ITransientNotification callback); - void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived); - void cancelNotificationWithTag(String pkg, String tag, int id); + void enqueueNotificationWithTag(String pkg, String tag, int id, + in Notification notification, inout int[] idReceived, int userId); + void cancelNotificationWithTag(String pkg, String tag, int id, int userId); void setNotificationsEnabledForPackage(String pkg, boolean enabled); boolean areNotificationsEnabledForPackage(String pkg); diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index 688cdfd..074d343 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -30,4 +30,5 @@ interface ISearchManager { List<ResolveInfo> getGlobalSearchActivities(); ComponentName getGlobalSearchActivity(); ComponentName getWebSearchActivity(); + ComponentName getAssistIntent(int userHandle); } diff --git a/core/java/android/app/IStopUserCallback.aidl b/core/java/android/app/IStopUserCallback.aidl new file mode 100644 index 0000000..19ac1d5 --- /dev/null +++ b/core/java/android/app/IStopUserCallback.aidl @@ -0,0 +1,27 @@ +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.app; + +/** + * Callback to find out when we have finished stopping a user. + * {@hide} + */ +interface IStopUserCallback +{ + void userStopped(int userId); + void userStopAborted(int userId); +} diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl new file mode 100644 index 0000000..845897b --- /dev/null +++ b/core/java/android/app/IUserSwitchObserver.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.os.IRemoteCallback; + +/** {@hide} */ +oneway interface IUserSwitchObserver { + void onUserSwitching(int newUserId, IRemoteCallback reply); + void onUserSwitchComplete(int newUserId); +} diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 69f64a1..3efd3c0 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -52,6 +52,11 @@ interface IWallpaperManager { void clearWallpaper(); /** + * Return whether there is a wallpaper set with the given name. + */ + boolean hasNamedWallpaper(String name); + + /** * Sets the dimension hint for the wallpaper. These hints indicate the desired * minimum width and height for the wallpaper. */ diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index cad4b01..e0856ae 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -33,6 +33,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.util.AndroidRuntimeException; import android.util.Log; import android.view.IWindowManager; @@ -1429,6 +1430,21 @@ public class Instrumentation { */ public void execStartActivities(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options) { + execStartActivitiesAsUser(who, contextThread, token, target, intents, options, + UserHandle.myUserId()); + } + + /** + * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, + * but accepts an array of activities to be started. Note that active + * {@link ActivityMonitor} objects only match against the first activity in + * the array. + * + * {@hide} + */ + public void execStartActivitiesAsUser(Context who, IBinder contextThread, + IBinder token, Activity target, Intent[] intents, Bundle options, + int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1452,7 +1468,8 @@ public class Instrumentation { resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver()); } int result = ActivityManagerNative.getDefault() - .startActivities(whoThread, intents, resolvedTypes, token, options); + .startActivities(whoThread, intents, resolvedTypes, token, options, + userId); checkStartActivityResult(result, intents[0]); } catch (RemoteException e) { } @@ -1518,6 +1535,66 @@ public class Instrumentation { return null; } + /** + * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, + * but for starting as a particular user. + * + * @param who The Context from which the activity is being started. + * @param contextThread The main thread of the Context from which the activity + * is being started. + * @param token Internal token identifying to the system who is starting + * the activity; may be null. + * @param target Which fragment is performing the start (and thus receiving + * any result). + * @param intent The actual Intent to start. + * @param requestCode Identifier for this request's result; less than zero + * if the caller is not expecting a result. + * + * @return To force the return of a particular result, return an + * ActivityResult object containing the desired data; otherwise + * return null. The default implementation always returns null. + * + * @throws android.content.ActivityNotFoundException + * + * @see Activity#startActivity(Intent) + * @see Activity#startActivityForResult(Intent, int) + * @see Activity#startActivityFromChild + * + * {@hide} + */ + public ActivityResult execStartActivity( + Context who, IBinder contextThread, IBinder token, Activity target, + Intent intent, int requestCode, Bundle options, UserHandle user) { + IApplicationThread whoThread = (IApplicationThread) contextThread; + if (mActivityMonitors != null) { + synchronized (mSync) { + final int N = mActivityMonitors.size(); + for (int i=0; i<N; i++) { + final ActivityMonitor am = mActivityMonitors.get(i); + if (am.match(who, null, intent)) { + am.mHits++; + if (am.isBlocking()) { + return requestCode >= 0 ? am.getResult() : null; + } + break; + } + } + } + } + try { + intent.setAllowFds(false); + intent.migrateExtraStreamToClipData(); + int result = ActivityManagerNative.getDefault() + .startActivityAsUser(whoThread, intent, + intent.resolveTypeIfNeeded(who.getContentResolver()), + token, target != null ? target.mEmbeddedID : null, + requestCode, 0, null, null, options, user.getIdentifier()); + checkStartActivityResult(result, intent); + } catch (RemoteException e) { + } + return null; + } + /*package*/ final void init(ActivityThread thread, Context instrContext, Context appContext, ComponentName component, IInstrumentationWatcher watcher) { diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index ef61af7..22a21cd 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -16,13 +16,12 @@ package android.app; -import android.content.Context; import android.os.Binder; import android.os.RemoteException; import android.os.IBinder; -import android.os.ServiceManager; import android.view.IWindowManager; import android.view.IOnKeyguardExitResult; +import android.view.WindowManagerGlobal; /** * Class that can be used to lock and unlock the keyboard. Get an instance of this @@ -111,7 +110,7 @@ public class KeyguardManager { KeyguardManager() { - mWM = IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)); + mWM = WindowManagerGlobal.getWindowManagerService(); } /** diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index be4b284..0a9ed58 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -37,10 +37,11 @@ import android.os.Process; import android.os.RemoteException; import android.os.StrictMode; import android.os.Trace; -import android.os.UserId; +import android.os.UserHandle; import android.util.AndroidRuntimeException; import android.util.Slog; import android.view.CompatibilityInfoHolder; +import android.view.Display; import java.io.File; import java.io.IOException; @@ -120,8 +121,8 @@ public final class LoadedApk { final int myUid = Process.myUid(); mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir; - if (!UserId.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) { - aInfo.dataDir = PackageManager.getDataDirForUser(UserId.getUserId(myUid), + if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) { + aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid), mPackageName); } mSharedLibraries = aInfo.sharedLibraryFiles; @@ -139,7 +140,8 @@ public final class LoadedApk { ContextImpl.createSystemContext(mainThread); ActivityThread.mSystemContext.getResources().updateConfiguration( mainThread.getConfiguration(), - mainThread.getDisplayMetricsLocked(compatInfo, false), + mainThread.getDisplayMetricsLocked( + Display.DEFAULT_DISPLAY, compatInfo), compatInfo); //Slog.i(TAG, "Created system resources " // + mSystemContext.getResources() + ": " @@ -195,7 +197,7 @@ public final class LoadedApk { ApplicationInfo ai = null; try { ai = ActivityThread.getPackageManager().getApplicationInfo(packageName, - PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId()); + PackageManager.GET_SHARED_LIBRARY_FILES, UserHandle.myUserId()); } catch (RemoteException e) { throw new AssertionError(e); } @@ -358,7 +360,7 @@ public final class LoadedApk { IPackageManager pm = ActivityThread.getPackageManager(); android.content.pm.PackageInfo pi; try { - pi = pm.getPackageInfo(mPackageName, 0, UserId.myUserId()); + pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId()); } catch (RemoteException e) { throw new AssertionError(e); } @@ -471,7 +473,8 @@ public final class LoadedApk { public Resources getResources(ActivityThread mainThread) { if (mResources == null) { - mResources = mainThread.getTopLevelResources(mResDir, this); + mResources = mainThread.getTopLevelResources(mResDir, + Display.DEFAULT_DISPLAY, null, this); } return mResources; } @@ -667,8 +670,8 @@ public final class LoadedApk { mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd); mStrongRef = strong ? rd : null; } - public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean ordered, boolean sticky) { + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean ordered, boolean sticky, int sendingUser) { LoadedApk.ReceiverDispatcher rd = mDispatcher.get(); if (ActivityThread.DEBUG_BROADCAST) { int seq = intent.getIntExtra("seq", -1); @@ -677,7 +680,7 @@ public final class LoadedApk { } if (rd != null) { rd.performReceive(intent, resultCode, data, extras, - ordered, sticky); + ordered, sticky, sendingUser); } else { // The activity manager dispatched a broadcast to a registered // receiver in this process, but before it could be delivered the @@ -713,10 +716,10 @@ public final class LoadedApk { private final boolean mOrdered; public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras, - boolean ordered, boolean sticky) { + boolean ordered, boolean sticky, int sendingUser) { super(resultCode, resultData, resultExtras, mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, - ordered, sticky, mIIntentReceiver.asBinder()); + ordered, sticky, mIIntentReceiver.asBinder(), sendingUser); mCurIntent = intent; mOrdered = ordered; } @@ -827,14 +830,15 @@ public final class LoadedApk { return mUnregisterLocation; } - public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean ordered, boolean sticky) { + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean ordered, boolean sticky, int sendingUser) { if (ActivityThread.DEBUG_BROADCAST) { int seq = intent.getIntExtra("seq", -1); Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq + " to " + mReceiver); } - Args args = new Args(intent, resultCode, data, extras, ordered, sticky); + Args args = new Args(intent, resultCode, data, extras, ordered, + sticky, sendingUser); if (!mActivityThread.post(args)) { if (mRegistered && ordered) { IActivityManager mgr = ActivityManagerNative.getDefault(); diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index ff71ee7..fd0f0bf 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -17,7 +17,6 @@ package android.app; import android.content.Loader; -import android.content.Loader.OnLoadCanceledListener; import android.os.Bundle; import android.util.DebugUtils; import android.util.Log; @@ -213,6 +212,8 @@ class LoaderManagerImpl extends LoaderManager { // previously run loader until the new loader's data is available. final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(); + final String mWho; + Activity mActivity; boolean mStarted; boolean mRetaining; @@ -529,7 +530,8 @@ class LoaderManagerImpl extends LoaderManager { } } - LoaderManagerImpl(Activity activity, boolean started) { + LoaderManagerImpl(String who, Activity activity, boolean started) { + mWho = who; mActivity = activity; mStarted = started; } diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java index c2f5ac1..63b641c 100644 --- a/core/java/android/app/MediaRouteActionProvider.java +++ b/core/java/android/app/MediaRouteActionProvider.java @@ -26,6 +26,7 @@ import android.util.Log; import android.view.ActionProvider; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import java.lang.ref.WeakReference; @@ -80,8 +81,11 @@ public class MediaRouteActionProvider extends ActionProvider { } mMenuItem = item; mView = new MediaRouteButton(mContext); + mView.setCheatSheetEnabled(true); mView.setRouteTypes(mRouteTypes); mView.setExtendedSettingsClickListener(mExtendedSettingsListener); + mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT)); return mView; } diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java index b0bfe74..3ecafc3 100644 --- a/core/java/android/app/MediaRouteButton.java +++ b/core/java/android/app/MediaRouteButton.java @@ -23,14 +23,19 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.media.MediaRouter; import android.media.MediaRouter.RouteGroup; import android.media.MediaRouter.RouteInfo; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; import android.view.View; +import android.widget.Toast; public class MediaRouteButton extends View { private static final String TAG = "MediaRouteButton"; @@ -44,6 +49,8 @@ public class MediaRouteButton extends View { private Drawable mRemoteIndicator; private boolean mRemoteActive; private boolean mToggleMode; + private boolean mCheatSheetEnabled; + private boolean mIsConnecting; private int mMinWidth; private int mMinHeight; @@ -51,6 +58,10 @@ public class MediaRouteButton extends View { private OnClickListener mExtendedSettingsClickListener; private MediaRouteChooserDialogFragment mDialogFragment; + private static final int[] CHECKED_STATE_SET = { + R.attr.state_checked + }; + private static final int[] ACTIVATED_STATE_SET = { R.attr.state_activated }; @@ -82,6 +93,7 @@ public class MediaRouteButton extends View { a.recycle(); setClickable(true); + setLongClickable(true); setRouteTypes(routeTypes); } @@ -129,6 +141,52 @@ public class MediaRouteButton extends View { return handled; } + void setCheatSheetEnabled(boolean enable) { + mCheatSheetEnabled = enable; + } + + @Override + public boolean performLongClick() { + if (super.performLongClick()) { + return true; + } + + if (!mCheatSheetEnabled) { + return false; + } + + final CharSequence contentDesc = getContentDescription(); + if (TextUtils.isEmpty(contentDesc)) { + // Don't show the cheat sheet if we have no description + return false; + } + + final int[] screenPos = new int[2]; + final Rect displayFrame = new Rect(); + getLocationOnScreen(screenPos); + getWindowVisibleDisplayFrame(displayFrame); + + final Context context = getContext(); + final int width = getWidth(); + final int height = getHeight(); + final int midy = screenPos[1] + height / 2; + final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + + Toast cheatSheet = Toast.makeText(context, contentDesc, Toast.LENGTH_SHORT); + if (midy < displayFrame.height()) { + // Show along the top; follow action buttons + cheatSheet.setGravity(Gravity.TOP | Gravity.END, + screenWidth - screenPos[0] - width / 2, height); + } else { + // Show along the bottom center + cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height); + } + cheatSheet.show(); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + + return true; + } + public void setRouteTypes(int types) { if (types == mRouteTypes) { // Already registered; nothing to do. @@ -157,10 +215,21 @@ public class MediaRouteButton extends View { } void updateRemoteIndicator() { - final boolean isRemote = - mRouter.getSelectedRoute(mRouteTypes) != mRouter.getSystemAudioRoute(); + final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes); + final boolean isRemote = selected != mRouter.getSystemAudioRoute(); + final boolean isConnecting = selected.getStatusCode() == RouteInfo.STATUS_CONNECTING; + + boolean needsRefresh = false; if (mRemoteActive != isRemote) { mRemoteActive = isRemote; + needsRefresh = true; + } + if (mIsConnecting != isConnecting) { + mIsConnecting = isConnecting; + needsRefresh = true; + } + + if (needsRefresh) { refreshDrawableState(); } } @@ -168,27 +237,41 @@ public class MediaRouteButton extends View { void updateRouteCount() { final int N = mRouter.getRouteCount(); int count = 0; + boolean hasVideoRoutes = false; for (int i = 0; i < N; i++) { final RouteInfo route = mRouter.getRouteAt(i); - if ((route.getSupportedTypes() & mRouteTypes) != 0) { + final int routeTypes = route.getSupportedTypes(); + if ((routeTypes & mRouteTypes) != 0) { if (route instanceof RouteGroup) { count += ((RouteGroup) route).getRouteCount(); } else { count++; } + if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) { + hasVideoRoutes = true; + } } } setEnabled(count != 0); - // Only allow toggling if we have more than just user routes - mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0; + // Only allow toggling if we have more than just user routes. + // Don't toggle if we support video routes, we may have to let the dialog scan. + mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 && + !hasVideoRoutes; } @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); - if (mRemoteActive) { + + // Technically we should be handling this more completely, but these + // are implementation details here. Checked is used to express the connecting + // drawable state and it's mutually exclusive with activated for the purposes + // of state selection here. + if (mIsConnecting) { + mergeDrawableStates(drawableState, CHECKED_STATE_SET); + } else if (mRemoteActive) { mergeDrawableStates(drawableState, ACTIVATED_STATE_SET); } return drawableState; @@ -366,6 +449,11 @@ public class MediaRouteButton extends View { } @Override + public void onRouteChanged(MediaRouter router, RouteInfo info) { + updateRemoteIndicator(); + } + + @Override public void onRouteAdded(MediaRouter router, RouteInfo info) { updateRouteCount(); } diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java index 35cc324..396f910 100644 --- a/core/java/android/app/NativeActivity.java +++ b/core/java/android/app/NativeActivity.java @@ -1,6 +1,5 @@ package android.app; -import com.android.internal.view.IInputMethodCallback; import com.android.internal.view.IInputMethodSession; import android.content.Context; @@ -119,7 +118,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, } } - static class InputMethodCallback extends IInputMethodCallback.Stub { + static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback { WeakReference<NativeActivity> mNa; InputMethodCallback(NativeActivity na) { @@ -133,11 +132,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled); } } - - @Override - public void sessionCreated(IInputMethodSession session) { - // Stub -- not for use in the client. - } } @Override diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index cb83dc2..182ebef 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -29,6 +29,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import android.os.UserHandle; import android.text.TextUtils; import android.util.IntProperty; import android.util.Log; @@ -893,6 +894,19 @@ public class Notification implements Parcelable return sb.toString(); } + /** {@hide} */ + public void setUser(UserHandle user) { + if (tickerView != null) { + tickerView.setUser(user); + } + if (contentView != null) { + contentView.setUser(user); + } + if (bigContentView != null) { + bigContentView.setUser(user); + } + } + /** * Builder class for {@link Notification} objects. * @@ -951,6 +965,7 @@ public class Notification implements Parcelable private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); private boolean mUseChronometer; private Style mStyle; + private boolean mShowWhen = true; /** * Constructs a new Builder with the defaults: @@ -982,8 +997,9 @@ public class Notification implements Parcelable /** * Add a timestamp pertaining to the notification (usually the time the event occurred). + * It will be shown in the notification content view by default; use + * {@link Builder#setShowWhen(boolean) setShowWhen} to control this. * - * @see Notification#when */ public Builder setWhen(long when) { @@ -992,6 +1008,15 @@ public class Notification implements Parcelable } /** + * Control whether the timestamp set with {@link Builder#setWhen(long) setWhen} is shown + * in the content view. + */ + public Builder setShowWhen(boolean show) { + mShowWhen = show; + return this; + } + + /** * Show the {@link Notification#when} field as a stopwatch. * * Instead of presenting <code>when</code> as a timestamp, the notification will show an @@ -1467,7 +1492,7 @@ public class Notification implements Parcelable contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); } - if (mWhen != 0) { + if (mWhen != 0 && mShowWhen) { if (mUseChronometer) { contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); contentView.setLong(R.id.chronometer, "setBase", @@ -1477,7 +1502,10 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.time, View.VISIBLE); contentView.setLong(R.id.time, "setTime", mWhen); } + } else { + contentView.setViewVisibility(R.id.time, View.GONE); } + contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE); contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE); return contentView; @@ -1923,6 +1951,7 @@ public class Notification implements Parcelable contentView.setViewVisibility(rowId, View.GONE); } + int i=0; while (i < mTexts.size() && i < rowIds.length) { CharSequence str = mTexts.get(i); @@ -1933,11 +1962,11 @@ public class Notification implements Parcelable i++; } - if (mTexts.size() > rowIds.length) { - contentView.setViewVisibility(R.id.inbox_more, View.VISIBLE); - } else { - contentView.setViewVisibility(R.id.inbox_more, View.GONE); - } + contentView.setViewVisibility(R.id.inbox_end_pad, + mTexts.size() > 0 ? View.VISIBLE : View.GONE); + + contentView.setViewVisibility(R.id.inbox_more, + mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE); return contentView; } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index bf83f5e..0acad75 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -17,11 +17,11 @@ package android.app; import android.content.Context; -import android.os.Binder; -import android.os.RemoteException; import android.os.Handler; import android.os.IBinder; +import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; /** @@ -88,6 +88,11 @@ public class NotificationManager mContext = context; } + /** {@hide} */ + public static NotificationManager from(Context context) { + return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + /** * Post a notification to be shown in the status bar. If a notification with * the same id has already been posted by your application and has not yet been canceled, it @@ -119,9 +124,35 @@ public class NotificationManager int[] idOut = new int[1]; INotificationManager service = getService(); String pkg = mContext.getPackageName(); + if (notification.sound != null) { + notification.sound = notification.sound.getCanonicalUri(); + } + if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); + try { + service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut, + UserHandle.myUserId()); + if (id != idOut[0]) { + Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); + } + } catch (RemoteException e) { + } + } + + /** + * @hide + */ + public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) + { + int[] idOut = new int[1]; + INotificationManager service = getService(); + String pkg = mContext.getPackageName(); + if (notification.sound != null) { + notification.sound = notification.sound.getCanonicalUri(); + } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { - service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut); + service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut, + user.getIdentifier()); if (id != idOut[0]) { Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); } @@ -150,7 +181,21 @@ public class NotificationManager String pkg = mContext.getPackageName(); if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")"); try { - service.cancelNotificationWithTag(pkg, tag, id); + service.cancelNotificationWithTag(pkg, tag, id, UserHandle.myUserId()); + } catch (RemoteException e) { + } + } + + /** + * @hide + */ + public void cancelAsUser(String tag, int id, UserHandle user) + { + INotificationManager service = getService(); + String pkg = mContext.getPackageName(); + if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")"); + try { + service.cancelNotificationWithTag(pkg, tag, id, user.getIdentifier()); } catch (RemoteException e) { } } @@ -165,7 +210,7 @@ public class NotificationManager String pkg = mContext.getPackageName(); if (localLOGV) Log.v(TAG, pkg + ": cancelAll()"); try { - service.cancelAllNotifications(pkg); + service.cancelAllNotifications(pkg, UserHandle.myUserId()); } catch (RemoteException e) { } } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 8adc8a2..d36d99d 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -27,12 +27,13 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.util.AndroidException; /** * A description of an Intent and target action to perform with it. Instances - * of this class are created with {@link #getActivity}, - * {@link #getBroadcast}, {@link #getService}; the returned object can be + * of this class are created with {@link #getActivity}, {@link #getActivities}, + * {@link #getBroadcast}, and {@link #getService}; the returned object can be * handed to other applications so that they can perform the action you * described on your behalf at a later time. * @@ -53,6 +54,34 @@ import android.util.AndroidException; * categories, and components, and same flags), it will receive a PendingIntent * representing the same token if that is still valid, and can thus call * {@link #cancel} to remove it. + * + * <p>Because of this behavior, it is important to know when two Intents + * are considered to be the same for purposes of retrieving a PendingIntent. + * A common mistake people make is to create multiple PendingIntent objects + * with Intents that only vary in their "extra" contents, expecting to get + * a different PendingIntent each time. This does <em>not</em> happen. The + * parts of the Intent that are used for matching are the same ones defined + * by {@link Intent#filterEquals(Intent) Intent.filterEquals}. If you use two + * Intent objects that are equivalent as per + * {@link Intent#filterEquals(Intent) Intent.filterEquals}, then you will get + * the same PendingIntent for both of them. + * + * <p>There are two typical ways to deal with this. + * + * <p>If you truly need multiple distinct PendingIntent objects active at + * the same time (such as to use as two notifications that are both shown + * at the same time), then you will need to ensure there is something that + * is different about them to associate them with different PendingIntents. + * This may be any of the Intent attributes considered by + * {@link Intent#filterEquals(Intent) Intent.filterEquals}, or different + * request code integers supplied to {@link #getActivity}, {@link #getActivities}, + * {@link #getBroadcast}, or {@link #getService}. + * + * <p>If you only need one PendingIntent active at a time for any of the + * Intents you will use, then you can alternatively use the flags + * {@link #FLAG_CANCEL_CURRENT} or {@link #FLAG_UPDATE_CURRENT} to either + * cancel or modify whatever current PendingIntent is associated with the + * Intent you are supplying. */ public final class PendingIntent implements Parcelable { private final IIntentSender mTarget; @@ -146,8 +175,8 @@ public final class PendingIntent implements Parcelable { mWho = who; mHandler = handler; } - public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean serialized, boolean sticky) { + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean serialized, boolean sticky, int sendingUser) { mIntent = intent; mResultCode = resultCode; mResultData = data; @@ -227,7 +256,31 @@ public final class PendingIntent implements Parcelable { ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, - flags, options); + flags, options, UserHandle.myUserId()); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + } + return null; + } + + /** + * @hide + * Note that UserHandle.CURRENT will be interpreted at the time the + * activity is started, not when the pending intent is created. + */ + public static PendingIntent getActivityAsUser(Context context, int requestCode, + Intent intent, int flags, Bundle options, UserHandle user) { + String packageName = context.getPackageName(); + String resolvedType = intent != null ? intent.resolveTypeIfNeeded( + context.getContentResolver()) : null; + try { + intent.setAllowFds(false); + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY, packageName, + null, null, requestCode, new Intent[] { intent }, + resolvedType != null ? new String[] { resolvedType } : null, + flags, options, user.getIdentifier()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } @@ -333,7 +386,33 @@ public final class PendingIntent implements Parcelable { IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, packageName, - null, null, requestCode, intents, resolvedTypes, flags, options); + null, null, requestCode, intents, resolvedTypes, flags, options, + UserHandle.myUserId()); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + } + return null; + } + + /** + * @hide + * Note that UserHandle.CURRENT will be interpreted at the time the + * activity is started, not when the pending intent is created. + */ + public static PendingIntent getActivitiesAsUser(Context context, int requestCode, + Intent[] intents, int flags, Bundle options, UserHandle user) { + String packageName = context.getPackageName(); + String[] resolvedTypes = new String[intents.length]; + for (int i=0; i<intents.length; i++) { + intents[i].setAllowFds(false); + resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); + } + try { + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY, packageName, + null, null, requestCode, intents, resolvedTypes, + flags, options, user.getIdentifier()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } @@ -361,6 +440,17 @@ public final class PendingIntent implements Parcelable { */ public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags) { + return getBroadcastAsUser(context, requestCode, intent, flags, + new UserHandle(UserHandle.myUserId())); + } + + /** + * @hide + * Note that UserHandle.CURRENT will be interpreted at the time the + * broadcast is sent, not when the pending intent is created. + */ + public static PendingIntent getBroadcastAsUser(Context context, int requestCode, + Intent intent, int flags, UserHandle userHandle) { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; @@ -371,7 +461,7 @@ public final class PendingIntent implements Parcelable { ActivityManager.INTENT_SENDER_BROADCAST, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, - flags, null); + flags, null, userHandle.getIdentifier()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } @@ -410,7 +500,7 @@ public final class PendingIntent implements Parcelable { ActivityManager.INTENT_SENDER_SERVICE, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, - flags, null); + flags, null, UserHandle.myUserId()); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } @@ -598,6 +688,20 @@ public final class PendingIntent implements Parcelable { } /** + * @deprecated Renamed to {@link #getCreatorPackage()}. + */ + @Deprecated + public String getTargetPackage() { + try { + return ActivityManagerNative.getDefault() + .getPackageForIntentSender(mTarget); + } catch (RemoteException e) { + // Should never happen. + return null; + } + } + + /** * Return the package name of the application that created this * PendingIntent, that is the identity under which you will actually be * sending the Intent. The returned string is supplied by the system, so @@ -606,7 +710,7 @@ public final class PendingIntent implements Parcelable { * @return The package name of the PendingIntent, or null if there is * none associated with it. */ - public String getTargetPackage() { + public String getCreatorPackage() { try { return ActivityManagerNative.getDefault() .getPackageForIntentSender(mTarget); @@ -617,6 +721,47 @@ public final class PendingIntent implements Parcelable { } /** + * Return the uid of the application that created this + * PendingIntent, that is the identity under which you will actually be + * sending the Intent. The returned integer is supplied by the system, so + * that an application can not spoof its uid. + * + * @return The uid of the PendingIntent, or -1 if there is + * none associated with it. + */ + public int getCreatorUid() { + try { + return ActivityManagerNative.getDefault() + .getUidForIntentSender(mTarget); + } catch (RemoteException e) { + // Should never happen. + return -1; + } + } + + /** + * Return the user handle of the application that created this + * PendingIntent, that is the user under which you will actually be + * sending the Intent. The returned UserHandle is supplied by the system, so + * that an application can not spoof its user. See + * {@link android.os.Process#myUserHandle() Process.myUserHandle()} for + * more explanation of user handles. + * + * @return The user handle of the PendingIntent, or null if there is + * none associated with it. + */ + public UserHandle getCreatorUserHandle() { + try { + int uid = ActivityManagerNative.getDefault() + .getUidForIntentSender(mTarget); + return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null; + } catch (RemoteException e) { + // Should never happen. + return null; + } + } + + /** * @hide * Check to verify that this PendingIntent targets a specific package. */ diff --git a/core/java/android/app/Presentation.java b/core/java/android/app/Presentation.java new file mode 100644 index 0000000..b5e5244 --- /dev/null +++ b/core/java/android/app/Presentation.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.content.Context; +import android.content.res.Resources; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.Gravity; +import android.view.WindowManagerImpl; +import android.os.Handler; +import android.os.Message; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; + +/** + * Base class for presentations. + * <p> + * A presentation is a special kind of dialog whose purpose is to present + * content on a secondary display. A {@link Presentation} is associated with + * the target {@link Display} at creation time and configures its context and + * resource configuration according to the display's metrics. + * </p><p> + * Notably, the {@link Context} of a presentation is different from the context + * of its containing {@link Activity}. It is important to inflate the layout + * of a presentation and load other resources using the presentation's own context + * to ensure that assets of the correct size and density for the target display + * are loaded. + * </p><p> + * A presentation is automatically canceled (see {@link Dialog#cancel()}) when + * the display to which it is attached is removed. An activity should take + * care of pausing and resuming whatever content is playing within the presentation + * whenever the activity itself is paused or resumed. + * </p> + * + * @see DisplayManager for information on how to enumerate displays and receive + * notifications when displays are added or removed. + */ +public class Presentation extends Dialog { + private static final String TAG = "Presentation"; + + private static final int MSG_CANCEL = 1; + + private final Display mDisplay; + private final DisplayManager mDisplayManager; + + /** + * Creates a new presentation that is attached to the specified display + * using the default theme. + * + * @param outerContext The context of the application that is showing the presentation. + * The presentation will create its own context (see {@link #getContext()}) based + * on this context and information about the associated display. + * @param display The display to which the presentation should be attached. + */ + public Presentation(Context outerContext, Display display) { + this(outerContext, display, 0); + } + + /** + * Creates a new presentation that is attached to the specified display + * using the optionally specified theme. + * + * @param outerContext The context of the application that is showing the presentation. + * The presentation will create its own context (see {@link #getContext()}) based + * on this context and information about the associated display. + * @param display The display to which the presentation should be attached. + * @param theme A style resource describing the theme to use for the window. + * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes"> + * Style and Theme Resources</a> for more information about defining and using + * styles. This theme is applied on top of the current theme in + * <var>outerContext</var>. If 0, the default presentation theme will be used. + */ + public Presentation(Context outerContext, Display display, int theme) { + super(createPresentationContext(outerContext, display, theme), theme, false); + + mDisplay = display; + mDisplayManager = (DisplayManager)getContext().getSystemService(Context.DISPLAY_SERVICE); + + getWindow().setGravity(Gravity.FILL); + setCanceledOnTouchOutside(false); + } + + /** + * Gets the {@link Display} that this presentation appears on. + * + * @return The display. + */ + public Display getDisplay() { + return mDisplay; + } + + /** + * Gets the {@link Resources} that should be used to inflate the layout of this presentation. + * This resources object has been configured according to the metrics of the + * display that the presentation appears on. + * + * @return The presentation resources object. + */ + public Resources getResources() { + return getContext().getResources(); + } + + @Override + protected void onStart() { + super.onStart(); + mDisplayManager.registerDisplayListener(mDisplayListener, null); + + // Since we were not watching for display changes until just now, there is a + // chance that the display metrics have changed. If so, we will need to + // dismiss the presentation immediately. This case is expected + // to be rare but surprising, so we'll write a log message about it. + if (!isConfigurationStillValid()) { + Log.i(TAG, "Presentation is being immediately dismissed because the " + + "display metrics have changed since it was created."); + mHandler.sendEmptyMessage(MSG_CANCEL); + } + } + + @Override + protected void onStop() { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + super.onStop(); + } + + /** + * Called by the system when the {@link Display} to which the presentation + * is attached has been removed. + * + * The system automatically calls {@link #cancel} to dismiss the presentation + * after sending this event. + * + * @see #getDisplay + */ + public void onDisplayRemoved() { + } + + /** + * Called by the system when the properties of the {@link Display} to which + * the presentation is attached have changed. + * + * If the display metrics have changed (for example, if the display has been + * resized or rotated), then the system automatically calls + * {@link #cancel} to dismiss the presentation. + * + * @see #getDisplay + */ + public void onDisplayChanged() { + } + + private void handleDisplayRemoved() { + onDisplayRemoved(); + cancel(); + } + + private void handleDisplayChanged() { + onDisplayChanged(); + + // We currently do not support configuration changes for presentations + // (although we could add that feature with a bit more work). + // If the display metrics have changed in any way then the current configuration + // is invalid and the application must recreate the presentation to get + // a new context. + if (!isConfigurationStillValid()) { + cancel(); + } + } + + private boolean isConfigurationStillValid() { + DisplayMetrics dm = new DisplayMetrics(); + mDisplay.getMetrics(dm); + return dm.equals(getResources().getDisplayMetrics()); + } + + private static Context createPresentationContext( + Context outerContext, Display display, int theme) { + if (outerContext == null) { + throw new IllegalArgumentException("outerContext must not be null"); + } + if (display == null) { + throw new IllegalArgumentException("display must not be null"); + } + + Context displayContext = outerContext.createDisplayContext(display); + if (theme == 0) { + TypedValue outValue = new TypedValue(); + displayContext.getTheme().resolveAttribute( + com.android.internal.R.attr.presentationTheme, outValue, true); + theme = outValue.resourceId; + } + + // Derive the display's window manager from the outer window manager. + // We do this because the outer window manager have some extra information + // such as the parent window, which is important if the presentation uses + // an application window type. + final WindowManagerImpl outerWindowManager = + (WindowManagerImpl)outerContext.getSystemService(Context.WINDOW_SERVICE); + final WindowManagerImpl displayWindowManager = + outerWindowManager.createPresentationWindowManager(display); + return new ContextThemeWrapper(displayContext, theme) { + @Override + public Object getSystemService(String name) { + if (Context.WINDOW_SERVICE.equals(name)) { + return displayWindowManager; + } + return super.getSystemService(name); + } + }; + } + + private final DisplayListener mDisplayListener = new DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + if (displayId == mDisplay.getDisplayId()) { + handleDisplayRemoved(); + } + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == mDisplay.getDisplayId()) { + handleDisplayChanged(); + } + } + }; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_CANCEL: + cancel(); + break; + } + } + }; +} diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index d1d5131..43a163d 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -31,6 +31,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -845,14 +846,28 @@ public class SearchManager * * @hide */ - public static final Intent getAssistIntent(Context context) { - PackageManager pm = context.getPackageManager(); - Intent intent = new Intent(Intent.ACTION_ASSIST); - ComponentName component = intent.resolveActivity(pm); - if (component != null) { - intent.setComponent(component); + public Intent getAssistIntent(Context context) { + return getAssistIntent(context, UserHandle.myUserId()); + } + + /** + * Gets an intent for launching installed assistant activity, or null if not available. + * @return The assist intent. + * + * @hide + */ + public Intent getAssistIntent(Context context, int userHandle) { + try { + ComponentName comp = mService.getAssistIntent(userHandle); + if (comp == null) { + return null; + } + Intent intent = new Intent(Intent.ACTION_ASSIST); + intent.setComponent(comp); return intent; + } catch (RemoteException re) { + Log.e(TAG, "getAssistIntent() failed: " + re); + return null; } - return null; } } diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index dd9f337..3d656c7 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -97,13 +97,13 @@ public class StatusBarManager { } /** - * Expand the status bar. + * Expand the notifications panel. */ - public void expand() { + public void expandNotificationsPanel() { try { final IStatusBarService svc = getService(); if (svc != null) { - svc.expand(); + svc.expandNotificationsPanel(); } } catch (RemoteException ex) { // system process is dead anyway. @@ -112,13 +112,28 @@ public class StatusBarManager { } /** - * Collapse the status bar. + * Collapse the notifications and settings panels. */ - public void collapse() { + public void collapsePanels() { try { final IStatusBarService svc = getService(); if (svc != null) { - svc.collapse(); + svc.collapsePanels(); + } + } catch (RemoteException ex) { + // system process is dead anyway. + throw new RuntimeException(ex); + } + } + + /** + * Expand the settings panel. + */ + public void expandSettingsPanel() { + try { + final IStatusBarService svc = getService(); + if (svc != null) { + svc.expandSettingsPanel(); } } catch (RemoteException ex) { // system process is dead anyway. diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java index f21b3fd..3e0ac7e 100644 --- a/core/java/android/app/TaskStackBuilder.java +++ b/core/java/android/app/TaskStackBuilder.java @@ -23,6 +23,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -124,24 +125,16 @@ public class TaskStackBuilder { * @return This TaskStackBuilder for method chaining */ public TaskStackBuilder addParentStack(Activity sourceActivity) { - final int insertAt = mIntents.size(); - Intent parent = sourceActivity.getParentActivityIntent(); - PackageManager pm = sourceActivity.getPackageManager(); - while (parent != null) { - mIntents.add(insertAt, parent); - try { - ActivityInfo info = pm.getActivityInfo(parent.getComponent(), 0); - String parentActivity = info.parentActivityName; - if (parentActivity != null) { - parent = new Intent().setComponent( - new ComponentName(mSourceContext, parentActivity)); - } else { - parent = null; - } - } catch (NameNotFoundException e) { - Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); - throw new IllegalArgumentException(e); + final Intent parent = sourceActivity.getParentActivityIntent(); + if (parent != null) { + // We have the actual parent intent, build the rest from static metadata + // then add the direct parent intent to the end. + ComponentName target = parent.getComponent(); + if (target == null) { + target = parent.resolveActivity(mSourceContext.getPackageManager()); } + addParentStack(target); + addNextIntent(parent); } return this; } @@ -155,24 +148,7 @@ public class TaskStackBuilder { * @return This TaskStackBuilder for method chaining */ public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) { - final int insertAt = mIntents.size(); - PackageManager pm = mSourceContext.getPackageManager(); - try { - ActivityInfo info = pm.getActivityInfo( - new ComponentName(mSourceContext, sourceActivityClass), 0); - String parentActivity = info.parentActivityName; - while (parentActivity != null) { - Intent parent = new Intent().setComponent( - new ComponentName(mSourceContext, parentActivity)); - mIntents.add(insertAt, parent); - info = pm.getActivityInfo(parent.getComponent(), 0); - parentActivity = info.parentActivityName; - } - } catch (NameNotFoundException e) { - Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); - throw new IllegalArgumentException(e); - } - return this; + return addParentStack(new ComponentName(mSourceContext, sourceActivityClass)); } /** @@ -191,11 +167,13 @@ public class TaskStackBuilder { ActivityInfo info = pm.getActivityInfo(sourceActivityName, 0); String parentActivity = info.parentActivityName; while (parentActivity != null) { - Intent parent = new Intent().setComponent( - new ComponentName(info.packageName, parentActivity)); - mIntents.add(insertAt, parent); - info = pm.getActivityInfo(parent.getComponent(), 0); + final ComponentName target = new ComponentName(info.packageName, parentActivity); + info = pm.getActivityInfo(target, 0); parentActivity = info.parentActivityName; + final Intent parent = parentActivity == null && insertAt == 0 + ? Intent.makeMainActivity(target) + : new Intent().setComponent(target); + mIntents.add(insertAt, parent); } } catch (NameNotFoundException e) { Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); @@ -232,22 +210,26 @@ public class TaskStackBuilder { /** * Start the task stack constructed by this builder. - * - * @param options Additional options for how the Activity should be started. - * See {@link android.content.Context#startActivity(Intent, Bundle) - * Context.startActivity(Intent, Bundle)} for more details. + * @hide */ - public void startActivities(Bundle options) { + public void startActivities(Bundle options, UserHandle userHandle) { if (mIntents.isEmpty()) { throw new IllegalStateException( "No intents added to TaskStackBuilder; cannot startActivities"); } - Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]); - intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_CLEAR_TASK | - Intent.FLAG_ACTIVITY_TASK_ON_HOME); - mSourceContext.startActivities(intents, options); + mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle); + } + + /** + * Start the task stack constructed by this builder. + * + * @param options Additional options for how the Activity should be started. + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + */ + public void startActivities(Bundle options) { + startActivities(options, new UserHandle(UserHandle.myUserId())); } /** @@ -287,11 +269,22 @@ public class TaskStackBuilder { "No intents added to TaskStackBuilder; cannot getPendingIntent"); } - Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]); - intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_CLEAR_TASK | - Intent.FLAG_ACTIVITY_TASK_ON_HOME); - return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags, options); + return PendingIntent.getActivities(mSourceContext, requestCode, getIntents(), + flags, options); + } + + /** + * @hide + */ + public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options, + UserHandle user) { + if (mIntents.isEmpty()) { + throw new IllegalStateException( + "No intents added to TaskStackBuilder; cannot getPendingIntent"); + } + + return PendingIntent.getActivitiesAsUser(mSourceContext, requestCode, getIntents(), flags, + options, user); } /** @@ -302,6 +295,15 @@ public class TaskStackBuilder { * @return An array containing the intents added to this builder. */ public Intent[] getIntents() { - return mIntents.toArray(new Intent[mIntents.size()]); + Intent[] intents = new Intent[mIntents.size()]; + if (intents.length == 0) return intents; + + intents[0] = new Intent(mIntents.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TASK | + Intent.FLAG_ACTIVITY_TASK_ON_HOME); + for (int i = 1; i < intents.length; i++) { + intents[i] = new Intent(mIntents.get(i)); + } + return intents; } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index c131549..9c0064e 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -42,6 +42,8 @@ import android.os.ServiceManager; import android.util.DisplayMetrics; import android.util.Log; import android.view.ViewRootImpl; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; import java.io.FileOutputStream; import java.io.IOException; @@ -241,7 +243,7 @@ public class WallpaperManager { } mWallpaper = null; try { - mWallpaper = getCurrentWallpaperLocked(); + mWallpaper = getCurrentWallpaperLocked(context); } catch (OutOfMemoryError e) { Log.w(TAG, "No memory load current wallpaper", e); } @@ -264,7 +266,7 @@ public class WallpaperManager { } } - private Bitmap getCurrentWallpaperLocked() { + private Bitmap getCurrentWallpaperLocked(Context context) { try { Bundle params = new Bundle(); ParcelFileDescriptor fd = mService.getWallpaper(this, params); @@ -276,7 +278,7 @@ public class WallpaperManager { BitmapFactory.Options options = new BitmapFactory.Options(); Bitmap bm = BitmapFactory.decodeFileDescriptor( fd.getFileDescriptor(), null, options); - return generateBitmap(bm, width, height); + return generateBitmap(context, bm, width, height); } catch (OutOfMemoryError e) { Log.w(TAG, "Can't decode file", e); } finally { @@ -304,7 +306,7 @@ public class WallpaperManager { try { BitmapFactory.Options options = new BitmapFactory.Options(); Bitmap bm = BitmapFactory.decodeStream(is, null, options); - return generateBitmap(bm, width, height); + return generateBitmap(context, bm, width, height); } catch (OutOfMemoryError e) { Log.w(TAG, "Can't decode stream", e); } finally { @@ -588,6 +590,25 @@ public class WallpaperManager { } /** + * Return whether any users are currently set to use the wallpaper + * with the given resource ID. That is, their wallpaper has been + * set through {@link #setResource(int)} with the same resource id. + */ + public boolean hasResourceWallpaper(int resid) { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + return false; + } + try { + Resources resources = mContext.getResources(); + String name = "res:" + resources.getResourceName(resid); + return sGlobals.mService.hasNamedWallpaper(name); + } catch (RemoteException e) { + return false; + } + } + + /** * Returns the desired minimum width for the wallpaper. Callers of * {@link #setBitmap(android.graphics.Bitmap)} or * {@link #setStream(java.io.InputStream)} should check this value @@ -688,7 +709,7 @@ public class WallpaperManager { public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { try { //Log.v(TAG, "Sending new wallpaper offsets from app..."); - ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep); //Log.v(TAG, "...app returning after sending offsets!"); } catch (RemoteException e) { @@ -726,7 +747,7 @@ public class WallpaperManager { int x, int y, int z, Bundle extras) { try { //Log.v(TAG, "Sending new wallpaper offsets from app..."); - ViewRootImpl.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand( + WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand( windowToken, action, x, y, z, extras, false); //Log.v(TAG, "...app returning after sending offsets!"); } catch (RemoteException e) { @@ -746,7 +767,7 @@ public class WallpaperManager { */ public void clearWallpaperOffsets(IBinder windowToken) { try { - ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( windowToken, -1, -1, -1, -1); } catch (RemoteException e) { // Ignore. @@ -768,12 +789,15 @@ public class WallpaperManager { setResource(com.android.internal.R.drawable.default_wallpaper); } - static Bitmap generateBitmap(Bitmap bm, int width, int height) { + static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) { if (bm == null) { return null; } - bm.setDensity(DisplayMetrics.DENSITY_DEVICE); + WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics metrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(metrics); + bm.setDensity(metrics.noncompatDensityDpi); if (width <= 0 || height <= 0 || (bm.getWidth() == width && bm.getHeight() == height)) { @@ -783,7 +807,7 @@ public class WallpaperManager { // This is the final bitmap we want to return. try { Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - newbm.setDensity(DisplayMetrics.DENSITY_DEVICE); + newbm.setDensity(metrics.noncompatDensityDpi); Canvas c = new Canvas(newbm); Rect targetRect = new Rect(); diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 1c37414..b351811 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -50,23 +50,23 @@ import java.util.HashMap; */ public final class DeviceAdminInfo implements Parcelable { static final String TAG = "DeviceAdminInfo"; - + /** * A type of policy that this device admin can use: limit the passwords * that the user can select, via {@link DevicePolicyManager#setPasswordQuality} * and {@link DevicePolicyManager#setPasswordMinimumLength}. - * + * * <p>To control this policy, the device admin must have a "limit-password" * tag in the "uses-policies" section of its meta-data. */ public static final int USES_POLICY_LIMIT_PASSWORD = 0; - + /** * A type of policy that this device admin can use: able to watch login * attempts from the user, via {@link DeviceAdminReceiver#ACTION_PASSWORD_FAILED}, * {@link DeviceAdminReceiver#ACTION_PASSWORD_SUCCEEDED}, and * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts}. - * + * * <p>To control this policy, the device admin must have a "watch-login" * tag in the "uses-policies" section of its meta-data. */ @@ -76,7 +76,7 @@ public final class DeviceAdminInfo implements Parcelable { * A type of policy that this device admin can use: able to reset the * user's password via * {@link DevicePolicyManager#resetPassword}. - * + * * <p>To control this policy, the device admin must have a "reset-password" * tag in the "uses-policies" section of its meta-data. */ @@ -87,7 +87,7 @@ public final class DeviceAdminInfo implements Parcelable { * to lock via{@link DevicePolicyManager#lockNow} or limit the * maximum lock timeout for the device via * {@link DevicePolicyManager#setMaximumTimeToLock}. - * + * * <p>To control this policy, the device admin must have a "force-lock" * tag in the "uses-policies" section of its meta-data. */ @@ -97,7 +97,7 @@ public final class DeviceAdminInfo implements Parcelable { * A type of policy that this device admin can use: able to factory * reset the device, erasing all of the user's data, via * {@link DevicePolicyManager#wipeData}. - * + * * <p>To control this policy, the device admin must have a "wipe-data" * tag in the "uses-policies" section of its meta-data. */ @@ -138,13 +138,21 @@ public final class DeviceAdminInfo implements Parcelable { */ public static final int USES_POLICY_DISABLE_CAMERA = 8; + /** + * A type of policy that this device admin can use: disables use of keyguard widgets. + * + * <p>To control this policy, the device admin must have a "disable-keyguard-widgets" + * tag in the "uses-policies" section of its meta-data. + */ + public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9; + /** @hide */ public static class PolicyInfo { public final int ident; final public String tag; final public int label; final public int description; - + public PolicyInfo(int identIn, String tagIn, int labelIn, int descriptionIn) { ident = identIn; tag = tagIn; @@ -152,11 +160,11 @@ public final class DeviceAdminInfo implements Parcelable { description = descriptionIn; } } - + static ArrayList<PolicyInfo> sPoliciesDisplayOrder = new ArrayList<PolicyInfo>(); static HashMap<String, Integer> sKnownPolicies = new HashMap<String, Integer>(); static SparseArray<PolicyInfo> sRevKnownPolicies = new SparseArray<PolicyInfo>(); - + static { sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_WIPE_DATA, "wipe-data", com.android.internal.R.string.policylab_wipeData, @@ -185,6 +193,10 @@ public final class DeviceAdminInfo implements Parcelable { sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_DISABLE_CAMERA, "disable-camera", com.android.internal.R.string.policylab_disableCamera, com.android.internal.R.string.policydesc_disableCamera)); + sPoliciesDisplayOrder.add(new PolicyInfo( + USES_POLICY_DISABLE_KEYGUARD_FEATURES, "disable-keyguard-features", + com.android.internal.R.string.policylab_disableKeyguardFeatures, + com.android.internal.R.string.policydesc_disableKeyguardFeatures)); for (int i=0; i<sPoliciesDisplayOrder.size(); i++) { PolicyInfo pi = sPoliciesDisplayOrder.get(i); @@ -192,25 +204,25 @@ public final class DeviceAdminInfo implements Parcelable { sKnownPolicies.put(pi.tag, pi.ident); } } - + /** * The BroadcastReceiver that implements this device admin component. */ final ResolveInfo mReceiver; - + /** * Whether this should be visible to the user. */ boolean mVisible; - + /** * The policies this administrator needs access to. */ int mUsesPolicies; - + /** * Constructor. - * + * * @param context The Context in which we are parsing the device admin. * @param receiver The ResolveInfo returned from the package manager about * this device admin's component. @@ -219,9 +231,9 @@ public final class DeviceAdminInfo implements Parcelable { throws XmlPullParserException, IOException { mReceiver = receiver; ActivityInfo ai = receiver.activityInfo; - + PackageManager pm = context.getPackageManager(); - + XmlResourceParser parser = null; try { parser = ai.loadXmlMetaData(pm, DeviceAdminReceiver.DEVICE_ADMIN_META_DATA); @@ -229,30 +241,30 @@ public final class DeviceAdminInfo implements Parcelable { throw new XmlPullParserException("No " + DeviceAdminReceiver.DEVICE_ADMIN_META_DATA + " meta-data"); } - + Resources res = pm.getResourcesForApplication(ai.applicationInfo); - + AttributeSet attrs = Xml.asAttributeSet(parser); - + int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { } - + String nodeName = parser.getName(); if (!"device-admin".equals(nodeName)) { throw new XmlPullParserException( "Meta-data does not start with device-admin tag"); } - + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.DeviceAdmin); mVisible = sa.getBoolean( com.android.internal.R.styleable.DeviceAdmin_visible, true); - + sa.recycle(); - + int outerDepth = parser.getDepth(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { @@ -290,14 +302,14 @@ public final class DeviceAdminInfo implements Parcelable { mReceiver = ResolveInfo.CREATOR.createFromParcel(source); mUsesPolicies = source.readInt(); } - + /** * Return the .apk package that implements this device admin. */ public String getPackageName() { return mReceiver.activityInfo.packageName; } - + /** * Return the class name of the receiver component that implements * this device admin. @@ -321,20 +333,20 @@ public final class DeviceAdminInfo implements Parcelable { return new ComponentName(mReceiver.activityInfo.packageName, mReceiver.activityInfo.name); } - + /** * Load the user-displayed label for this device admin. - * + * * @param pm Supply a PackageManager used to load the device admin's * resources. */ public CharSequence loadLabel(PackageManager pm) { return mReceiver.loadLabel(pm); } - + /** * Load user-visible description associated with this device admin. - * + * * @param pm Supply a PackageManager used to load the device admin's * resources. */ @@ -351,17 +363,17 @@ public final class DeviceAdminInfo implements Parcelable { } throw new NotFoundException(); } - + /** * Load the user-displayed icon for this device admin. - * + * * @param pm Supply a PackageManager used to load the device admin's * resources. */ public Drawable loadIcon(PackageManager pm) { return mReceiver.loadIcon(pm); } - + /** * Returns whether this device admin would like to be visible to the * user, even when it is not enabled. @@ -369,7 +381,7 @@ public final class DeviceAdminInfo implements Parcelable { public boolean isVisible() { return mVisible; } - + /** * Return true if the device admin has requested that it be able to use * the given policy control. The possible policy identifier inputs are: @@ -382,7 +394,7 @@ public final class DeviceAdminInfo implements Parcelable { public boolean usesPolicy(int policyIdent) { return (mUsesPolicies & (1<<policyIdent)) != 0; } - + /** * Return the XML tag name for the given policy identifier. Valid identifiers * are as per {@link #usesPolicy(int)}. If the given identifier is not @@ -391,7 +403,7 @@ public final class DeviceAdminInfo implements Parcelable { public String getTagForPolicy(int policyIdent) { return sRevKnownPolicies.get(policyIdent).tag; } - + /** @hide */ public ArrayList<PolicyInfo> getUsedPolicies() { ArrayList<PolicyInfo> res = new ArrayList<PolicyInfo>(); @@ -403,25 +415,25 @@ public final class DeviceAdminInfo implements Parcelable { } return res; } - + /** @hide */ public void writePoliciesToXml(XmlSerializer out) throws IllegalArgumentException, IllegalStateException, IOException { out.attribute(null, "flags", Integer.toString(mUsesPolicies)); } - + /** @hide */ public void readPoliciesFromXml(XmlPullParser parser) throws XmlPullParserException, IOException { mUsesPolicies = Integer.parseInt( parser.getAttributeValue(null, "flags")); } - + public void dump(Printer pw, String prefix) { pw.println(prefix + "Receiver:"); mReceiver.dump(pw, prefix + " "); } - + @Override public String toString() { return "DeviceAdminInfo{" + mReceiver.activityInfo.name + "}"; @@ -429,7 +441,7 @@ public final class DeviceAdminInfo implements Parcelable { /** * Used to package this object into a {@link Parcel}. - * + * * @param dest The {@link Parcel} to be written. * @param flags The flags used for parceling. */ diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0b58396..6966793 100755 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import java.io.IOException; @@ -131,7 +132,7 @@ public class DevicePolicyManager { public boolean isAdminActive(ComponentName who) { if (mService != null) { try { - return mService.isAdminActive(who); + return mService.isAdminActive(who, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -147,7 +148,7 @@ public class DevicePolicyManager { public List<ComponentName> getActiveAdmins() { if (mService != null) { try { - return mService.getActiveAdmins(); + return mService.getActiveAdmins(UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -156,12 +157,14 @@ public class DevicePolicyManager { } /** + * Used by package administration code to determine if a package can be stopped + * or uninstalled. * @hide */ public boolean packageHasActiveAdmins(String packageName) { if (mService != null) { try { - return mService.packageHasActiveAdmins(packageName); + return mService.packageHasActiveAdmins(packageName, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -178,7 +181,7 @@ public class DevicePolicyManager { public void removeActiveAdmin(ComponentName who) { if (mService != null) { try { - mService.removeActiveAdmin(who); + mService.removeActiveAdmin(who, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -197,7 +200,7 @@ public class DevicePolicyManager { public boolean hasGrantedPolicy(ComponentName admin, int usesPolicy) { if (mService != null) { try { - return mService.hasGrantedPolicy(admin, usesPolicy); + return mService.hasGrantedPolicy(admin, usesPolicy, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -289,7 +292,7 @@ public class DevicePolicyManager { public void setPasswordQuality(ComponentName admin, int quality) { if (mService != null) { try { - mService.setPasswordQuality(admin, quality); + mService.setPasswordQuality(admin, quality, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -303,9 +306,14 @@ public class DevicePolicyManager { * all admins. */ public int getPasswordQuality(ComponentName admin) { + return getPasswordQuality(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordQuality(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordQuality(admin); + return mService.getPasswordQuality(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -337,7 +345,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLength(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLength(admin, length); + mService.setPasswordMinimumLength(admin, length, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -351,9 +359,14 @@ public class DevicePolicyManager { * all admins. */ public int getPasswordMinimumLength(ComponentName admin) { + return getPasswordMinimumLength(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordMinimumLength(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordMinimumLength(admin); + return mService.getPasswordMinimumLength(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -386,7 +399,7 @@ public class DevicePolicyManager { public void setPasswordMinimumUpperCase(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumUpperCase(admin, length); + mService.setPasswordMinimumUpperCase(admin, length, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -406,9 +419,14 @@ public class DevicePolicyManager { * password. */ public int getPasswordMinimumUpperCase(ComponentName admin) { + return getPasswordMinimumUpperCase(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordMinimumUpperCase(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordMinimumUpperCase(admin); + return mService.getPasswordMinimumUpperCase(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -441,7 +459,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLowerCase(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLowerCase(admin, length); + mService.setPasswordMinimumLowerCase(admin, length, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -461,9 +479,14 @@ public class DevicePolicyManager { * password. */ public int getPasswordMinimumLowerCase(ComponentName admin) { + return getPasswordMinimumLowerCase(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordMinimumLowerCase(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordMinimumLowerCase(admin); + return mService.getPasswordMinimumLowerCase(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -495,7 +518,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLetters(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLetters(admin, length); + mService.setPasswordMinimumLetters(admin, length, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -514,9 +537,14 @@ public class DevicePolicyManager { * @return The minimum number of letters required in the password. */ public int getPasswordMinimumLetters(ComponentName admin) { + return getPasswordMinimumLetters(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordMinimumLetters(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordMinimumLetters(admin); + return mService.getPasswordMinimumLetters(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -548,7 +576,7 @@ public class DevicePolicyManager { public void setPasswordMinimumNumeric(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumNumeric(admin, length); + mService.setPasswordMinimumNumeric(admin, length, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -567,9 +595,14 @@ public class DevicePolicyManager { * @return The minimum number of numerical digits required in the password. */ public int getPasswordMinimumNumeric(ComponentName admin) { + return getPasswordMinimumNumeric(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordMinimumNumeric(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordMinimumNumeric(admin); + return mService.getPasswordMinimumNumeric(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -601,7 +634,7 @@ public class DevicePolicyManager { public void setPasswordMinimumSymbols(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumSymbols(admin, length); + mService.setPasswordMinimumSymbols(admin, length, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -620,9 +653,14 @@ public class DevicePolicyManager { * @return The minimum number of symbols required in the password. */ public int getPasswordMinimumSymbols(ComponentName admin) { + return getPasswordMinimumSymbols(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordMinimumSymbols(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordMinimumSymbols(admin); + return mService.getPasswordMinimumSymbols(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -654,7 +692,7 @@ public class DevicePolicyManager { public void setPasswordMinimumNonLetter(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumNonLetter(admin, length); + mService.setPasswordMinimumNonLetter(admin, length, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -673,9 +711,14 @@ public class DevicePolicyManager { * @return The minimum number of letters required in the password. */ public int getPasswordMinimumNonLetter(ComponentName admin) { + return getPasswordMinimumNonLetter(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordMinimumNonLetter(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordMinimumNonLetter(admin); + return mService.getPasswordMinimumNonLetter(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -708,7 +751,7 @@ public class DevicePolicyManager { public void setPasswordHistoryLength(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordHistoryLength(admin, length); + mService.setPasswordHistoryLength(admin, length, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -737,7 +780,7 @@ public class DevicePolicyManager { public void setPasswordExpirationTimeout(ComponentName admin, long timeout) { if (mService != null) { try { - mService.setPasswordExpirationTimeout(admin, timeout); + mService.setPasswordExpirationTimeout(admin, timeout, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -756,7 +799,7 @@ public class DevicePolicyManager { public long getPasswordExpirationTimeout(ComponentName admin) { if (mService != null) { try { - return mService.getPasswordExpirationTimeout(admin); + return mService.getPasswordExpirationTimeout(admin, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -776,7 +819,7 @@ public class DevicePolicyManager { public long getPasswordExpiration(ComponentName admin) { if (mService != null) { try { - return mService.getPasswordExpiration(admin); + return mService.getPasswordExpiration(admin, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -792,9 +835,14 @@ public class DevicePolicyManager { * @return The length of the password history */ public int getPasswordHistoryLength(ComponentName admin) { + return getPasswordHistoryLength(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getPasswordHistoryLength(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordHistoryLength(admin); + return mService.getPasswordHistoryLength(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -828,7 +876,7 @@ public class DevicePolicyManager { public boolean isActivePasswordSufficient() { if (mService != null) { try { - return mService.isActivePasswordSufficient(); + return mService.isActivePasswordSufficient(UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -847,7 +895,7 @@ public class DevicePolicyManager { public int getCurrentFailedPasswordAttempts() { if (mService != null) { try { - return mService.getCurrentFailedPasswordAttempts(); + return mService.getCurrentFailedPasswordAttempts(UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -877,7 +925,7 @@ public class DevicePolicyManager { public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) { if (mService != null) { try { - mService.setMaximumFailedPasswordsForWipe(admin, num); + mService.setMaximumFailedPasswordsForWipe(admin, num, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -892,9 +940,14 @@ public class DevicePolicyManager { * all admins. */ public int getMaximumFailedPasswordsForWipe(ComponentName admin) { + return getMaximumFailedPasswordsForWipe(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getMaximumFailedPasswordsForWipe(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getMaximumFailedPasswordsForWipe(admin); + return mService.getMaximumFailedPasswordsForWipe(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -933,7 +986,7 @@ public class DevicePolicyManager { public boolean resetPassword(String password, int flags) { if (mService != null) { try { - return mService.resetPassword(password, flags); + return mService.resetPassword(password, flags, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -957,7 +1010,7 @@ public class DevicePolicyManager { public void setMaximumTimeToLock(ComponentName admin, long timeMs) { if (mService != null) { try { - mService.setMaximumTimeToLock(admin, timeMs); + mService.setMaximumTimeToLock(admin, timeMs, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -971,9 +1024,14 @@ public class DevicePolicyManager { * all admins. */ public long getMaximumTimeToLock(ComponentName admin) { + return getMaximumTimeToLock(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public long getMaximumTimeToLock(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getMaximumTimeToLock(admin); + return mService.getMaximumTimeToLock(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1021,7 +1079,7 @@ public class DevicePolicyManager { public void wipeData(int flags) { if (mService != null) { try { - mService.wipeData(flags); + mService.wipeData(flags, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1090,7 +1148,7 @@ public class DevicePolicyManager { } android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec); } - return mService.setGlobalProxy(admin, hostSpec, exclSpec); + return mService.setGlobalProxy(admin, hostSpec, exclSpec, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1107,7 +1165,7 @@ public class DevicePolicyManager { public ComponentName getGlobalProxyAdmin() { if (mService != null) { try { - return mService.getGlobalProxyAdmin(); + return mService.getGlobalProxyAdmin(UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1155,6 +1213,26 @@ public class DevicePolicyManager { = "android.app.action.START_ENCRYPTION"; /** + * Widgets are enabled in keyguard + */ + public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; + + /** + * Disable all keyguard widgets + */ + public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1 << 0; + + /** + * Disable the camera on secure keyguard screens (e.g. PIN/Pattern/Password) + */ + public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 1 << 1; + + /** + * Disable all current and future keyguard customizations + */ + public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff; + + /** * Called by an application that is administering the device to * request that the storage system be encrypted. * @@ -1189,7 +1267,7 @@ public class DevicePolicyManager { public int setStorageEncryption(ComponentName admin, boolean encrypt) { if (mService != null) { try { - return mService.setStorageEncryption(admin, encrypt); + return mService.setStorageEncryption(admin, encrypt, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1209,7 +1287,7 @@ public class DevicePolicyManager { public boolean getStorageEncryption(ComponentName admin) { if (mService != null) { try { - return mService.getStorageEncryption(admin); + return mService.getStorageEncryption(admin, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1234,9 +1312,14 @@ public class DevicePolicyManager { * {@link #ENCRYPTION_STATUS_ACTIVATING}, or{@link #ENCRYPTION_STATUS_ACTIVE}. */ public int getStorageEncryptionStatus() { + return getStorageEncryptionStatus(UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getStorageEncryptionStatus(int userHandle) { if (mService != null) { try { - return mService.getStorageEncryptionStatus(); + return mService.getStorageEncryptionStatus(userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1259,7 +1342,7 @@ public class DevicePolicyManager { public void setCameraDisabled(ComponentName admin, boolean disabled) { if (mService != null) { try { - mService.setCameraDisabled(admin, disabled); + mService.setCameraDisabled(admin, disabled, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1273,9 +1356,14 @@ public class DevicePolicyManager { * have disabled the camera */ public boolean getCameraDisabled(ComponentName admin) { + return getCameraDisabled(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean getCameraDisabled(ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getCameraDisabled(admin); + return mService.getCameraDisabled(admin, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1284,12 +1372,57 @@ public class DevicePolicyManager { } /** + * Called by an application that is administering the device to disable keyguard customizations, + * such as widgets. After setting this, keyguard features will be disabled according to the + * provided feature list. + * + * <p>The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} to be able to call + * this method; if it has not, a security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param which {@link DevicePolicyManager#KEYGUARD_DISABLE_WIDGETS_ALL} or + * {@link DevicePolicyManager#KEYGUARD_DISABLE_FEATURES_NONE} (the default). + */ + public void setKeyguardDisabledFeatures(ComponentName admin, int which) { + if (mService != null) { + try { + mService.setKeyguardDisabledFeatures(admin, which, UserHandle.myUserId()); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Determine whether or not features have been disabled in keyguard either by the current + * admin, if specified, or all admins. + * @param admin The name of the admin component to check, or null to check if any admins + * have disabled features in keyguard. + */ + public int getKeyguardDisabledFeatures(ComponentName admin) { + return getKeyguardDisabledFeatures(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public int getKeyguardDisabledFeatures(ComponentName admin, int userHandle) { + if (mService != null) { + try { + return mService.getKeyguardDisabledFeatures(admin, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return KEYGUARD_DISABLE_FEATURES_NONE; + } + + /** * @hide */ public void setActiveAdmin(ComponentName policyReceiver, boolean refreshing) { if (mService != null) { try { - mService.setActiveAdmin(policyReceiver, refreshing); + mService.setActiveAdmin(policyReceiver, refreshing, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1330,7 +1463,7 @@ public class DevicePolicyManager { public void getRemoveWarning(ComponentName admin, RemoteCallback result) { if (mService != null) { try { - mService.getRemoveWarning(admin, result); + mService.getRemoveWarning(admin, result, UserHandle.myUserId()); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1341,11 +1474,11 @@ public class DevicePolicyManager { * @hide */ public void setActivePasswordState(int quality, int length, int letters, int uppercase, - int lowercase, int numbers, int symbols, int nonletter) { + int lowercase, int numbers, int symbols, int nonletter, int userHandle) { if (mService != null) { try { mService.setActivePasswordState(quality, length, letters, uppercase, lowercase, - numbers, symbols, nonletter); + numbers, symbols, nonletter, userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1355,10 +1488,10 @@ public class DevicePolicyManager { /** * @hide */ - public void reportFailedPasswordAttempt() { + public void reportFailedPasswordAttempt(int userHandle) { if (mService != null) { try { - mService.reportFailedPasswordAttempt(); + mService.reportFailedPasswordAttempt(userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1368,14 +1501,13 @@ public class DevicePolicyManager { /** * @hide */ - public void reportSuccessfulPasswordAttempt() { + public void reportSuccessfulPasswordAttempt(int userHandle) { if (mService != null) { try { - mService.reportSuccessfulPasswordAttempt(); + mService.reportSuccessfulPasswordAttempt(userHandle); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } } } - } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 9419a62..e061ab3 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -25,73 +25,76 @@ import android.os.RemoteCallback; * {@hide} */ interface IDevicePolicyManager { - void setPasswordQuality(in ComponentName who, int quality); - int getPasswordQuality(in ComponentName who); + void setPasswordQuality(in ComponentName who, int quality, int userHandle); + int getPasswordQuality(in ComponentName who, int userHandle); - void setPasswordMinimumLength(in ComponentName who, int length); - int getPasswordMinimumLength(in ComponentName who); + void setPasswordMinimumLength(in ComponentName who, int length, int userHandle); + int getPasswordMinimumLength(in ComponentName who, int userHandle); - void setPasswordMinimumUpperCase(in ComponentName who, int length); - int getPasswordMinimumUpperCase(in ComponentName who); + void setPasswordMinimumUpperCase(in ComponentName who, int length, int userHandle); + int getPasswordMinimumUpperCase(in ComponentName who, int userHandle); - void setPasswordMinimumLowerCase(in ComponentName who, int length); - int getPasswordMinimumLowerCase(in ComponentName who); + void setPasswordMinimumLowerCase(in ComponentName who, int length, int userHandle); + int getPasswordMinimumLowerCase(in ComponentName who, int userHandle); - void setPasswordMinimumLetters(in ComponentName who, int length); - int getPasswordMinimumLetters(in ComponentName who); + void setPasswordMinimumLetters(in ComponentName who, int length, int userHandle); + int getPasswordMinimumLetters(in ComponentName who, int userHandle); - void setPasswordMinimumNumeric(in ComponentName who, int length); - int getPasswordMinimumNumeric(in ComponentName who); + void setPasswordMinimumNumeric(in ComponentName who, int length, int userHandle); + int getPasswordMinimumNumeric(in ComponentName who, int userHandle); - void setPasswordMinimumSymbols(in ComponentName who, int length); - int getPasswordMinimumSymbols(in ComponentName who); + void setPasswordMinimumSymbols(in ComponentName who, int length, int userHandle); + int getPasswordMinimumSymbols(in ComponentName who, int userHandle); - void setPasswordMinimumNonLetter(in ComponentName who, int length); - int getPasswordMinimumNonLetter(in ComponentName who); - - void setPasswordHistoryLength(in ComponentName who, int length); - int getPasswordHistoryLength(in ComponentName who); + void setPasswordMinimumNonLetter(in ComponentName who, int length, int userHandle); + int getPasswordMinimumNonLetter(in ComponentName who, int userHandle); - void setPasswordExpirationTimeout(in ComponentName who, long expiration); - long getPasswordExpirationTimeout(in ComponentName who); + void setPasswordHistoryLength(in ComponentName who, int length, int userHandle); + int getPasswordHistoryLength(in ComponentName who, int userHandle); - long getPasswordExpiration(in ComponentName who); + void setPasswordExpirationTimeout(in ComponentName who, long expiration, int userHandle); + long getPasswordExpirationTimeout(in ComponentName who, int userHandle); + + long getPasswordExpiration(in ComponentName who, int userHandle); + + boolean isActivePasswordSufficient(int userHandle); + int getCurrentFailedPasswordAttempts(int userHandle); + + void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, int userHandle); + int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle); + + boolean resetPassword(String password, int flags, int userHandle); + + void setMaximumTimeToLock(in ComponentName who, long timeMs, int userHandle); + long getMaximumTimeToLock(in ComponentName who, int userHandle); - boolean isActivePasswordSufficient(); - int getCurrentFailedPasswordAttempts(); - - void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num); - int getMaximumFailedPasswordsForWipe(in ComponentName admin); - - boolean resetPassword(String password, int flags); - - void setMaximumTimeToLock(in ComponentName who, long timeMs); - long getMaximumTimeToLock(in ComponentName who); - void lockNow(); - - void wipeData(int flags); - - ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList); - ComponentName getGlobalProxyAdmin(); - - int setStorageEncryption(in ComponentName who, boolean encrypt); - boolean getStorageEncryption(in ComponentName who); - int getStorageEncryptionStatus(); - - void setCameraDisabled(in ComponentName who, boolean disabled); - boolean getCameraDisabled(in ComponentName who); - - void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing); - boolean isAdminActive(in ComponentName policyReceiver); - List<ComponentName> getActiveAdmins(); - boolean packageHasActiveAdmins(String packageName); - void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result); - void removeActiveAdmin(in ComponentName policyReceiver); - boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy); - + + void wipeData(int flags, int userHandle); + + ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle); + ComponentName getGlobalProxyAdmin(int userHandle); + + int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle); + boolean getStorageEncryption(in ComponentName who, int userHandle); + int getStorageEncryptionStatus(int userHandle); + + void setCameraDisabled(in ComponentName who, boolean disabled, int userHandle); + boolean getCameraDisabled(in ComponentName who, int userHandle); + + void setKeyguardDisabledFeatures(in ComponentName who, int which, int userHandle); + int getKeyguardDisabledFeatures(in ComponentName who, int userHandle); + + void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing, int userHandle); + boolean isAdminActive(in ComponentName policyReceiver, int userHandle); + List<ComponentName> getActiveAdmins(int userHandle); + boolean packageHasActiveAdmins(String packageName, int userHandle); + void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result, int userHandle); + void removeActiveAdmin(in ComponentName policyReceiver, int userHandle); + boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle); + void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase, - int numbers, int symbols, int nonletter); - void reportFailedPasswordAttempt(); - void reportSuccessfulPasswordAttempt(); + int numbers, int symbols, int nonletter, int userHandle); + void reportFailedPasswordAttempt(int userHandle); + void reportSuccessfulPasswordAttempt(int userHandle); } diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java index a74a268..9e8ab2c 100644 --- a/core/java/android/app/backup/WallpaperBackupHelper.java +++ b/core/java/android/app/backup/WallpaperBackupHelper.java @@ -20,7 +20,9 @@ import android.app.WallpaperManager; import android.content.Context; import android.graphics.BitmapFactory; import android.graphics.Point; +import android.os.Environment; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; import android.util.Slog; import android.view.Display; import android.view.WindowManager; @@ -39,8 +41,12 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu // This path must match what the WallpaperManagerService uses // TODO: Will need to change if backing up non-primary user's wallpaper - public static final String WALLPAPER_IMAGE = "/data/system/users/0/wallpaper"; - public static final String WALLPAPER_INFO = "/data/system/users/0/wallpaper_info.xml"; + public static final String WALLPAPER_IMAGE = + new File(Environment.getUserSystemDirectory(UserHandle.USER_OWNER), + "wallpaper").getAbsolutePath(); + public static final String WALLPAPER_INFO = + new File(Environment.getUserSystemDirectory(UserHandle.USER_OWNER), + "wallpaper_info.xml").getAbsolutePath(); // Use old keys to keep legacy data compatibility and avoid writing two wallpapers public static final String WALLPAPER_IMAGE_KEY = "/data/data/com.android.settings/files/wallpaper"; @@ -50,7 +56,9 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu // will be saved to this file from the restore stream, then renamed to the proper // location if it's deemed suitable. // TODO: Will need to change if backing up non-primary user's wallpaper - private static final String STAGE_FILE = "/data/system/users/0/wallpaper-tmp"; + private static final String STAGE_FILE = + new File(Environment.getUserSystemDirectory(UserHandle.USER_OWNER), + "wallpaper-tmp").getAbsolutePath(); Context mContext; String[] mFiles; diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 08bc0ac..cb61a71 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -19,16 +19,21 @@ package android.appwidget; import java.util.ArrayList; import java.util.HashMap; +import android.app.ActivityThread; import android.content.Context; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.DisplayMetrics; import android.util.TypedValue; import android.widget.RemoteViews; +import android.widget.RemoteViews.OnClickHandler; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; @@ -41,7 +46,8 @@ public class AppWidgetHost { static final int HANDLE_UPDATE = 1; static final int HANDLE_PROVIDER_CHANGED = 2; - static final int HANDLE_VIEW_DATA_CHANGED = 3; + static final int HANDLE_PROVIDERS_CHANGED = 3; + static final int HANDLE_VIEW_DATA_CHANGED = 4; final static Object sServiceLock = new Object(); static IAppWidgetService sService; @@ -52,6 +58,9 @@ public class AppWidgetHost { class Callbacks extends IAppWidgetHost.Stub { public void updateAppWidget(int appWidgetId, RemoteViews views) { + if (isLocalBinder() && views != null) { + views = views.clone(); + } Message msg = mHandler.obtainMessage(HANDLE_UPDATE); msg.arg1 = appWidgetId; msg.obj = views; @@ -59,12 +68,20 @@ public class AppWidgetHost { } public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) { + if (isLocalBinder() && info != null) { + info = info.clone(); + } Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED); msg.arg1 = appWidgetId; msg.obj = info; msg.sendToTarget(); } + public void providersChanged() { + Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED); + msg.sendToTarget(); + } + public void viewDataChanged(int appWidgetId, int viewId) { Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED); msg.arg1 = appWidgetId; @@ -77,7 +94,7 @@ public class AppWidgetHost { public UpdateHandler(Looper looper) { super(looper); } - + public void handleMessage(Message msg) { switch (msg.what) { case HANDLE_UPDATE: { @@ -88,6 +105,10 @@ public class AppWidgetHost { onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj); break; } + case HANDLE_PROVIDERS_CHANGED: { + onProvidersChanged(); + break; + } case HANDLE_VIEW_DATA_CHANGED: { viewDataChanged(msg.arg1, msg.arg2); break; @@ -95,18 +116,31 @@ public class AppWidgetHost { } } } - + Handler mHandler; int mHostId; Callbacks mCallbacks = new Callbacks(); final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>(); + private OnClickHandler mOnClickHandler; public AppWidgetHost(Context context, int hostId) { + this(context, hostId, null, context.getMainLooper()); + } + + /** + * @hide + */ + public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) { mContext = context; mHostId = hostId; - mHandler = new UpdateHandler(context.getMainLooper()); + mOnClickHandler = handler; + mHandler = new UpdateHandler(looper); mDisplayMetrics = context.getResources().getDisplayMetrics(); + bindService(); + } + + private static void bindService() { synchronized (sServiceLock) { if (sService == null) { IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); @@ -122,7 +156,7 @@ public class AppWidgetHost { public void startListening() { int[] updatedIds; ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>(); - + try { if (mPackageName == null) { mPackageName = mContext.getPackageName(); @@ -170,7 +204,40 @@ public class AppWidgetHost { } /** - * Stop listening to changes for this AppWidget. + * Get a appWidgetId for a host in the calling process. + * + * @return a appWidgetId + * @hide + */ + public static int allocateAppWidgetIdForSystem(int hostId) { + checkCallerIsSystem(); + try { + if (sService == null) { + bindService(); + } + Context systemContext = + (Context) ActivityThread.currentActivityThread().getSystemContext(); + String packageName = systemContext.getPackageName(); + return sService.allocateAppWidgetId(packageName, hostId); + } catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + private static void checkCallerIsSystem() { + int uid = Process.myUid(); + if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) { + return; + } + throw new SecurityException("Disallowed call for uid " + uid); + } + + private boolean isLocalBinder() { + return Process.myPid() == Binder.getCallingPid(); + } + + /** + * Stop listening to changes for this AppWidget. */ public void deleteAppWidgetId(int appWidgetId) { synchronized (mViews) { @@ -185,6 +252,22 @@ public class AppWidgetHost { } /** + * Stop listening to changes for this AppWidget. + * @hide + */ + public static void deleteAppWidgetIdForSystem(int appWidgetId) { + checkCallerIsSystem(); + try { + if (sService == null) { + bindService(); + } + sService.deleteAppWidgetId(appWidgetId); + } catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** * Remove all records about this host from the AppWidget manager. * <ul> * <li>Call this when initializing your database, as it might be because of a data wipe.</li> @@ -225,6 +308,7 @@ public class AppWidgetHost { public final AppWidgetHostView createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); + view.setOnClickHandler(mOnClickHandler); view.setAppWidget(appWidgetId, appWidget); synchronized (mViews) { mViews.put(appWidgetId, view); @@ -236,6 +320,7 @@ public class AppWidgetHost { throw new RuntimeException("system server dead?", e); } view.updateAppWidget(views); + return view; } @@ -245,7 +330,7 @@ public class AppWidgetHost { */ protected AppWidgetHostView onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { - return new AppWidgetHostView(context); + return new AppWidgetHostView(context, mOnClickHandler); } /** @@ -255,7 +340,7 @@ public class AppWidgetHost { AppWidgetHostView v; // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the - // AppWidgetService, which doesn't have our context, hence we need to do the + // AppWidgetService, which doesn't have our context, hence we need to do the // conversion here. appWidget.minWidth = TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics); @@ -274,6 +359,14 @@ public class AppWidgetHost { } } + /** + * Called when the set of available widgets changes (ie. widget containing packages + * are added, updated or removed, or widget components are enabled or disabled.) + */ + protected void onProvidersChanged() { + // Do nothing + } + void updateAppWidgetView(int appWidgetId, RemoteViews views) { AppWidgetHostView v; synchronized (mViews) { diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index ed95ae5..f258f17 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -44,6 +44,7 @@ import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; import android.widget.RemoteViews; +import android.widget.RemoteViews.OnClickHandler; import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback; import android.widget.TextView; @@ -83,7 +84,8 @@ public class AppWidgetHostView extends FrameLayout { long mFadeStartTime = -1; Bitmap mOld; Paint mOldPaint = new Paint(); - + private OnClickHandler mOnClickHandler; + /** * Create a host view. Uses default fade animations. */ @@ -92,9 +94,17 @@ public class AppWidgetHostView extends FrameLayout { } /** + * @hide + */ + public AppWidgetHostView(Context context, OnClickHandler handler) { + this(context, android.R.anim.fade_in, android.R.anim.fade_out); + mOnClickHandler = handler; + } + + /** * Create a host view. Uses specified animations when pushing * {@link #updateAppWidget(RemoteViews)}. - * + * * @param animationIn Resource ID of in animation to use * @param animationOut Resource ID of out animation to use */ @@ -109,6 +119,17 @@ public class AppWidgetHostView extends FrameLayout { } /** + * Pass the given handler to RemoteViews when updating this widget. Unless this + * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)} + * should be made. + * @param handler + * @hide + */ + public void setOnClickHandler(OnClickHandler handler) { + mOnClickHandler = handler; + } + + /** * Set the AppWidget that will be displayed by this view. This method also adds default padding * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)} * and can be overridden in order to add custom padding. @@ -177,7 +198,7 @@ public class AppWidgetHostView extends FrameLayout { public int getAppWidgetId() { return mAppWidgetId; } - + public AppWidgetProviderInfo getAppWidgetInfo() { return mInfo; } @@ -205,7 +226,12 @@ public class AppWidgetHostView extends FrameLayout { if (jail == null) jail = new ParcelableSparseArray(); - super.dispatchRestoreInstanceState(jail); + try { + super.dispatchRestoreInstanceState(jail); + } catch (Exception e) { + Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", " + + (mInfo == null ? "null" : mInfo.provider), e); + } } /** @@ -281,12 +307,13 @@ public class AppWidgetHostView extends FrameLayout { * AppWidget provider. Will animate into these new views as needed */ public void updateAppWidget(RemoteViews remoteViews) { + if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld); boolean recycled = false; View content = null; Exception exception = null; - + // Capture the old view into a bitmap so we can do the crossfade. if (CROSSFADE) { if (mFadeStartTime < 0) { @@ -305,7 +332,7 @@ public class AppWidgetHostView extends FrameLayout { } } } - + if (remoteViews == null) { if (mViewMode == VIEW_MODE_DEFAULT) { // We've already done this -- nothing to do. @@ -324,7 +351,7 @@ public class AppWidgetHostView extends FrameLayout { // layout matches, try recycling it if (content == null && layoutId == mLayoutId) { try { - remoteViews.reapply(mContext, mView); + remoteViews.reapply(mContext, mView, mOnClickHandler); content = mView; recycled = true; if (LOGD) Log.d(TAG, "was able to recycled existing layout"); @@ -332,11 +359,11 @@ public class AppWidgetHostView extends FrameLayout { exception = e; } } - + // Try normal RemoteView inflation if (content == null) { try { - content = remoteViews.apply(mContext, this); + content = remoteViews.apply(mContext, this, mOnClickHandler); if (LOGD) Log.d(TAG, "had to inflate new layout"); } catch (RuntimeException e) { exception = e; @@ -346,7 +373,7 @@ public class AppWidgetHostView extends FrameLayout { mLayoutId = layoutId; mViewMode = VIEW_MODE_CONTENT; } - + if (content == null) { if (mViewMode == VIEW_MODE_ERROR) { // We've already done this -- nothing to do. @@ -356,7 +383,7 @@ public class AppWidgetHostView extends FrameLayout { content = getErrorView(); mViewMode = VIEW_MODE_ERROR; } - + if (!recycled) { prepareView(content); addView(content); @@ -455,7 +482,7 @@ public class AppWidgetHostView extends FrameLayout { return super.drawChild(canvas, child, drawingTime); } } - + /** * Prepare the given view to be shown. This might include adjusting * {@link FrameLayout.LayoutParams} before inserting. @@ -471,7 +498,7 @@ public class AppWidgetHostView extends FrameLayout { requested.gravity = Gravity.CENTER; view.setLayoutParams(requested); } - + /** * Inflate and return the default layout requested by AppWidget provider. */ @@ -481,7 +508,7 @@ public class AppWidgetHostView extends FrameLayout { } View defaultView = null; Exception exception = null; - + try { if (mInfo != null) { Context theirContext = mContext.createPackageContext( @@ -491,7 +518,17 @@ public class AppWidgetHostView extends FrameLayout { theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater = inflater.cloneInContext(theirContext); inflater.setFilter(sInflaterFilter); - defaultView = inflater.inflate(mInfo.initialLayout, this, false); + AppWidgetManager manager = AppWidgetManager.getInstance(mContext); + Bundle options = manager.getAppWidgetOptions(mAppWidgetId); + + int layoutId = mInfo.initialLayout; + if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) { + int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY); + if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { + layoutId = mInfo.initialKeyguardLayout; + } + } + defaultView = inflater.inflate(layoutId, this, false); } else { Log.w(TAG, "can't inflate defaultView because mInfo is missing"); } @@ -500,19 +537,19 @@ public class AppWidgetHostView extends FrameLayout { } catch (RuntimeException e) { exception = e; } - + if (exception != null) { Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString()); } - + if (defaultView == null) { if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error"); defaultView = getErrorView(); } - + return defaultView; } - + /** * Inflate and return a view that represents an error state. */ diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 2df675e..888955c 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -150,26 +150,34 @@ public class AppWidgetManager { public static final String EXTRA_APPWIDGET_ID = "appWidgetId"; /** - * An bundle extra that contains the lower bound on the current width, in dips, of a widget instance. + * A bundle extra that contains the lower bound on the current width, in dips, of a widget instance. */ public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth"; /** - * An bundle extra that contains the lower bound on the current height, in dips, of a widget instance. + * A bundle extra that contains the lower bound on the current height, in dips, of a widget instance. */ public static final String OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight"; /** - * An bundle extra that contains the upper bound on the current width, in dips, of a widget instance. + * A bundle extra that contains the upper bound on the current width, in dips, of a widget instance. */ public static final String OPTION_APPWIDGET_MAX_WIDTH = "appWidgetMaxWidth"; /** - * An bundle extra that contains the upper bound on the current width, in dips, of a widget instance. + * A bundle extra that contains the upper bound on the current width, in dips, of a widget instance. */ public static final String OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight"; /** + * A bundle extra that hints to the AppWidgetProvider the category of host that owns this + * this widget. Can have the value {@link + * AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN} or {@link + * AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD}. + */ + public static final String OPTION_APPWIDGET_HOST_CATEGORY = "appWidgetCategory"; + + /** * An intent extra which points to a bundle of extra information for a particular widget id. * In particular this bundle can contain EXTRA_APPWIDGET_WIDTH and EXTRA_APPWIDGET_HEIGHT. */ @@ -208,6 +216,28 @@ public class AppWidgetManager { public static final String EXTRA_CUSTOM_EXTRAS = "customExtras"; /** + * An intent extra to pass to the AppWidget picker which allows the picker to filter + * the list based on the {@link AppWidgetProviderInfo#widgetCategory}. + * + * @hide + */ + public static final String EXTRA_CATEGORY_FILTER = "categoryFilter"; + + /** + * An intent extra to pass to the AppWidget picker which allows the picker to filter + * the list based on the {@link AppWidgetProviderInfo#widgetFeatures}. + * @hide + */ + public static final String EXTRA_FEATURES_FILTER = "featuresFilter"; + + /** + * An intent extra to pass to the AppWidget picker to specify whether or not to sort + * the list of caller-specified extra AppWidgets along with the rest of the AppWidgets + * @hide + */ + public static final String EXTRA_CUSTOM_SORT = "customSort"; + + /** * A sentiel value that the AppWidget manager will never return as a appWidgetId. */ public static final int INVALID_APPWIDGET_ID = 0; @@ -406,10 +436,9 @@ public class AppWidgetManager { * * This update differs from {@link #updateAppWidget(int[], RemoteViews)} in that the * RemoteViews object which is passed is understood to be an incomplete representation of the - * widget, and hence is not cached by the AppWidgetService. Note that because these updates are - * not cached, any state that they modify that is not restored by restoreInstanceState will not - * persist in the case that the widgets are restored using the cached version in - * AppWidgetService. + * widget, and hence does not replace the cached representation of the widget. As of API + * level 17, the new properties set within the views objects will be appended to the cached + * representation of the widget, and hence will persist. * * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)}, * {@link RemoteViews#setScrollPosition(int, int)} and similar commands. @@ -568,7 +597,31 @@ public class AppWidgetManager { */ public void bindAppWidgetId(int appWidgetId, ComponentName provider) { try { - sService.bindAppWidgetId(appWidgetId, provider); + sService.bindAppWidgetId(appWidgetId, provider, null); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Set the component for a given appWidgetId. + * + * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding + * widgets always for your component. This method is used by the AppWidget picker and + * should not be used by other apps. + * + * @param appWidgetId The AppWidget instance for which to set the RemoteViews. + * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget + * provider for this AppWidget. + * @param options Bundle containing options for the AppWidget. See also + * {@link #updateAppWidgetOptions(int, Bundle)} + * + * @hide + */ + public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) { + try { + sService.bindAppWidgetId(appWidgetId, provider, options); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -594,7 +647,37 @@ public class AppWidgetManager { } try { return sService.bindAppWidgetIdIfAllowed( - mContext.getPackageName(), appWidgetId, provider); + mContext.getPackageName(), appWidgetId, provider, null); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Set the component for a given appWidgetId. + * + * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding + * widgets always for your component. Should be used by apps that host widgets; if this + * method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to + * bind + * + * @param appWidgetId The AppWidget instance for which to set the RemoteViews. + * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget + * provider for this AppWidget. + * @param options Bundle containing options for the AppWidget. See also + * {@link #updateAppWidgetOptions(int, Bundle)} + * + * @return true if this component has permission to bind the AppWidget + */ + public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider, + Bundle options) { + if (mContext == null) { + return false; + } + try { + return sService.bindAppWidgetIdIfAllowed( + mContext.getPackageName(), appWidgetId, provider, options); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index c33681d..8b62931 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -44,6 +44,28 @@ public class AppWidgetProviderInfo implements Parcelable { public static final int RESIZE_BOTH = RESIZE_HORIZONTAL | RESIZE_VERTICAL; /** + * Indicates that the widget can be displayed on the home screen. This is the default value. + */ + public static final int WIDGET_CATEGORY_HOME_SCREEN = 1; + + /** + * Indicates that the widget can be displayed on the keyguard. + */ + public static final int WIDGET_CATEGORY_KEYGUARD = 2; + + /** + * Indicates that the widget supports no special features. + */ + public static final int WIDGET_FEATURES_NONE = 0; + + /** + * Indicates that the widget is output only, ie. has nothing clickable. This may be enforced by + * the host. Presently, this flag is used by the keyguard to indicate that it can be placed + * in the first position. + */ + public static final int WIDGET_FEATURES_STATUS = 1; + + /** * Identity of this AppWidget component. This component should be a {@link * android.content.BroadcastReceiver}, and it will be sent the AppWidget intents * {@link android.appwidget as described in the AppWidget package documentation}. @@ -111,6 +133,16 @@ public class AppWidgetProviderInfo implements Parcelable { public int initialLayout; /** + * The resource id of the initial layout for this AppWidget when it is displayed on keyguard. + * This parameter only needs to be provided if the widget can be displayed on the keyguard, + * see {@link #widgetCategory}. + * + * <p>This field corresponds to the <code>android:initialKeyguardLayout</code> attribute in + * the AppWidget meta-data file. + */ + public int initialKeyguardLayout; + + /** * The activity to launch that will configure the AppWidget. * * <p>This class name of field corresponds to the <code>android:configure</code> attribute in @@ -164,6 +196,26 @@ public class AppWidgetProviderInfo implements Parcelable { */ public int resizeMode; + /** + * Determines whether this widget can be displayed on the home screen, the keyguard, or both. + * A widget which is displayed on both needs to ensure that it follows the design guidelines + * for both widget classes. This can be achieved by querying the AppWidget options in its + * widget provider's update method. + * + * <p>This field corresponds to the <code>widgetCategory</code> attribute in + * the AppWidget meta-data file. + */ + public int widgetCategory; + + /** + * A field which specifies any special features that this widget supports. See + * {@link #WIDGET_FEATURES_NONE}, {@link #WIDGET_FEATURES_STATUS}. + * + * <p>This field corresponds to the <code>widgetFeatures</code> attribute in + * the AppWidget meta-data file. + */ + public int widgetFeatures; + public AppWidgetProviderInfo() { } @@ -180,6 +232,7 @@ public class AppWidgetProviderInfo implements Parcelable { this.minResizeHeight = in.readInt(); this.updatePeriodMillis = in.readInt(); this.initialLayout = in.readInt(); + this.initialKeyguardLayout = in.readInt(); if (0 != in.readInt()) { this.configure = new ComponentName(in); } @@ -188,6 +241,8 @@ public class AppWidgetProviderInfo implements Parcelable { this.previewImage = in.readInt(); this.autoAdvanceViewId = in.readInt(); this.resizeMode = in.readInt(); + this.widgetCategory = in.readInt(); + this.widgetFeatures = in.readInt(); } public void writeToParcel(android.os.Parcel out, int flags) { @@ -203,6 +258,7 @@ public class AppWidgetProviderInfo implements Parcelable { out.writeInt(this.minResizeHeight); out.writeInt(this.updatePeriodMillis); out.writeInt(this.initialLayout); + out.writeInt(this.initialKeyguardLayout); if (this.configure != null) { out.writeInt(1); this.configure.writeToParcel(out, flags); @@ -214,6 +270,30 @@ public class AppWidgetProviderInfo implements Parcelable { out.writeInt(this.previewImage); out.writeInt(this.autoAdvanceViewId); out.writeInt(this.resizeMode); + out.writeInt(this.widgetCategory); + out.writeInt(this.widgetFeatures); + } + + @Override + public AppWidgetProviderInfo clone() { + AppWidgetProviderInfo that = new AppWidgetProviderInfo(); + that.provider = this.provider == null ? null : this.provider.clone(); + that.minWidth = this.minWidth; + that.minHeight = this.minHeight; + that.minResizeWidth = this.minResizeHeight; + that.minResizeHeight = this.minResizeHeight; + that.updatePeriodMillis = this.updatePeriodMillis; + that.initialLayout = that.initialLayout; + that.initialKeyguardLayout = this.initialKeyguardLayout; + that.configure = this.configure == null ? null : this.configure.clone(); + that.label = this.label == null ? null : this.label.substring(0); + that.icon = this.icon; + that.previewImage = this.previewImage; + that.autoAdvanceViewId = this.autoAdvanceViewId; + that.resizeMode = this.resizeMode; + that.widgetCategory = this.widgetCategory; + that.widgetFeatures = this.widgetFeatures; + return that; } public int describeContents() { diff --git a/core/java/android/bluetooth/AtCommandHandler.java b/core/java/android/bluetooth/AtCommandHandler.java deleted file mode 100644 index 6deab34..0000000 --- a/core/java/android/bluetooth/AtCommandHandler.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.bluetooth.AtCommandResult; - -/** - * Handler Interface for {@link AtParser}.<p> - * @hide - */ -public abstract class AtCommandHandler { - - /** - * Handle Basic commands "ATA".<p> - * These are single letter commands such as ATA and ATD. Anything following - * the single letter command ('A' and 'D' respectively) will be passed as - * 'arg'.<p> - * For example, "ATDT1234" would result in the call - * handleBasicCommand("T1234").<p> - * @param arg Everything following the basic command character. - * @return The result of this command. - */ - public AtCommandResult handleBasicCommand(String arg) { - return new AtCommandResult(AtCommandResult.ERROR); - } - - /** - * Handle Actions command "AT+FOO".<p> - * Action commands are part of the Extended command syntax, and are - * typically used to signal an action on "FOO".<p> - * @return The result of this command. - */ - public AtCommandResult handleActionCommand() { - return new AtCommandResult(AtCommandResult.ERROR); - } - - /** - * Handle Read command "AT+FOO?".<p> - * Read commands are part of the Extended command syntax, and are - * typically used to read the value of "FOO".<p> - * @return The result of this command. - */ - public AtCommandResult handleReadCommand() { - return new AtCommandResult(AtCommandResult.ERROR); - } - - /** - * Handle Set command "AT+FOO=...".<p> - * Set commands are part of the Extended command syntax, and are - * typically used to set the value of "FOO". Multiple arguments can be - * sent.<p> - * AT+FOO=[<arg1>[,<arg2>[,...]]]<p> - * Each argument will be either numeric (Integer) or String. - * handleSetCommand is passed a generic Object[] array in which each - * element will be an Integer (if it can be parsed with parseInt()) or - * String.<p> - * Missing arguments ",," are set to empty Strings.<p> - * @param args Array of String and/or Integer's. There will always be at - * least one element in this array. - * @return The result of this command. - */ - // Typically used to set this parameter - public AtCommandResult handleSetCommand(Object[] args) { - return new AtCommandResult(AtCommandResult.ERROR); - } - - /** - * Handle Test command "AT+FOO=?".<p> - * Test commands are part of the Extended command syntax, and are typically - * used to request an indication of the range of legal values that "FOO" - * can take.<p> - * By default we return an OK result, to indicate that this command is at - * least recognized.<p> - * @return The result of this command. - */ - public AtCommandResult handleTestCommand() { - return new AtCommandResult(AtCommandResult.OK); - } - -} diff --git a/core/java/android/bluetooth/AtCommandResult.java b/core/java/android/bluetooth/AtCommandResult.java deleted file mode 100644 index 9675234..0000000 --- a/core/java/android/bluetooth/AtCommandResult.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -/** - * The result of execution of a single AT command.<p> - * - * - * This class can represent the final response to an AT command line, and also - * intermediate responses to a single command within a chained AT command - * line.<p> - * - * The actual responses that are intended to be send in reply to the AT command - * line are stored in a string array. The final response is stored as an - * int enum, converted to a string when toString() is called. Only a single - * final response is sent from multiple commands chained into a single command - * line.<p> - * @hide - */ -public class AtCommandResult { - // Result code enumerations - public static final int OK = 0; - public static final int ERROR = 1; - public static final int UNSOLICITED = 2; - - private static final String OK_STRING = "OK"; - private static final String ERROR_STRING = "ERROR"; - - private int mResultCode; // Result code - private StringBuilder mResponse; // Response with CRLF line breaks - - /** - * Construct a new AtCommandResult with given result code, and an empty - * response array. - * @param resultCode One of OK, ERROR or UNSOLICITED. - */ - public AtCommandResult(int resultCode) { - mResultCode = resultCode; - mResponse = new StringBuilder(); - } - - /** - * Construct a new AtCommandResult with result code OK, and the specified - * single line response. - * @param response The single line response. - */ - public AtCommandResult(String response) { - this(OK); - addResponse(response); - } - - public int getResultCode() { - return mResultCode; - } - - /** - * Add another line to the response. - */ - public void addResponse(String response) { - appendWithCrlf(mResponse, response); - } - - /** - * Add the given result into this AtCommandResult object.<p> - * Used to combine results from multiple commands in a single command line - * (command chaining). - * @param result The AtCommandResult to add to this result. - */ - public void addResult(AtCommandResult result) { - if (result != null) { - appendWithCrlf(mResponse, result.mResponse.toString()); - mResultCode = result.mResultCode; - } - } - - /** - * Generate the string response ready to send - */ - public String toString() { - StringBuilder result = new StringBuilder(mResponse.toString()); - switch (mResultCode) { - case OK: - appendWithCrlf(result, OK_STRING); - break; - case ERROR: - appendWithCrlf(result, ERROR_STRING); - break; - } - return result.toString(); - } - - /** Append a string to a string builder, joining with a double - * CRLF. Used to create multi-line AT command replies - */ - public static void appendWithCrlf(StringBuilder str1, String str2) { - if (str1.length() > 0 && str2.length() > 0) { - str1.append("\r\n\r\n"); - } - str1.append(str2); - } -}; diff --git a/core/java/android/bluetooth/AtParser.java b/core/java/android/bluetooth/AtParser.java deleted file mode 100644 index 328fb2b..0000000 --- a/core/java/android/bluetooth/AtParser.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import java.util.*; - -/** - * An AT (Hayes command) Parser based on (a subset of) the ITU-T V.250 standard. - * <p> - * - * Conformant with the subset of V.250 required for implementation of the - * Bluetooth Headset and Handsfree Profiles, as per Bluetooth SIP - * specifications. Also implements some V.250 features not required by - * Bluetooth - such as chained commands.<p> - * - * Command handlers are registered with an AtParser object. These handlers are - * invoked when command lines are processed by AtParser's process() method.<p> - * - * The AtParser object accepts a new command line to parse via its process() - * method. It breaks each command line into one or more commands. Each command - * is parsed for name, type, and (optional) arguments, and an appropriate - * external handler method is called through the AtCommandHandler interface. - * - * The command types are<ul> - * <li>Basic Command. For example "ATDT1234567890". Basic command names are a - * single character (e.g. "D"), and everything following this character is - * passed to the handler as a string argument (e.g. "T1234567890"). - * <li>Action Command. For example "AT+CIMI". The command name is "CIMI", and - * there are no arguments for action commands. - * <li>Read Command. For example "AT+VGM?". The command name is "VGM", and there - * are no arguments for get commands. - * <li>Set Command. For example "AT+VGM=14". The command name is "VGM", and - * there is a single integer argument in this case. In the general case then - * can be zero or more arguments (comma delimited) each of integer or string - * form. - * <li>Test Command. For example "AT+VGM=?. No arguments. - * </ul> - * - * In V.250 the last four command types are known as Extended Commands, and - * they are used heavily in Bluetooth.<p> - * - * Basic commands cannot be chained in this implementation. For Bluetooth - * headset/handsfree use this is acceptable, because they only use the basic - * commands ATA and ATD, which are not allowed to be chained. For general V.250 - * use we would need to improve this class to allow Basic command chaining - - * however it's tricky to get right because there is no delimiter for Basic - * command chaining.<p> - * - * Extended commands can be chained. For example:<p> - * AT+VGM?;+VGM=14;+CIMI<p> - * This is equivalent to:<p> - * AT+VGM? - * AT+VGM=14 - * AT+CIMI - * Except that only one final result code is return (although several - * intermediate responses may be returned), and as soon as one command in the - * chain fails the rest are abandoned.<p> - * - * Handlers are registered by there command name via register(Char c, ...) or - * register(String s, ...). Handlers for Basic command should be registered by - * the basic command character, and handlers for Extended commands should be - * registered by String.<p> - * - * Refer to:<ul> - * <li>ITU-T Recommendation V.250 - * <li>ETSI TS 127.007 (AT Command set for User Equipment, 3GPP TS 27.007) - * <li>Bluetooth Headset Profile Spec (K6) - * <li>Bluetooth Handsfree Profile Spec (HFP 1.5) - * </ul> - * @hide - */ -public class AtParser { - - // Extended command type enumeration, only used internally - private static final int TYPE_ACTION = 0; // AT+FOO - private static final int TYPE_READ = 1; // AT+FOO? - private static final int TYPE_SET = 2; // AT+FOO= - private static final int TYPE_TEST = 3; // AT+FOO=? - - private HashMap<String, AtCommandHandler> mExtHandlers; - private HashMap<Character, AtCommandHandler> mBasicHandlers; - - private String mLastInput; // for "A/" (repeat last command) support - - /** - * Create a new AtParser.<p> - * No handlers are registered. - */ - public AtParser() { - mBasicHandlers = new HashMap<Character, AtCommandHandler>(); - mExtHandlers = new HashMap<String, AtCommandHandler>(); - mLastInput = ""; - } - - /** - * Register a Basic command handler.<p> - * Basic command handlers are later called via their - * <code>handleBasicCommand(String args)</code> method. - * @param command Command name - a single character - * @param handler Handler to register - */ - public void register(Character command, AtCommandHandler handler) { - mBasicHandlers.put(command, handler); - } - - /** - * Register an Extended command handler.<p> - * Extended command handlers are later called via:<ul> - * <li><code>handleActionCommand()</code> - * <li><code>handleGetCommand()</code> - * <li><code>handleSetCommand()</code> - * <li><code>handleTestCommand()</code> - * </ul> - * Only one method will be called for each command processed. - * @param command Command name - can be multiple characters - * @param handler Handler to register - */ - public void register(String command, AtCommandHandler handler) { - mExtHandlers.put(command, handler); - } - - - /** - * Strip input of whitespace and force Uppercase - except sections inside - * quotes. Also fixes unmatched quotes (by appending a quote). Double - * quotes " are the only quotes allowed by V.250 - */ - static private String clean(String input) { - StringBuilder out = new StringBuilder(input.length()); - - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - if (c == '"') { - int j = input.indexOf('"', i + 1 ); // search for closing " - if (j == -1) { // unmatched ", insert one. - out.append(input.substring(i, input.length())); - out.append('"'); - break; - } - out.append(input.substring(i, j + 1)); - i = j; - } else if (c != ' ') { - out.append(Character.toUpperCase(c)); - } - } - - return out.toString(); - } - - static private boolean isAtoZ(char c) { - return (c >= 'A' && c <= 'Z'); - } - - /** - * Find a character ch, ignoring quoted sections. - * Return input.length() if not found. - */ - static private int findChar(char ch, String input, int fromIndex) { - for (int i = fromIndex; i < input.length(); i++) { - char c = input.charAt(i); - if (c == '"') { - i = input.indexOf('"', i + 1); - if (i == -1) { - return input.length(); - } - } else if (c == ch) { - return i; - } - } - return input.length(); - } - - /** - * Break an argument string into individual arguments (comma delimited). - * Integer arguments are turned into Integer objects. Otherwise a String - * object is used. - */ - static private Object[] generateArgs(String input) { - int i = 0; - int j; - ArrayList<Object> out = new ArrayList<Object>(); - while (i <= input.length()) { - j = findChar(',', input, i); - - String arg = input.substring(i, j); - try { - out.add(new Integer(arg)); - } catch (NumberFormatException e) { - out.add(arg); - } - - i = j + 1; // move past comma - } - return out.toArray(); - } - - /** - * Return the index of the end of character after the last character in - * the extended command name. Uses the V.250 spec for allowed command - * names. - */ - static private int findEndExtendedName(String input, int index) { - for (int i = index; i < input.length(); i++) { - char c = input.charAt(i); - - // V.250 defines the following chars as legal extended command - // names - if (isAtoZ(c)) continue; - if (c >= '0' && c <= '9') continue; - switch (c) { - case '!': - case '%': - case '-': - case '.': - case '/': - case ':': - case '_': - continue; - default: - return i; - } - } - return input.length(); - } - - /** - * Processes an incoming AT command line.<p> - * This method will invoke zero or one command handler methods for each - * command in the command line.<p> - * @param raw_input The AT input, without EOL delimiter (e.g. <CR>). - * @return Result object for this command line. This can be - * converted to a String[] response with toStrings(). - */ - public AtCommandResult process(String raw_input) { - String input = clean(raw_input); - - // Handle "A/" (repeat previous line) - if (input.regionMatches(0, "A/", 0, 2)) { - input = new String(mLastInput); - } else { - mLastInput = new String(input); - } - - // Handle empty line - no response necessary - if (input.equals("")) { - // Return [] - return new AtCommandResult(AtCommandResult.UNSOLICITED); - } - - // Anything else deserves an error - if (!input.regionMatches(0, "AT", 0, 2)) { - // Return ["ERROR"] - return new AtCommandResult(AtCommandResult.ERROR); - } - - // Ok we have a command that starts with AT. Process it - int index = 2; - AtCommandResult result = - new AtCommandResult(AtCommandResult.UNSOLICITED); - while (index < input.length()) { - char c = input.charAt(index); - - if (isAtoZ(c)) { - // Option 1: Basic Command - // Pass the rest of the line as is to the handler. Do not - // look for any more commands on this line. - String args = input.substring(index + 1); - if (mBasicHandlers.containsKey((Character)c)) { - result.addResult(mBasicHandlers.get( - (Character)c).handleBasicCommand(args)); - return result; - } else { - // no handler - result.addResult( - new AtCommandResult(AtCommandResult.ERROR)); - return result; - } - // control never reaches here - } - - if (c == '+') { - // Option 2: Extended Command - // Search for first non-name character. Short-circuit if - // we don't handle this command name. - int i = findEndExtendedName(input, index + 1); - String commandName = input.substring(index, i); - if (!mExtHandlers.containsKey(commandName)) { - // no handler - result.addResult( - new AtCommandResult(AtCommandResult.ERROR)); - return result; - } - AtCommandHandler handler = mExtHandlers.get(commandName); - - // Search for end of this command - this is usually the end of - // line - int endIndex = findChar(';', input, index); - - // Determine what type of command this is. - // Default to TYPE_ACTION if we can't find anything else - // obvious. - int type; - - if (i >= endIndex) { - type = TYPE_ACTION; - } else if (input.charAt(i) == '?') { - type = TYPE_READ; - } else if (input.charAt(i) == '=') { - if (i + 1 < endIndex) { - if (input.charAt(i + 1) == '?') { - type = TYPE_TEST; - } else { - type = TYPE_SET; - } - } else { - type = TYPE_SET; - } - } else { - type = TYPE_ACTION; - } - - // Call this command. Short-circuit as soon as a command fails - switch (type) { - case TYPE_ACTION: - result.addResult(handler.handleActionCommand()); - break; - case TYPE_READ: - result.addResult(handler.handleReadCommand()); - break; - case TYPE_TEST: - result.addResult(handler.handleTestCommand()); - break; - case TYPE_SET: - Object[] args = - generateArgs(input.substring(i + 1, endIndex)); - result.addResult(handler.handleSetCommand(args)); - break; - } - if (result.getResultCode() != AtCommandResult.OK) { - return result; // short-circuit - } - - index = endIndex; - } else { - // Can't tell if this is a basic or extended command. - // Push forwards and hope we hit something. - index++; - } - } - // Finished processing (and all results were ok) - return result; - } -} diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 7300107..6e2278d 100644..100755 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -18,12 +18,13 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; -import android.os.ServiceManager; -import android.server.BluetoothA2dpService; import android.util.Log; import java.util.ArrayList; @@ -43,7 +44,7 @@ import java.util.List; */ public final class BluetoothA2dp implements BluetoothProfile { private static final String TAG = "BluetoothA2dp"; - private static final boolean DBG = false; + private static final boolean DBG = true; /** * Intent used to broadcast the change in connection state of the A2DP @@ -102,37 +103,90 @@ public final class BluetoothA2dp implements BluetoothProfile { */ public static final int STATE_NOT_PLAYING = 11; + private Context mContext; private ServiceListener mServiceListener; private IBluetoothA2dp mService; private BluetoothAdapter mAdapter; + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (DBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (DBG) Log.d(TAG,"Binding service..."); + if (!mContext.bindService(new Intent(IBluetoothA2dp.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth A2DP Service"); + } + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; /** * Create a BluetoothA2dp proxy object for interacting with the local * Bluetooth A2DP service. * */ - /*package*/ BluetoothA2dp(Context mContext, ServiceListener l) { - IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE); + /*package*/ BluetoothA2dp(Context context, ServiceListener l) { + mContext = context; mServiceListener = l; mAdapter = BluetoothAdapter.getDefaultAdapter(); - if (b != null) { - mService = IBluetoothA2dp.Stub.asInterface(b); - if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.A2DP, this); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); } - } else { - Log.w(TAG, "Bluetooth A2DP service not available!"); + } - // Instead of throwing an exception which prevents people from going - // into Wireless settings in the emulator. Let it crash later when it is actually used. - mService = null; + if (!context.bindService(new Intent(IBluetoothA2dp.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth A2DP Service"); } } /*package*/ void close() { mServiceListener = null; + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } } + public void finalize() { + close(); + } /** * Initiate connection to a profile of the remote bluetooth device. * @@ -267,7 +321,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * Set priority of the profile * * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or + * Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager * {@link #PRIORITY_OFF}, * * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} @@ -283,7 +337,7 @@ public final class BluetoothA2dp implements BluetoothProfile { if (mService != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && - priority != BluetoothProfile.PRIORITY_ON) { + priority != BluetoothProfile.PRIORITY_ON){ return false; } try { @@ -347,67 +401,6 @@ public final class BluetoothA2dp implements BluetoothProfile { } /** - * Initiate suspend from an A2DP sink. - * - * <p> This API will return false in scenarios like the A2DP - * device is not in connected state etc. When this API returns, - * true, it is guaranteed that {@link #ACTION_CONNECTION_STATE_CHANGED} - * intent will be broadcasted with the state. Users can get the - * state of the A2DP device from this intent. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * - * @param device Remote A2DP sink - * @return false on immediate error, - * true otherwise - * @hide - */ - public boolean suspendSink(BluetoothDevice device) { - if (mService != null && isEnabled() - && isValidDevice(device)) { - try { - return mService.suspendSink(device); - } catch (RemoteException e) { - Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); - return false; - } - } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); - return false; - } - - /** - * Initiate resume from a suspended A2DP sink. - * - * <p> This API will return false in scenarios like the A2DP - * device is not in suspended state etc. When this API returns, - * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED} - * intent will be broadcasted with the state. Users can get the - * state of the A2DP device from this intent. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * - * @param device Remote A2DP sink - * @return false on immediate error, - * true otherwise - * @hide - */ - public boolean resumeSink(BluetoothDevice device) { - if (mService != null && isEnabled() - && isValidDevice(device)) { - try { - return mService.resumeSink(device); - } catch (RemoteException e) { - Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); - return false; - } - } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); - return false; - } - - /** * This function checks if the remote device is an AVCRP * target and thus whether we should send volume keys * changes or not. @@ -428,23 +421,6 @@ public final class BluetoothA2dp implements BluetoothProfile { } /** - * Allow or disallow incoming connection - * @param device Sink - * @param value True / False - * @return Success or Failure of the binder call. - * @hide - */ - public boolean allowIncomingConnect(BluetoothDevice device, boolean value) { - if (DBG) log("allowIncomingConnect(" + device + ":" + value + ")"); - try { - return mService.allowIncomingConnect(device, value); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - } - - /** * Helper for converting a state to a string. * * For debug use only - strings are not internationalized. @@ -469,6 +445,24 @@ public final class BluetoothA2dp implements BluetoothProfile { } } + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "Proxy object connected"); + mService = IBluetoothA2dp.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.A2DP, BluetoothA2dp.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP); + } + } + }; + private boolean isEnabled() { if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; return false; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 8e3df47..f817fb4 100644..100755 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -31,6 +31,7 @@ import android.util.Log; import android.util.Pair; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -73,7 +74,8 @@ import java.util.UUID; */ public final class BluetoothAdapter { private static final String TAG = "BluetoothAdapter"; - private static final boolean DBG = false; + private static final boolean DBG = true; + private static final boolean VDBG = false; /** * Sentinel error value for this class. Guaranteed to not equal any other @@ -343,7 +345,7 @@ public final class BluetoothAdapter { public static final int STATE_DISCONNECTING = 3; /** @hide */ - public static final String BLUETOOTH_SERVICE = "bluetooth"; + public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; private static final int ADDRESS_LENGTH = 17; @@ -353,7 +355,8 @@ public final class BluetoothAdapter { */ private static BluetoothAdapter sAdapter; - private final IBluetooth mService; + private final IBluetoothManager mManagerService; + private IBluetooth mService; private Handler mServiceRecordHandler; @@ -367,10 +370,12 @@ public final class BluetoothAdapter { */ public static synchronized BluetoothAdapter getDefaultAdapter() { if (sAdapter == null) { - IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); + IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE); if (b != null) { - IBluetooth service = IBluetooth.Stub.asInterface(b); - sAdapter = new BluetoothAdapter(service); + IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b); + sAdapter = new BluetoothAdapter(managerService); + } else { + Log.e(TAG, "Bluetooth binder is null"); } } return sAdapter; @@ -378,13 +383,16 @@ public final class BluetoothAdapter { /** * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance. - * @hide */ - public BluetoothAdapter(IBluetooth service) { - if (service == null) { - throw new IllegalArgumentException("service is null"); + BluetoothAdapter(IBluetoothManager managerService) { + + if (managerService == null) { + throw new IllegalArgumentException("bluetooth manager service is null"); } - mService = service; + try { + mService = managerService.registerAdapter(mManagerCallback); + } catch (RemoteException e) {Log.e(TAG, "", e);} + mManagerService = managerService; mServiceRecordHandler = null; } @@ -432,8 +440,11 @@ public final class BluetoothAdapter { * @return true if the local adapter is turned on */ public boolean isEnabled() { + try { - return mService.isEnabled(); + synchronized(mManagerCallback) { + if (mService != null) return mService.isEnabled(); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -451,8 +462,18 @@ public final class BluetoothAdapter { */ public int getState() { try { - return mService.getBluetoothState(); + synchronized(mManagerCallback) { + if (mService != null) + { + int state= mService.getState(); + if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state); + return state; + } + // TODO(BT) there might be a small gap during STATE_TURNING_ON that + // mService is null, handle that case + } } catch (RemoteException e) {Log.e(TAG, "", e);} + if (DBG) Log.d(TAG, "" + hashCode() + ": getState() : mService = null. Returning STATE_OFF"); return STATE_OFF; } @@ -484,8 +505,12 @@ public final class BluetoothAdapter { * immediate error */ public boolean enable() { + if (isEnabled() == true){ + if (DBG) Log.d(TAG, "enable(): BT is already enabled..!"); + return true; + } try { - return mService.enable(); + return mManagerService.enable(); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -516,7 +541,25 @@ public final class BluetoothAdapter { */ public boolean disable() { try { - return mService.disable(true); + return mManagerService.disable(true); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Turn off the local Bluetooth adapter and don't persist the setting. + * + * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission + * + * @return true to indicate adapter shutdown has begun, or false on + * immediate error + * @hide + */ + public boolean disable(boolean persist) { + + try { + return mManagerService.disable(persist); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -530,7 +573,7 @@ public final class BluetoothAdapter { */ public String getAddress() { try { - return mService.getAddress(); + return mManagerService.getAddress(); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } @@ -544,7 +587,7 @@ public final class BluetoothAdapter { */ public String getName() { try { - return mService.getName(); + return mManagerService.getName(); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } @@ -560,7 +603,9 @@ public final class BluetoothAdapter { public ParcelUuid[] getUuids() { if (getState() != STATE_ON) return null; try { - return mService.getUuids(); + synchronized(mManagerCallback) { + if (mService != null) return mService.getUuids(); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } @@ -583,7 +628,9 @@ public final class BluetoothAdapter { public boolean setName(String name) { if (getState() != STATE_ON) return false; try { - return mService.setName(name); + synchronized(mManagerCallback) { + if (mService != null) return mService.setName(name); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -607,7 +654,9 @@ public final class BluetoothAdapter { public int getScanMode() { if (getState() != STATE_ON) return SCAN_MODE_NONE; try { - return mService.getScanMode(); + synchronized(mManagerCallback) { + if (mService != null) return mService.getScanMode(); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return SCAN_MODE_NONE; } @@ -643,7 +692,9 @@ public final class BluetoothAdapter { public boolean setScanMode(int mode, int duration) { if (getState() != STATE_ON) return false; try { - return mService.setScanMode(mode, duration); + synchronized(mManagerCallback) { + if (mService != null) return mService.setScanMode(mode, duration); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -651,14 +702,17 @@ public final class BluetoothAdapter { /** @hide */ public boolean setScanMode(int mode) { if (getState() != STATE_ON) return false; - return setScanMode(mode, 120); + /* getDiscoverableTimeout() to use the latest from NV than use 0 */ + return setScanMode(mode, getDiscoverableTimeout()); } /** @hide */ public int getDiscoverableTimeout() { if (getState() != STATE_ON) return -1; try { - return mService.getDiscoverableTimeout(); + synchronized(mManagerCallback) { + if (mService != null) return mService.getDiscoverableTimeout(); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return -1; } @@ -667,7 +721,9 @@ public final class BluetoothAdapter { public void setDiscoverableTimeout(int timeout) { if (getState() != STATE_ON) return; try { - mService.setDiscoverableTimeout(timeout); + synchronized(mManagerCallback) { + if (mService != null) mService.setDiscoverableTimeout(timeout); + } } catch (RemoteException e) {Log.e(TAG, "", e);} } @@ -704,7 +760,9 @@ public final class BluetoothAdapter { public boolean startDiscovery() { if (getState() != STATE_ON) return false; try { - return mService.startDiscovery(); + synchronized(mManagerCallback) { + if (mService != null) return mService.startDiscovery(); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -729,7 +787,9 @@ public final class BluetoothAdapter { public boolean cancelDiscovery() { if (getState() != STATE_ON) return false; try { - return mService.cancelDiscovery(); + synchronized(mManagerCallback) { + if (mService != null) return mService.cancelDiscovery(); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -756,7 +816,9 @@ public final class BluetoothAdapter { public boolean isDiscovering() { if (getState() != STATE_ON) return false; try { - return mService.isDiscovering(); + synchronized(mManagerCallback) { + if (mService != null ) return mService.isDiscovering(); + } } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -774,10 +836,13 @@ public final class BluetoothAdapter { */ public Set<BluetoothDevice> getBondedDevices() { if (getState() != STATE_ON) { - return toDeviceSet(new String[0]); + return toDeviceSet(new BluetoothDevice[0]); } try { - return toDeviceSet(mService.listBonds()); + synchronized(mManagerCallback) { + if (mService != null) return toDeviceSet(mService.getBondedDevices()); + } + return toDeviceSet(new BluetoothDevice[0]); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } @@ -798,7 +863,9 @@ public final class BluetoothAdapter { public int getConnectionState() { if (getState() != STATE_ON) return BluetoothAdapter.STATE_DISCONNECTED; try { - return mService.getAdapterConnectionState(); + synchronized(mManagerCallback) { + if (mService != null) return mService.getAdapterConnectionState(); + } } catch (RemoteException e) {Log.e(TAG, "getConnectionState:", e);} return BluetoothAdapter.STATE_DISCONNECTED; } @@ -821,7 +888,9 @@ public final class BluetoothAdapter { public int getProfileConnectionState(int profile) { if (getState() != STATE_ON) return BluetoothProfile.STATE_DISCONNECTED; try { - return mService.getProfileConnectionState(profile); + synchronized(mManagerCallback) { + if (mService != null) return mService.getProfileConnectionState(profile); + } } catch (RemoteException e) { Log.e(TAG, "getProfileConnectionState:", e); } @@ -829,51 +898,6 @@ public final class BluetoothAdapter { } /** - /** - * Picks RFCOMM channels until none are left. - * Avoids reserved channels. - */ - private static class RfcommChannelPicker { - private static final int[] RESERVED_RFCOMM_CHANNELS = new int[] { - 10, // HFAG - 11, // HSAG - 12, // OPUSH - 19, // PBAP - }; - private static LinkedList<Integer> sChannels; // master list of non-reserved channels - private static Random sRandom; - - private final LinkedList<Integer> mChannels; // local list of channels left to try - - private final UUID mUuid; - - public RfcommChannelPicker(UUID uuid) { - synchronized (RfcommChannelPicker.class) { - if (sChannels == null) { - // lazy initialization of non-reserved rfcomm channels - sChannels = new LinkedList<Integer>(); - for (int i = 1; i <= BluetoothSocket.MAX_RFCOMM_CHANNEL; i++) { - sChannels.addLast(new Integer(i)); - } - for (int reserved : RESERVED_RFCOMM_CHANNELS) { - sChannels.remove(new Integer(reserved)); - } - sRandom = new Random(); - } - mChannels = (LinkedList<Integer>)sChannels.clone(); - } - mUuid = uuid; - } - /* Returns next random channel, or -1 if we're out */ - public int nextChannel() { - if (mChannels.size() == 0) { - return -1; - } - return mChannels.remove(sRandom.nextInt(mChannels.size())); - } - } - - /** * Create a listening, secure RFCOMM Bluetooth socket. * <p>A remote device connecting to this socket will be authenticated and * communication on this socket will be encrypted. @@ -892,10 +916,10 @@ public final class BluetoothAdapter { BluetoothSocket.TYPE_RFCOMM, true, true, channel); int errno = socket.mSocket.bindListen(); if (errno != 0) { - try { - socket.close(); - } catch (IOException e) {} - socket.mSocket.throwErrnoNative(errno); + //TODO(BT): Throw the same exception error code + // that the previous code was using. + //socket.mSocket.throwErrnoNative(errno); + throw new IOException("Error: " + errno); } return socket; } @@ -996,70 +1020,23 @@ public final class BluetoothAdapter { return createNewRfcommSocketAndRecord(name, uuid, false, true); } + private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid, boolean auth, boolean encrypt) throws IOException { - RfcommChannelPicker picker = new RfcommChannelPicker(uuid); - BluetoothServerSocket socket; - int channel; - int errno; - while (true) { - channel = picker.nextChannel(); - - if (channel == -1) { - throw new IOException("No available channels"); - } - - socket = new BluetoothServerSocket( - BluetoothSocket.TYPE_RFCOMM, auth, encrypt, channel); - errno = socket.mSocket.bindListen(); - if (errno == 0) { - if (DBG) Log.d(TAG, "listening on RFCOMM channel " + channel); - break; // success - } else if (errno == BluetoothSocket.EADDRINUSE) { - if (DBG) Log.d(TAG, "RFCOMM channel " + channel + " in use"); - try { - socket.close(); - } catch (IOException e) {} - continue; // try another channel - } else { - try { - socket.close(); - } catch (IOException e) {} - socket.mSocket.throwErrnoNative(errno); // Exception as a result of bindListen() - } - } - - int handle = -1; - try { - handle = mService.addRfcommServiceRecord(name, new ParcelUuid(uuid), channel, - new Binder()); - } catch (RemoteException e) {Log.e(TAG, "", e);} - if (handle == -1) { - try { - socket.close(); - } catch (IOException e) {} - throw new IOException("Not able to register SDP record for " + name); - } - - if (mServiceRecordHandler == null) { - mServiceRecordHandler = new Handler(Looper.getMainLooper()) { - public void handleMessage(Message msg) { - /* handle socket closing */ - int handle = msg.what; - try { - if (DBG) Log.d(TAG, "Removing service record " + - Integer.toHexString(handle)); - mService.removeServiceRecord(handle); - } catch (RemoteException e) {Log.e(TAG, "", e);} - } - }; + socket = new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, auth, + encrypt, new ParcelUuid(uuid)); + socket.setServiceName(name); + int errno = socket.mSocket.bindListen(); + if (errno != 0) { + //TODO(BT): Throw the same exception error code + // that the previous code was using. + //socket.mSocket.throwErrnoNative(errno); + throw new IOException("Error: " + errno); } - socket.setCloseHandler(mServiceRecordHandler, handle); return socket; } - /** * Construct an unencrypted, unauthenticated, RFCOMM server socket. * Call #accept to retrieve connections to this socket. @@ -1073,10 +1050,10 @@ public final class BluetoothAdapter { BluetoothSocket.TYPE_RFCOMM, false, false, port); int errno = socket.mSocket.bindListen(); if (errno != 0) { - try { - socket.close(); - } catch (IOException e) {} - socket.mSocket.throwErrnoNative(errno); + //TODO(BT): Throw the same exception error code + // that the previous code was using. + //socket.mSocket.throwErrnoNative(errno); + throw new IOException("Error: " + errno); } return socket; } @@ -1094,11 +1071,11 @@ public final class BluetoothAdapter { BluetoothServerSocket socket = new BluetoothServerSocket( BluetoothSocket.TYPE_RFCOMM, false, true, port); int errno = socket.mSocket.bindListen(); - if (errno != 0) { - try { - socket.close(); - } catch (IOException e) {} - socket.mSocket.throwErrnoNative(errno); + if (errno < 0) { + //TODO(BT): Throw the same exception error code + // that the previous code was using. + //socket.mSocket.throwErrnoNative(errno); + throw new IOException("Error: " + errno); } return socket; } @@ -1115,11 +1092,10 @@ public final class BluetoothAdapter { BluetoothServerSocket socket = new BluetoothServerSocket( BluetoothSocket.TYPE_SCO, false, false, -1); int errno = socket.mSocket.bindListen(); - if (errno != 0) { - try { - socket.close(); - } catch (IOException e) {} - socket.mSocket.throwErrnoNative(errno); + if (errno < 0) { + //TODO(BT): Throw the same exception error code + // that the previous code was using. + //socket.mSocket.throwErrnoNative(errno); } return socket; } @@ -1134,6 +1110,8 @@ public final class BluetoothAdapter { */ public Pair<byte[], byte[]> readOutOfBandData() { if (getState() != STATE_ON) return null; + //TODO(BT + /* try { byte[] hash; byte[] randomizer; @@ -1151,7 +1129,7 @@ public final class BluetoothAdapter { } return new Pair<byte[], byte[]>(hash, randomizer); - } catch (RemoteException e) {Log.e(TAG, "", e);} + } catch (RemoteException e) {Log.e(TAG, "", e);}*/ return null; } @@ -1231,14 +1209,53 @@ public final class BluetoothAdapter { } } + final private IBluetoothManagerCallback mManagerCallback = + new IBluetoothManagerCallback.Stub() { + public void onBluetoothServiceUp(IBluetooth bluetoothService) { + if (DBG) Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService); + synchronized (mManagerCallback) { + mService = bluetoothService; + for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){ + try { + if (cb != null) { + cb.onBluetoothServiceUp(bluetoothService); + } else { + Log.d(TAG, "onBluetoothServiceUp: cb is null!!!"); + } + } catch (Exception e) { Log.e(TAG,"",e);} + } + } + } + + public void onBluetoothServiceDown() { + if (DBG) Log.d(TAG, "onBluetoothServiceDown: " + mService); + synchronized (mManagerCallback) { + mService = null; + for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){ + try { + if (cb != null) { + cb.onBluetoothServiceDown(); + } else { + Log.d(TAG, "onBluetoothServiceDown: cb is null!!!"); + } + } catch (Exception e) { Log.e(TAG,"",e);} + } + } + } + }; + /** * Enable the Bluetooth Adapter, but don't auto-connect devices * and don't persist state. Only for use by system applications. * @hide */ public boolean enableNoAutoConnect() { + if (isEnabled() == true){ + if (DBG) Log.d(TAG, "enableNoAutoConnect(): BT is already enabled..!"); + return true; + } try { - return mService.enableNoAutoConnect(); + return mManagerService.enableNoAutoConnect(); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -1276,12 +1293,14 @@ public final class BluetoothAdapter { BluetoothStateChangeCallback callback) { if (callback == null) return false; + //TODO(BT) + /* try { return mService.changeApplicationBluetoothState(on, new StateChangeCallbackWrapper(callback), new Binder()); } catch (RemoteException e) { Log.e(TAG, "changeBluetoothState", e); - } + }*/ return false; } @@ -1309,14 +1328,22 @@ public final class BluetoothAdapter { } } - private Set<BluetoothDevice> toDeviceSet(String[] addresses) { - Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length); - for (int i = 0; i < addresses.length; i++) { - devices.add(getRemoteDevice(addresses[i])); + private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) { + Set<BluetoothDevice> deviceSet = new HashSet<BluetoothDevice>(Arrays.asList(devices)); + return Collections.unmodifiableSet(deviceSet); + } + + protected void finalize() throws Throwable { + try { + mManagerService.unregisterAdapter(mManagerCallback); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + super.finalize(); } - return Collections.unmodifiableSet(devices); } + /** * Validate a String Bluetooth address, such as "00:43:A8:23:10:F0" * <p>Alphabetic characters must be uppercase to be valid. @@ -1347,4 +1374,27 @@ public final class BluetoothAdapter { } return true; } + + /*package*/ IBluetoothManager getBluetoothManager() { + return mManagerService; + } + + private ArrayList<IBluetoothManagerCallback> mProxyServiceStateCallbacks = new ArrayList<IBluetoothManagerCallback>(); + + /*package*/ IBluetooth getBluetoothService(IBluetoothManagerCallback cb) { + synchronized (mManagerCallback) { + if (cb == null) { + Log.w(TAG, "getBluetoothService() called with no BluetoothManagerCallback"); + } else if (!mProxyServiceStateCallbacks.contains(cb)) { + mProxyServiceStateCallbacks.add(cb); + } + } + return mService; + } + + /*package*/ void removeServiceStateCallback(IBluetoothManagerCallback cb) { + synchronized (mManagerCallback) { + mProxyServiceStateCallbacks.remove(cb); + } + } } diff --git a/core/java/android/bluetooth/BluetoothAudioGateway.java b/core/java/android/bluetooth/BluetoothAudioGateway.java deleted file mode 100644 index 9351393..0000000 --- a/core/java/android/bluetooth/BluetoothAudioGateway.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import java.lang.Thread; - -import android.os.Message; -import android.os.Handler; -import android.util.Log; - -/** - * Listens for incoming RFCOMM connection for the headset / handsfree service. - * - * TODO: Use the new generic BluetoothSocket class instead of this legacy code - * - * @hide - */ -public final class BluetoothAudioGateway { - private static final String TAG = "BT Audio Gateway"; - private static final boolean DBG = false; - - private int mNativeData; - static { classInitNative(); } - - /* in */ - private int mHandsfreeAgRfcommChannel = -1; - private int mHeadsetAgRfcommChannel = -1; - - /* out - written by native code */ - private String mConnectingHeadsetAddress; - private int mConnectingHeadsetRfcommChannel; /* -1 when not connected */ - private int mConnectingHeadsetSocketFd; - private String mConnectingHandsfreeAddress; - private int mConnectingHandsfreeRfcommChannel; /* -1 when not connected */ - private int mConnectingHandsfreeSocketFd; - private int mTimeoutRemainingMs; /* in/out */ - - private final BluetoothAdapter mAdapter; - - public static final int DEFAULT_HF_AG_CHANNEL = 10; - public static final int DEFAULT_HS_AG_CHANNEL = 11; - - public BluetoothAudioGateway(BluetoothAdapter adapter) { - this(adapter, DEFAULT_HF_AG_CHANNEL, DEFAULT_HS_AG_CHANNEL); - } - - public BluetoothAudioGateway(BluetoothAdapter adapter, int handsfreeAgRfcommChannel, - int headsetAgRfcommChannel) { - mAdapter = adapter; - mHandsfreeAgRfcommChannel = handsfreeAgRfcommChannel; - mHeadsetAgRfcommChannel = headsetAgRfcommChannel; - initializeNativeDataNative(); - } - - private Thread mConnectThead; - private volatile boolean mInterrupted; - private static final int SELECT_WAIT_TIMEOUT = 1000; - - private Handler mCallback; - - public class IncomingConnectionInfo { - public BluetoothAdapter mAdapter; - public BluetoothDevice mRemoteDevice; - public int mSocketFd; - public int mRfcommChan; - IncomingConnectionInfo(BluetoothAdapter adapter, BluetoothDevice remoteDevice, - int socketFd, int rfcommChan) { - mAdapter = adapter; - mRemoteDevice = remoteDevice; - mSocketFd = socketFd; - mRfcommChan = rfcommChan; - } - } - - public static final int MSG_INCOMING_HEADSET_CONNECTION = 100; - public static final int MSG_INCOMING_HANDSFREE_CONNECTION = 101; - - public synchronized boolean start(Handler callback) { - - if (mConnectThead == null) { - mCallback = callback; - mConnectThead = new Thread(TAG) { - public void run() { - if (DBG) log("Connect Thread starting"); - while (!mInterrupted) { - //Log.i(TAG, "waiting for connect"); - mConnectingHeadsetRfcommChannel = -1; - mConnectingHandsfreeRfcommChannel = -1; - if (waitForHandsfreeConnectNative(SELECT_WAIT_TIMEOUT) == false) { - if (mTimeoutRemainingMs > 0) { - try { - Log.i(TAG, "select thread timed out, but " + - mTimeoutRemainingMs + "ms of waiting remain."); - Thread.sleep(mTimeoutRemainingMs); - } catch (InterruptedException e) { - Log.i(TAG, "select thread was interrupted (2), exiting"); - mInterrupted = true; - } - } - } - else { - Log.i(TAG, "connect notification!"); - /* A device connected (most likely just one, but - it is possible for two separate devices, one - a headset and one a handsfree, to connect - simultaneously. - */ - if (mConnectingHeadsetRfcommChannel >= 0) { - Log.i(TAG, "Incoming connection from headset " + - mConnectingHeadsetAddress + " on channel " + - mConnectingHeadsetRfcommChannel); - Message msg = Message.obtain(mCallback); - msg.what = MSG_INCOMING_HEADSET_CONNECTION; - msg.obj = new IncomingConnectionInfo( - mAdapter, - mAdapter.getRemoteDevice(mConnectingHeadsetAddress), - mConnectingHeadsetSocketFd, - mConnectingHeadsetRfcommChannel); - msg.sendToTarget(); - } - if (mConnectingHandsfreeRfcommChannel >= 0) { - Log.i(TAG, "Incoming connection from handsfree " + - mConnectingHandsfreeAddress + " on channel " + - mConnectingHandsfreeRfcommChannel); - Message msg = Message.obtain(); - msg.setTarget(mCallback); - msg.what = MSG_INCOMING_HANDSFREE_CONNECTION; - msg.obj = new IncomingConnectionInfo( - mAdapter, - mAdapter.getRemoteDevice(mConnectingHandsfreeAddress), - mConnectingHandsfreeSocketFd, - mConnectingHandsfreeRfcommChannel); - msg.sendToTarget(); - } - } - } - if (DBG) log("Connect Thread finished"); - } - }; - - if (setUpListeningSocketsNative() == false) { - Log.e(TAG, "Could not set up listening socket, exiting"); - return false; - } - - mInterrupted = false; - mConnectThead.start(); - } - - return true; - } - - public synchronized void stop() { - if (mConnectThead != null) { - if (DBG) log("stopping Connect Thread"); - mInterrupted = true; - try { - mConnectThead.interrupt(); - if (DBG) log("waiting for thread to terminate"); - mConnectThead.join(); - mConnectThead = null; - mCallback = null; - tearDownListeningSocketsNative(); - } catch (InterruptedException e) { - Log.w(TAG, "Interrupted waiting for Connect Thread to join"); - } - } - } - - protected void finalize() throws Throwable { - try { - cleanupNativeDataNative(); - } finally { - super.finalize(); - } - } - - private static native void classInitNative(); - private native void initializeNativeDataNative(); - private native void cleanupNativeDataNative(); - private native boolean waitForHandsfreeConnectNative(int timeoutMs); - private native boolean setUpListeningSocketsNative(); - private native void tearDownListeningSocketsNative(); - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 56e1735..4cc22b4 100644..100755 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -65,6 +65,7 @@ import java.util.UUID; */ public final class BluetoothDevice implements Parcelable { private static final String TAG = "BluetoothDevice"; + private static final boolean DBG = false; /** * Sentinel error value for this class. Guaranteed to not equal any other @@ -483,16 +484,29 @@ public final class BluetoothDevice implements Parcelable { /*package*/ static IBluetooth getService() { synchronized (BluetoothDevice.class) { if (sService == null) { - IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); - if (b == null) { - throw new RuntimeException("Bluetooth service not available"); - } - sService = IBluetooth.Stub.asInterface(b); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + sService = adapter.getBluetoothService(mStateChangeCallback); } } return sService; } + static IBluetoothManagerCallback mStateChangeCallback = new IBluetoothManagerCallback.Stub() { + + public void onBluetoothServiceUp(IBluetooth bluetoothService) + throws RemoteException { + synchronized (BluetoothDevice.class) { + sService = bluetoothService; + } + } + + public void onBluetoothServiceDown() + throws RemoteException { + synchronized (BluetoothDevice.class) { + sService = null; + } + } + }; /** * Create a new BluetoothDevice * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB", @@ -561,6 +575,7 @@ public final class BluetoothDevice implements Parcelable { * @return Bluetooth hardware address as string */ public String getAddress() { + if (DBG) Log.d(TAG, "mAddress: " + mAddress); return mAddress; } @@ -575,8 +590,12 @@ public final class BluetoothDevice implements Parcelable { * @return the Bluetooth name, or null if there was a problem. */ public String getName() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot get Remote Device name"); + return null; + } try { - return sService.getRemoteName(mAddress); + return sService.getRemoteName(this); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } @@ -589,8 +608,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public String getAlias() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias"); + return null; + } try { - return sService.getRemoteAlias(mAddress); + return sService.getRemoteAlias(this); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } @@ -606,8 +629,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean setAlias(String alias) { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot set Remote Device name"); + return false; + } try { - return sService.setRemoteAlias(mAddress, alias); + return sService.setRemoteAlias(this, alias); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -642,8 +669,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean createBond() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot create bond to Remote Device"); + return false; + } try { - return sService.createBond(mAddress); + return sService.createBond(this); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -668,9 +699,11 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean createBondOutOfBand(byte[] hash, byte[] randomizer) { + //TODO(BT) + /* try { - return sService.createBondOutOfBand(mAddress, hash, randomizer); - } catch (RemoteException e) {Log.e(TAG, "", e);} + return sService.createBondOutOfBand(this, hash, randomizer); + } catch (RemoteException e) {Log.e(TAG, "", e);}*/ return false; } @@ -688,9 +721,11 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) { + //TODO(BT) + /* try { - return sService.setDeviceOutOfBandData(mAddress, hash, randomizer); - } catch (RemoteException e) {Log.e(TAG, "", e);} + return sService.setDeviceOutOfBandData(this, hash, randomizer); + } catch (RemoteException e) {Log.e(TAG, "", e);} */ return false; } @@ -702,8 +737,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean cancelBondProcess() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot cancel Remote Device bond"); + return false; + } try { - return sService.cancelBondProcess(mAddress); + return sService.cancelBondProcess(this); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -719,8 +758,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean removeBond() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond"); + return false; + } try { - return sService.removeBond(mAddress); + return sService.removeBond(this); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -736,9 +779,19 @@ public final class BluetoothDevice implements Parcelable { * @return the bond state */ public int getBondState() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot get bond state"); + return BOND_NONE; + } try { - return sService.getBondState(mAddress); + return sService.getBondState(this); } catch (RemoteException e) {Log.e(TAG, "", e);} + catch (NullPointerException npe) { + // Handle case where bluetooth service proxy + // is already null. + Log.e(TAG, "NullPointerException for getBondState() of device ("+ + getAddress()+")", npe); + } return BOND_NONE; } @@ -749,8 +802,12 @@ public final class BluetoothDevice implements Parcelable { * @return Bluetooth class object, or null on error */ public BluetoothClass getBluetoothClass() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot get Bluetooth Class"); + return null; + } try { - int classInt = sService.getRemoteClass(mAddress); + int classInt = sService.getRemoteClass(this); if (classInt == BluetoothClass.ERROR) return null; return new BluetoothClass(classInt); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -763,11 +820,13 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean getTrustState() { + //TODO(BT) + /* try { - return sService.getTrustState(mAddress); + return sService.getTrustState(this); } catch (RemoteException e) { Log.e(TAG, "", e); - } + }*/ return false; } @@ -778,11 +837,13 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean setTrust(boolean value) { + //TODO(BT) + /* try { - return sService.setTrust(mAddress, value); + return sService.setTrust(this, value); } catch (RemoteException e) { Log.e(TAG, "", e); - } + }*/ return false; } @@ -799,8 +860,12 @@ public final class BluetoothDevice implements Parcelable { * or null on error */ public ParcelUuid[] getUuids() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot get remote device Uuids"); + return null; + } try { - return sService.getRemoteUuids(mAddress); + return sService.getRemoteUuids(this); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } @@ -822,64 +887,84 @@ public final class BluetoothDevice implements Parcelable { */ public boolean fetchUuidsWithSdp() { try { - return sService.fetchRemoteUuids(mAddress, null, null); + return sService.fetchRemoteUuids(this); } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + return false; } /** @hide */ public int getServiceChannel(ParcelUuid uuid) { + //TODO(BT) + /* try { - return sService.getRemoteServiceChannel(mAddress, uuid); - } catch (RemoteException e) {Log.e(TAG, "", e);} + return sService.getRemoteServiceChannel(this, uuid); + } catch (RemoteException e) {Log.e(TAG, "", e);}*/ return BluetoothDevice.ERROR; } /** @hide */ public boolean setPin(byte[] pin) { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot set Remote Device pin"); + return false; + } try { - return sService.setPin(mAddress, pin); + return sService.setPin(this, true, pin.length, pin); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } /** @hide */ public boolean setPasskey(int passkey) { + //TODO(BT) + /* try { - return sService.setPasskey(mAddress, passkey); - } catch (RemoteException e) {Log.e(TAG, "", e);} + return sService.setPasskey(this, true, 4, passkey); + } catch (RemoteException e) {Log.e(TAG, "", e);}*/ return false; } /** @hide */ public boolean setPairingConfirmation(boolean confirm) { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot set pairing confirmation"); + return false; + } try { - return sService.setPairingConfirmation(mAddress, confirm); + return sService.setPairingConfirmation(this, confirm); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } /** @hide */ public boolean setRemoteOutOfBandData() { + // TODO(BT) + /* try { - return sService.setRemoteOutOfBandData(mAddress); - } catch (RemoteException e) {Log.e(TAG, "", e);} + return sService.setRemoteOutOfBandData(this); + } catch (RemoteException e) {Log.e(TAG, "", e);}*/ return false; } /** @hide */ public boolean cancelPairingUserInput() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot create pairing user input"); + return false; + } try { - return sService.cancelPairingUserInput(mAddress); + return sService.cancelBondProcess(this); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } /** @hide */ public boolean isBluetoothDock() { + // TODO(BT) + /* try { - return sService.isBluetoothDock(mAddress); - } catch (RemoteException e) {Log.e(TAG, "", e);} + return sService.isBluetoothDock(this); + } catch (RemoteException e) {Log.e(TAG, "", e);}*/ return false; } diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java deleted file mode 100644 index 020f051..0000000 --- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java +++ /dev/null @@ -1,1357 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Message; -import android.bluetooth.BluetoothAdapter; -import android.os.PowerManager; -import android.server.BluetoothA2dpService; -import android.server.BluetoothService; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; - -import java.util.Set; - -/** - * This class is the Profile connection state machine associated with a remote - * device. When the device bonds an instance of this class is created. - * This tracks incoming and outgoing connections of all the profiles. Incoming - * connections are preferred over outgoing connections and HFP preferred over - * A2DP. When the device is unbonded, the instance is removed. - * - * States: - * {@link BondedDevice}: This state represents a bonded device. When in this - * state none of the profiles are in transition states. - * - * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition - * state because of a outgoing Connect or Disconnect. - * - * {@link IncomingHandsfree}: Handsfree profile connection is in a transition - * state because of a incoming Connect or Disconnect. - * - * {@link IncomingA2dp}: A2dp profile connection is in a transition - * state because of a incoming Connect or Disconnect. - * - * {@link OutgoingA2dp}: A2dp profile connection is in a transition - * state because of a outgoing Connect or Disconnect. - * - * Todo(): Write tests for this class, when the Android Mock support is completed. - * @hide - */ -public final class BluetoothDeviceProfileState extends StateMachine { - private static final String TAG = "BluetoothDeviceProfileState"; - private static final boolean DBG = false; - - // TODO(): Restructure the state machine to make it scalable with regard to profiles. - public static final int CONNECT_HFP_OUTGOING = 1; - public static final int CONNECT_HFP_INCOMING = 2; - public static final int CONNECT_A2DP_OUTGOING = 3; - public static final int CONNECT_A2DP_INCOMING = 4; - public static final int CONNECT_HID_OUTGOING = 5; - public static final int CONNECT_HID_INCOMING = 6; - - public static final int DISCONNECT_HFP_OUTGOING = 50; - private static final int DISCONNECT_HFP_INCOMING = 51; - public static final int DISCONNECT_A2DP_OUTGOING = 52; - public static final int DISCONNECT_A2DP_INCOMING = 53; - public static final int DISCONNECT_HID_OUTGOING = 54; - public static final int DISCONNECT_HID_INCOMING = 55; - public static final int DISCONNECT_PBAP_OUTGOING = 56; - - public static final int UNPAIR = 100; - public static final int AUTO_CONNECT_PROFILES = 101; - public static final int TRANSITION_TO_STABLE = 102; - public static final int CONNECT_OTHER_PROFILES = 103; - private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104; - private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105; - - public static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs - private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs - private static final int CONNECTION_ACCESS_UNDEFINED = -1; - private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec - private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours - - private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; - private static final String ACCESS_AUTHORITY_CLASS = - "com.android.settings.bluetooth.BluetoothPermissionRequest"; - - private BondedDevice mBondedDevice = new BondedDevice(); - private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree(); - private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree(); - private IncomingA2dp mIncomingA2dp = new IncomingA2dp(); - private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp(); - private OutgoingHid mOutgoingHid = new OutgoingHid(); - private IncomingHid mIncomingHid = new IncomingHid(); - - private Context mContext; - private BluetoothService mService; - private BluetoothA2dpService mA2dpService; - private BluetoothHeadset mHeadsetService; - private BluetoothPbap mPbapService; - private PbapServiceListener mPbap; - private BluetoothAdapter mAdapter; - private boolean mPbapServiceConnected; - private boolean mAutoConnectionPending; - private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; - - private BluetoothDevice mDevice; - private int mHeadsetState = BluetoothProfile.STATE_DISCONNECTED; - private int mA2dpState = BluetoothProfile.STATE_DISCONNECTED; - private long mIncomingRejectTimer; - private boolean mConnectionAccessReplyReceived = false; - private Pair<Integer, String> mIncomingConnections; - private PowerManager.WakeLock mWakeLock; - private PowerManager mPowerManager; - private boolean mPairingRequestRcvd = false; - - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device == null || !device.equals(mDevice)) return; - - if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); - // We trust this device now - if (newState == BluetoothHeadset.STATE_CONNECTED) { - setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); - } - mA2dpState = newState; - if (oldState == BluetoothA2dp.STATE_CONNECTED && - newState == BluetoothA2dp.STATE_DISCONNECTED) { - sendMessage(DISCONNECT_A2DP_INCOMING); - } - if (newState == BluetoothProfile.STATE_CONNECTED || - newState == BluetoothProfile.STATE_DISCONNECTED) { - sendMessage(TRANSITION_TO_STABLE); - } - } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); - // We trust this device now - if (newState == BluetoothHeadset.STATE_CONNECTED) { - setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); - } - mHeadsetState = newState; - if (oldState == BluetoothHeadset.STATE_CONNECTED && - newState == BluetoothHeadset.STATE_DISCONNECTED) { - sendMessage(DISCONNECT_HFP_INCOMING); - } - if (newState == BluetoothProfile.STATE_CONNECTED || - newState == BluetoothProfile.STATE_DISCONNECTED) { - sendMessage(TRANSITION_TO_STABLE); - } - } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - int oldState = - intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); - // We trust this device now - if (newState == BluetoothHeadset.STATE_CONNECTED) { - setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); - } - if (oldState == BluetoothProfile.STATE_CONNECTED && - newState == BluetoothProfile.STATE_DISCONNECTED) { - sendMessage(DISCONNECT_HID_INCOMING); - } - if (newState == BluetoothProfile.STATE_CONNECTED || - newState == BluetoothProfile.STATE_DISCONNECTED) { - sendMessage(TRANSITION_TO_STABLE); - } - } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { - // This is technically not needed, but we can get stuck sometimes. - // For example, if incoming A2DP fails, we are not informed by Bluez - sendMessage(TRANSITION_TO_STABLE); - } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { - mWakeLock.release(); - int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, - BluetoothDevice.CONNECTION_ACCESS_NO); - Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY); - msg.arg1 = val; - sendMessage(msg); - } else if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { - mPairingRequestRcvd = true; - } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, - BluetoothDevice.ERROR); - if (state == BluetoothDevice.BOND_BONDED && mPairingRequestRcvd) { - setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); - mPairingRequestRcvd = false; - } else if (state == BluetoothDevice.BOND_NONE) { - mPairingRequestRcvd = false; - } - } - } - }; - - private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) { - // This works only because these broadcast intents are "sticky" - Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - if (i != null) { - int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device != null && autoConnectDevice.equals(device)) { - return true; - } - } - } - return false; - } - - public BluetoothDeviceProfileState(Context context, String address, - BluetoothService service, BluetoothA2dpService a2dpService, boolean setTrust) { - super(address); - mContext = context; - mDevice = new BluetoothDevice(address); - mService = service; - mA2dpService = a2dpService; - - addState(mBondedDevice); - addState(mOutgoingHandsfree); - addState(mIncomingHandsfree); - addState(mIncomingA2dp); - addState(mOutgoingA2dp); - addState(mOutgoingHid); - addState(mIncomingHid); - setInitialState(mBondedDevice); - - IntentFilter filter = new IntentFilter(); - // Fine-grained state broadcasts - filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); - filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); - filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); - filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); - filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - - mContext.registerReceiver(mBroadcastReceiver, filter); - - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mAdapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEADSET); - // TODO(): Convert PBAP to the new Profile APIs. - mPbap = new PbapServiceListener(); - - mIncomingConnections = mService.getIncomingState(address); - mIncomingRejectTimer = readTimerValue(); - mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | - PowerManager.ACQUIRE_CAUSES_WAKEUP | - PowerManager.ON_AFTER_RELEASE, TAG); - mWakeLock.setReferenceCounted(false); - - if (setTrust) { - setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); - } - } - - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - synchronized(BluetoothDeviceProfileState.this) { - mHeadsetService = (BluetoothHeadset) proxy; - if (mAutoConnectionPending) { - sendMessage(AUTO_CONNECT_PROFILES); - mAutoConnectionPending = false; - } - } - } - public void onServiceDisconnected(int profile) { - synchronized(BluetoothDeviceProfileState.this) { - mHeadsetService = null; - } - } - }; - - private class PbapServiceListener implements BluetoothPbap.ServiceListener { - public PbapServiceListener() { - mPbapService = new BluetoothPbap(mContext, this); - } - public void onServiceConnected() { - synchronized(BluetoothDeviceProfileState.this) { - mPbapServiceConnected = true; - } - } - public void onServiceDisconnected() { - synchronized(BluetoothDeviceProfileState.this) { - mPbapServiceConnected = false; - } - } - } - - @Override - protected void onQuitting() { - mContext.unregisterReceiver(mBroadcastReceiver); - mBroadcastReceiver = null; - mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetService); - mBluetoothProfileServiceListener = null; - mOutgoingHandsfree = null; - mPbap = null; - mPbapService.close(); - mPbapService = null; - mIncomingHid = null; - mOutgoingHid = null; - mIncomingHandsfree = null; - mOutgoingHandsfree = null; - mIncomingA2dp = null; - mOutgoingA2dp = null; - mBondedDevice = null; - } - - private class BondedDevice extends State { - @Override - public void enter() { - Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what); - Message m = new Message(); - m.copyFrom(getCurrentMessage()); - sendMessageAtFrontOfQueue(m); - } - @Override - public boolean processMessage(Message message) { - log("ACL Connected State -> Processing Message: " + message.what); - switch(message.what) { - case CONNECT_HFP_OUTGOING: - case DISCONNECT_HFP_OUTGOING: - transitionTo(mOutgoingHandsfree); - break; - case CONNECT_HFP_INCOMING: - transitionTo(mIncomingHandsfree); - break; - case DISCONNECT_HFP_INCOMING: - transitionTo(mIncomingHandsfree); - break; - case CONNECT_A2DP_OUTGOING: - case DISCONNECT_A2DP_OUTGOING: - transitionTo(mOutgoingA2dp); - break; - case CONNECT_A2DP_INCOMING: - case DISCONNECT_A2DP_INCOMING: - transitionTo(mIncomingA2dp); - break; - case CONNECT_HID_OUTGOING: - case DISCONNECT_HID_OUTGOING: - transitionTo(mOutgoingHid); - break; - case CONNECT_HID_INCOMING: - case DISCONNECT_HID_INCOMING: - transitionTo(mIncomingHid); - break; - case DISCONNECT_PBAP_OUTGOING: - processCommand(DISCONNECT_PBAP_OUTGOING); - break; - case UNPAIR: - if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) { - sendMessage(DISCONNECT_HFP_OUTGOING); - deferMessage(message); - break; - } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) { - sendMessage(DISCONNECT_A2DP_OUTGOING); - deferMessage(message); - break; - } else if (mService.getInputDeviceConnectionState(mDevice) != - BluetoothInputDevice.STATE_DISCONNECTED) { - sendMessage(DISCONNECT_HID_OUTGOING); - deferMessage(message); - break; - } - processCommand(UNPAIR); - break; - case AUTO_CONNECT_PROFILES: - if (isPhoneDocked(mDevice)) { - // Don't auto connect to docks. - break; - } else { - if (mHeadsetService == null) { - mAutoConnectionPending = true; - } else if (mHeadsetService.getPriority(mDevice) == - BluetoothHeadset.PRIORITY_AUTO_CONNECT && - mHeadsetService.getDevicesMatchingConnectionStates( - new int[] {BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}).size() == 0) { - mHeadsetService.connect(mDevice); - } - if (mA2dpService != null && - mA2dpService.getPriority(mDevice) == - BluetoothA2dp.PRIORITY_AUTO_CONNECT && - mA2dpService.getDevicesMatchingConnectionStates( - new int[] {BluetoothA2dp.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}).size() == 0) { - mA2dpService.connect(mDevice); - } - if (mService.getInputDevicePriority(mDevice) == - BluetoothInputDevice.PRIORITY_AUTO_CONNECT) { - mService.connectInputDevice(mDevice); - } - } - break; - case CONNECT_OTHER_PROFILES: - if (isPhoneDocked(mDevice)) { - break; - } - if (message.arg1 == CONNECT_A2DP_OUTGOING) { - if (mA2dpService != null && - mA2dpService.getConnectedDevices().size() == 0) { - Log.i(TAG, "A2dp:Connect Other Profiles"); - mA2dpService.connect(mDevice); - } - } else if (message.arg1 == CONNECT_HFP_OUTGOING) { - if (mHeadsetService == null) { - deferMessage(message); - } else { - if (mHeadsetService.getConnectedDevices().size() == 0) { - Log.i(TAG, "Headset:Connect Other Profiles"); - mHeadsetService.connect(mDevice); - } - } - } - break; - case TRANSITION_TO_STABLE: - // ignore. - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - private class OutgoingHandsfree extends State { - private boolean mStatus = false; - private int mCommand; - - @Override - public void enter() { - Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what); - mCommand = getCurrentMessage().what; - if (mCommand != CONNECT_HFP_OUTGOING && - mCommand != DISCONNECT_HFP_OUTGOING) { - Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand); - } - mStatus = processCommand(mCommand); - if (!mStatus) { - sendMessage(TRANSITION_TO_STABLE); - mService.sendProfileStateMessage(BluetoothProfileState.HFP, - BluetoothProfileState.TRANSITION_TO_STABLE); - } - } - - @Override - public boolean processMessage(Message message) { - log("OutgoingHandsfree State -> Processing Message: " + message.what); - Message deferMsg = new Message(); - int command = message.what; - switch(command) { - case CONNECT_HFP_OUTGOING: - if (command != mCommand) { - // Disconnect followed by a connect - defer - deferMessage(message); - } - break; - case CONNECT_HFP_INCOMING: - if (mCommand == CONNECT_HFP_OUTGOING) { - // Cancel outgoing connect, accept incoming - cancelCommand(CONNECT_HFP_OUTGOING); - transitionTo(mIncomingHandsfree); - } else { - // We have done the disconnect but we are not - // sure which state we are in at this point. - deferMessage(message); - } - break; - case CONNECT_A2DP_INCOMING: - // accept incoming A2DP, retry HFP_OUTGOING - transitionTo(mIncomingA2dp); - - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case CONNECT_A2DP_OUTGOING: - deferMessage(message); - break; - case DISCONNECT_HFP_OUTGOING: - if (mCommand == CONNECT_HFP_OUTGOING) { - // Cancel outgoing connect - cancelCommand(CONNECT_HFP_OUTGOING); - processCommand(DISCONNECT_HFP_OUTGOING); - } - // else ignore - break; - case DISCONNECT_HFP_INCOMING: - // When this happens the socket would be closed and the headset - // state moved to DISCONNECTED, cancel the outgoing thread. - // if it still is in CONNECTING state - cancelCommand(CONNECT_HFP_OUTGOING); - break; - case DISCONNECT_A2DP_OUTGOING: - deferMessage(message); - break; - case DISCONNECT_A2DP_INCOMING: - // Bluez will handle the disconnect. If because of this the outgoing - // handsfree connection has failed, then retry. - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case CONNECT_HID_OUTGOING: - case DISCONNECT_HID_OUTGOING: - deferMessage(message); - break; - case CONNECT_HID_INCOMING: - transitionTo(mIncomingHid); - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case DISCONNECT_HID_INCOMING: - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; // ignore - case DISCONNECT_PBAP_OUTGOING: - case UNPAIR: - case AUTO_CONNECT_PROFILES: - case CONNECT_OTHER_PROFILES: - deferMessage(message); - break; - case TRANSITION_TO_STABLE: - transitionTo(mBondedDevice); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - private class IncomingHandsfree extends State { - private boolean mStatus = false; - private int mCommand; - - @Override - public void enter() { - Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what); - mCommand = getCurrentMessage().what; - if (mCommand != CONNECT_HFP_INCOMING && - mCommand != DISCONNECT_HFP_INCOMING) { - Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand); - } - mStatus = processCommand(mCommand); - if (!mStatus) { - sendMessage(TRANSITION_TO_STABLE); - mService.sendProfileStateMessage(BluetoothProfileState.HFP, - BluetoothProfileState.TRANSITION_TO_STABLE); - } - } - - @Override - public boolean processMessage(Message message) { - log("IncomingHandsfree State -> Processing Message: " + message.what); - switch(message.what) { - case CONNECT_HFP_OUTGOING: - deferMessage(message); - break; - case CONNECT_HFP_INCOMING: - // Ignore - Log.e(TAG, "Error: Incoming connection with a pending incoming connection"); - break; - case CONNECTION_ACCESS_REQUEST_REPLY: - int val = message.arg1; - mConnectionAccessReplyReceived = true; - boolean value = false; - if (val == BluetoothDevice.CONNECTION_ACCESS_YES) { - value = true; - } - setTrust(val); - - handleIncomingConnection(CONNECT_HFP_INCOMING, value); - break; - case CONNECTION_ACCESS_REQUEST_EXPIRY: - if (!mConnectionAccessReplyReceived) { - handleIncomingConnection(CONNECT_HFP_INCOMING, false); - sendConnectionAccessRemovalIntent(); - sendMessage(TRANSITION_TO_STABLE); - } - break; - case CONNECT_A2DP_INCOMING: - // Serialize the commands. - deferMessage(message); - break; - case CONNECT_A2DP_OUTGOING: - deferMessage(message); - break; - case DISCONNECT_HFP_OUTGOING: - // We don't know at what state we are in the incoming HFP connection state. - // We can be changing from DISCONNECTED to CONNECTING, or - // from CONNECTING to CONNECTED, so serializing this command is - // the safest option. - deferMessage(message); - break; - case DISCONNECT_HFP_INCOMING: - // Nothing to do here, we will already be DISCONNECTED - // by this point. - break; - case DISCONNECT_A2DP_OUTGOING: - deferMessage(message); - break; - case DISCONNECT_A2DP_INCOMING: - // Bluez handles incoming A2DP disconnect. - // If this causes incoming HFP to fail, it is more of a headset problem - // since both connections are incoming ones. - break; - case CONNECT_HID_OUTGOING: - case DISCONNECT_HID_OUTGOING: - deferMessage(message); - break; - case CONNECT_HID_INCOMING: - case DISCONNECT_HID_INCOMING: - break; // ignore - case DISCONNECT_PBAP_OUTGOING: - case UNPAIR: - case AUTO_CONNECT_PROFILES: - case CONNECT_OTHER_PROFILES: - deferMessage(message); - break; - case TRANSITION_TO_STABLE: - transitionTo(mBondedDevice); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - private class OutgoingA2dp extends State { - private boolean mStatus = false; - private int mCommand; - - @Override - public void enter() { - Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what); - mCommand = getCurrentMessage().what; - if (mCommand != CONNECT_A2DP_OUTGOING && - mCommand != DISCONNECT_A2DP_OUTGOING) { - Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand); - } - mStatus = processCommand(mCommand); - if (!mStatus) { - sendMessage(TRANSITION_TO_STABLE); - mService.sendProfileStateMessage(BluetoothProfileState.A2DP, - BluetoothProfileState.TRANSITION_TO_STABLE); - } - } - - @Override - public boolean processMessage(Message message) { - log("OutgoingA2dp State->Processing Message: " + message.what); - Message deferMsg = new Message(); - switch(message.what) { - case CONNECT_HFP_OUTGOING: - processCommand(CONNECT_HFP_OUTGOING); - - // Don't cancel A2DP outgoing as there is no guarantee it - // will get canceled. - // It might already be connected but we might not have got the - // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here. - // The worst case, the connection will fail, retry. - // The same applies to Disconnecting an A2DP connection. - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case CONNECT_HFP_INCOMING: - processCommand(CONNECT_HFP_INCOMING); - - // Don't cancel A2DP outgoing as there is no guarantee - // it will get canceled. - // The worst case, the connection will fail, retry. - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case CONNECT_A2DP_INCOMING: - // Bluez will take care of conflicts between incoming and outgoing - // connections. - transitionTo(mIncomingA2dp); - break; - case CONNECT_A2DP_OUTGOING: - // Ignore - break; - case DISCONNECT_HFP_OUTGOING: - deferMessage(message); - break; - case DISCONNECT_HFP_INCOMING: - // At this point, we are already disconnected - // with HFP. Sometimes A2DP connection can - // fail due to the disconnection of HFP. So add a retry - // for the A2DP. - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case DISCONNECT_A2DP_OUTGOING: - deferMessage(message); - break; - case DISCONNECT_A2DP_INCOMING: - // Ignore, will be handled by Bluez - break; - case CONNECT_HID_OUTGOING: - case DISCONNECT_HID_OUTGOING: - deferMessage(message); - break; - case CONNECT_HID_INCOMING: - transitionTo(mIncomingHid); - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case DISCONNECT_HID_INCOMING: - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; // ignore - case DISCONNECT_PBAP_OUTGOING: - case UNPAIR: - case AUTO_CONNECT_PROFILES: - case CONNECT_OTHER_PROFILES: - deferMessage(message); - break; - case TRANSITION_TO_STABLE: - transitionTo(mBondedDevice); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - private class IncomingA2dp extends State { - private boolean mStatus = false; - private int mCommand; - - @Override - public void enter() { - Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what); - mCommand = getCurrentMessage().what; - if (mCommand != CONNECT_A2DP_INCOMING && - mCommand != DISCONNECT_A2DP_INCOMING) { - Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand); - } - mStatus = processCommand(mCommand); - if (!mStatus) { - sendMessage(TRANSITION_TO_STABLE); - mService.sendProfileStateMessage(BluetoothProfileState.A2DP, - BluetoothProfileState.TRANSITION_TO_STABLE); - } - } - - @Override - public boolean processMessage(Message message) { - log("IncomingA2dp State->Processing Message: " + message.what); - switch(message.what) { - case CONNECT_HFP_OUTGOING: - deferMessage(message); - break; - case CONNECT_HFP_INCOMING: - // Shouldn't happen, but serialize the commands. - deferMessage(message); - break; - case CONNECT_A2DP_INCOMING: - // ignore - break; - case CONNECTION_ACCESS_REQUEST_REPLY: - int val = message.arg1; - mConnectionAccessReplyReceived = true; - boolean value = false; - if (val == BluetoothDevice.CONNECTION_ACCESS_YES) { - value = true; - } - setTrust(val); - handleIncomingConnection(CONNECT_A2DP_INCOMING, value); - break; - case CONNECTION_ACCESS_REQUEST_EXPIRY: - // The check protects the race condition between REQUEST_REPLY - // and the timer expiry. - if (!mConnectionAccessReplyReceived) { - handleIncomingConnection(CONNECT_A2DP_INCOMING, false); - sendConnectionAccessRemovalIntent(); - sendMessage(TRANSITION_TO_STABLE); - } - break; - case CONNECT_A2DP_OUTGOING: - // Defer message and retry - deferMessage(message); - break; - case DISCONNECT_HFP_OUTGOING: - deferMessage(message); - break; - case DISCONNECT_HFP_INCOMING: - // Shouldn't happen but if does, we can handle it. - // Depends if the headset can handle it. - // Incoming A2DP will be handled by Bluez, Disconnect HFP - // the socket would have already been closed. - // ignore - break; - case DISCONNECT_A2DP_OUTGOING: - deferMessage(message); - break; - case DISCONNECT_A2DP_INCOMING: - // Ignore, will be handled by Bluez - break; - case CONNECT_HID_OUTGOING: - case DISCONNECT_HID_OUTGOING: - deferMessage(message); - break; - case CONNECT_HID_INCOMING: - case DISCONNECT_HID_INCOMING: - break; // ignore - case DISCONNECT_PBAP_OUTGOING: - case UNPAIR: - case AUTO_CONNECT_PROFILES: - case CONNECT_OTHER_PROFILES: - deferMessage(message); - break; - case TRANSITION_TO_STABLE: - transitionTo(mBondedDevice); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - - private class OutgoingHid extends State { - private boolean mStatus = false; - private int mCommand; - - @Override - public void enter() { - log("Entering OutgoingHid state with: " + getCurrentMessage().what); - mCommand = getCurrentMessage().what; - if (mCommand != CONNECT_HID_OUTGOING && - mCommand != DISCONNECT_HID_OUTGOING) { - Log.e(TAG, "Error: OutgoingHid state with command:" + mCommand); - } - mStatus = processCommand(mCommand); - if (!mStatus) sendMessage(TRANSITION_TO_STABLE); - } - - @Override - public boolean processMessage(Message message) { - log("OutgoingHid State->Processing Message: " + message.what); - Message deferMsg = new Message(); - switch(message.what) { - // defer all outgoing messages - case CONNECT_HFP_OUTGOING: - case CONNECT_A2DP_OUTGOING: - case CONNECT_HID_OUTGOING: - case DISCONNECT_HFP_OUTGOING: - case DISCONNECT_A2DP_OUTGOING: - case DISCONNECT_HID_OUTGOING: - deferMessage(message); - break; - - case CONNECT_HFP_INCOMING: - transitionTo(mIncomingHandsfree); - break; - case CONNECT_A2DP_INCOMING: - transitionTo(mIncomingA2dp); - - // Don't cancel HID outgoing as there is no guarantee it - // will get canceled. - // It might already be connected but we might not have got the - // INPUT_DEVICE_STATE_CHANGE. Hence, no point disconnecting here. - // The worst case, the connection will fail, retry. - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case CONNECT_HID_INCOMING: - // Bluez will take care of the conflicts - transitionTo(mIncomingHid); - break; - - case DISCONNECT_HFP_INCOMING: - case DISCONNECT_A2DP_INCOMING: - // At this point, we are already disconnected - // with HFP. Sometimes HID connection can - // fail due to the disconnection of HFP. So add a retry - // for the HID. - if (mStatus) { - deferMsg.what = mCommand; - deferMessage(deferMsg); - } - break; - case DISCONNECT_HID_INCOMING: - // Ignore, will be handled by Bluez - break; - case DISCONNECT_PBAP_OUTGOING: - case UNPAIR: - case AUTO_CONNECT_PROFILES: - deferMessage(message); - break; - case TRANSITION_TO_STABLE: - transitionTo(mBondedDevice); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - private class IncomingHid extends State { - private boolean mStatus = false; - private int mCommand; - - @Override - public void enter() { - log("Entering IncomingHid state with: " + getCurrentMessage().what); - mCommand = getCurrentMessage().what; - if (mCommand != CONNECT_HID_INCOMING && - mCommand != DISCONNECT_HID_INCOMING) { - Log.e(TAG, "Error: IncomingHid state with command:" + mCommand); - } - mStatus = processCommand(mCommand); - if (!mStatus) sendMessage(TRANSITION_TO_STABLE); - } - - @Override - public boolean processMessage(Message message) { - log("IncomingHid State->Processing Message: " + message.what); - Message deferMsg = new Message(); - switch(message.what) { - case CONNECT_HFP_OUTGOING: - case CONNECT_HFP_INCOMING: - case DISCONNECT_HFP_OUTGOING: - case CONNECT_A2DP_INCOMING: - case CONNECT_A2DP_OUTGOING: - case DISCONNECT_A2DP_OUTGOING: - case CONNECT_HID_OUTGOING: - case CONNECT_HID_INCOMING: - case DISCONNECT_HID_OUTGOING: - deferMessage(message); - break; - case CONNECTION_ACCESS_REQUEST_REPLY: - mConnectionAccessReplyReceived = true; - int val = message.arg1; - setTrust(val); - handleIncomingConnection(CONNECT_HID_INCOMING, - val == BluetoothDevice.CONNECTION_ACCESS_YES); - break; - case CONNECTION_ACCESS_REQUEST_EXPIRY: - if (!mConnectionAccessReplyReceived) { - handleIncomingConnection(CONNECT_HID_INCOMING, false); - sendConnectionAccessRemovalIntent(); - sendMessage(TRANSITION_TO_STABLE); - } - break; - case DISCONNECT_HFP_INCOMING: - // Shouldn't happen but if does, we can handle it. - // Depends if the headset can handle it. - // Incoming HID will be handled by Bluez, Disconnect HFP - // the socket would have already been closed. - // ignore - break; - case DISCONNECT_HID_INCOMING: - case DISCONNECT_A2DP_INCOMING: - // Ignore, will be handled by Bluez - break; - case DISCONNECT_PBAP_OUTGOING: - case UNPAIR: - case AUTO_CONNECT_PROFILES: - deferMessage(message); - break; - case TRANSITION_TO_STABLE: - transitionTo(mBondedDevice); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - - synchronized void cancelCommand(int command) { - if (command == CONNECT_HFP_OUTGOING ) { - // Cancel the outgoing thread. - if (mHeadsetService != null) { - mHeadsetService.cancelConnectThread(); - } - // HeadsetService is down. Phone process most likely crashed. - // The thread would have got killed. - } - } - - synchronized void deferProfileServiceMessage(int command) { - Message msg = new Message(); - msg.what = command; - deferMessage(msg); - } - - private void updateIncomingAllowedTimer() { - // Not doing a perfect exponential backoff because - // we want two different rates. For all practical - // purposes, this is good enough. - if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER; - - mIncomingRejectTimer *= 5; - if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) { - mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER; - } - writeTimerValue(mIncomingRejectTimer); - } - - private boolean handleIncomingConnection(int command, boolean accept) { - boolean ret = false; - Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept); - switch (command) { - case CONNECT_HFP_INCOMING: - if (!accept) { - ret = mHeadsetService.rejectIncomingConnect(mDevice); - sendMessage(TRANSITION_TO_STABLE); - updateIncomingAllowedTimer(); - } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { - writeTimerValue(0); - ret = mHeadsetService.acceptIncomingConnect(mDevice); - } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { - writeTimerValue(0); - handleConnectionOfOtherProfiles(command); - ret = mHeadsetService.createIncomingConnect(mDevice); - } - break; - case CONNECT_A2DP_INCOMING: - if (!accept) { - ret = mA2dpService.allowIncomingConnect(mDevice, false); - sendMessage(TRANSITION_TO_STABLE); - updateIncomingAllowedTimer(); - } else { - writeTimerValue(0); - ret = mA2dpService.allowIncomingConnect(mDevice, true); - handleConnectionOfOtherProfiles(command); - } - break; - case CONNECT_HID_INCOMING: - if (!accept) { - ret = mService.allowIncomingProfileConnect(mDevice, false); - sendMessage(TRANSITION_TO_STABLE); - updateIncomingAllowedTimer(); - } else { - writeTimerValue(0); - ret = mService.allowIncomingProfileConnect(mDevice, true); - } - break; - default: - Log.e(TAG, "Waiting for incoming connection but state changed to:" + command); - break; - } - return ret; - } - - private void sendConnectionAccessIntent() { - mConnectionAccessReplyReceived = false; - - if (!mPowerManager.isScreenOn()) mWakeLock.acquire(); - - Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); - intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); - intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, - BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - } - - private void sendConnectionAccessRemovalIntent() { - mWakeLock.release(); - Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - } - - private int getTrust() { - String address = mDevice.getAddress(); - if (mIncomingConnections != null) return mIncomingConnections.first; - return CONNECTION_ACCESS_UNDEFINED; - } - - - private String getStringValue(long value) { - StringBuilder sbr = new StringBuilder(); - sbr.append(Long.toString(System.currentTimeMillis())); - sbr.append("-"); - sbr.append(Long.toString(value)); - return sbr.toString(); - } - - private void setTrust(int value) { - String second; - if (mIncomingConnections == null) { - second = getStringValue(INIT_INCOMING_REJECT_TIMER); - } else { - second = mIncomingConnections.second; - } - - mIncomingConnections = new Pair(value, second); - mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections); - } - - private void writeTimerValue(long value) { - Integer first; - if (mIncomingConnections == null) { - first = CONNECTION_ACCESS_UNDEFINED; - } else { - first = mIncomingConnections.first; - } - mIncomingConnections = new Pair(first, getStringValue(value)); - mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections); - } - - private long readTimerValue() { - if (mIncomingConnections == null) - return 0; - String value = mIncomingConnections.second; - String[] splits = value.split("-"); - if (splits != null && splits.length == 2) { - return Long.parseLong(splits[1]); - } - return 0; - } - - private boolean readIncomingAllowedValue() { - if (readTimerValue() == 0) return true; - String value = mIncomingConnections.second; - String[] splits = value.split("-"); - if (splits != null && splits.length == 2) { - long val1 = Long.parseLong(splits[0]); - long val2 = Long.parseLong(splits[1]); - if (val1 + val2 <= System.currentTimeMillis()) { - return true; - } - } - return false; - } - - synchronized boolean processCommand(int command) { - log("Processing command:" + command); - switch(command) { - case CONNECT_HFP_OUTGOING: - if (mHeadsetService == null) { - deferProfileServiceMessage(command); - } else { - return mHeadsetService.connectHeadsetInternal(mDevice); - } - break; - case CONNECT_HFP_INCOMING: - if (mHeadsetService == null) { - deferProfileServiceMessage(command); - } else { - processIncomingConnectCommand(command); - return true; - } - break; - case CONNECT_A2DP_OUTGOING: - if (mA2dpService != null) { - return mA2dpService.connectSinkInternal(mDevice); - } - break; - case CONNECT_A2DP_INCOMING: - processIncomingConnectCommand(command); - return true; - case CONNECT_HID_OUTGOING: - return mService.connectInputDeviceInternal(mDevice); - case CONNECT_HID_INCOMING: - processIncomingConnectCommand(command); - return true; - case DISCONNECT_HFP_OUTGOING: - if (mHeadsetService == null) { - deferProfileServiceMessage(command); - } else { - // Disconnect PBAP - // TODO(): Add PBAP to the state machine. - Message m = new Message(); - m.what = DISCONNECT_PBAP_OUTGOING; - deferMessage(m); - if (mHeadsetService.getPriority(mDevice) == - BluetoothHeadset.PRIORITY_AUTO_CONNECT) { - mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); - } - return mHeadsetService.disconnectHeadsetInternal(mDevice); - } - break; - case DISCONNECT_HFP_INCOMING: - // ignore - return true; - case DISCONNECT_A2DP_INCOMING: - // ignore - return true; - case DISCONNECT_A2DP_OUTGOING: - if (mA2dpService != null) { - if (mA2dpService.getPriority(mDevice) == - BluetoothA2dp.PRIORITY_AUTO_CONNECT) { - mA2dpService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); - } - return mA2dpService.disconnectSinkInternal(mDevice); - } - break; - case DISCONNECT_HID_INCOMING: - // ignore - return true; - case DISCONNECT_HID_OUTGOING: - if (mService.getInputDevicePriority(mDevice) == - BluetoothInputDevice.PRIORITY_AUTO_CONNECT) { - mService.setInputDevicePriority(mDevice, BluetoothInputDevice.PRIORITY_ON); - } - return mService.disconnectInputDeviceInternal(mDevice); - case DISCONNECT_PBAP_OUTGOING: - if (!mPbapServiceConnected) { - deferProfileServiceMessage(command); - } else { - return mPbapService.disconnect(); - } - break; - case UNPAIR: - writeTimerValue(INIT_INCOMING_REJECT_TIMER); - setTrust(CONNECTION_ACCESS_UNDEFINED); - return mService.removeBondInternal(mDevice.getAddress()); - default: - Log.e(TAG, "Error: Unknown Command"); - } - return false; - } - - private void processIncomingConnectCommand(int command) { - // Check if device is already trusted - int access = getTrust(); - if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { - handleIncomingConnection(command, true); - } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO && - !readIncomingAllowedValue()) { - handleIncomingConnection(command, false); - } else { - sendConnectionAccessIntent(); - Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY); - sendMessageDelayed(msg, - CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT); - } - } - - private void handleConnectionOfOtherProfiles(int command) { - // The white paper recommendations mentions that when there is a - // link loss, it is the responsibility of the remote device to connect. - // Many connect only 1 profile - and they connect the second profile on - // some user action (like play being pressed) and so we need this code. - // Auto Connect code only connects to the last connected device - which - // is useful in cases like when the phone reboots. But consider the - // following case: - // User is connected to the car's phone and A2DP profile. - // User comes to the desk and places the phone in the dock - // (or any speaker or music system or even another headset) and thus - // gets connected to the A2DP profile. User goes back to the car. - // Ideally the car's system is supposed to send incoming connections - // from both Handsfree and A2DP profile. But they don't. The Auto - // connect code, will not work here because we only auto connect to the - // last connected device for that profile which in this case is the dock. - // Now suppose a user is using 2 headsets simultaneously, one for the - // phone profile one for the A2DP profile. If this is the use case, we - // expect the user to use the preference to turn off the A2DP profile in - // the Settings screen for the first headset. Else, after link loss, - // there can be an incoming connection from the first headset which - // might result in the connection of the A2DP profile (if the second - // headset is slower) and thus the A2DP profile on the second headset - // will never get connected. - // - // TODO(): Handle other profiles here. - switch (command) { - case CONNECT_HFP_INCOMING: - // Connect A2DP if there is no incoming connection - // If the priority is OFF - don't auto connect. - if (mA2dpService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON || - mA2dpService.getPriority(mDevice) == - BluetoothProfile.PRIORITY_AUTO_CONNECT) { - Message msg = new Message(); - msg.what = CONNECT_OTHER_PROFILES; - msg.arg1 = CONNECT_A2DP_OUTGOING; - sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY); - } - break; - case CONNECT_A2DP_INCOMING: - // This is again against spec. HFP incoming connections should be made - // before A2DP, so we should not hit this case. But many devices - // don't follow this. - if (mHeadsetService != null && - (mHeadsetService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON || - mHeadsetService.getPriority(mDevice) == - BluetoothProfile.PRIORITY_AUTO_CONNECT)) { - Message msg = new Message(); - msg.what = CONNECT_OTHER_PROFILES; - msg.arg1 = CONNECT_HFP_OUTGOING; - sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY); - } - break; - default: - break; - } - - } - - /*package*/ BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Quit the state machine, only to be called by BluetoothService. - * - * @hide - */ - public void doQuit() { - this.quit(); - } - - private void log(String message) { - if (DBG) { - Log.i(TAG, "Device:" + mDevice + " Message:" + message); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 2bbf008..541b69f 100644..100755 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -45,7 +45,7 @@ import java.util.List; */ public final class BluetoothHeadset implements BluetoothProfile { private static final String TAG = "BluetoothHeadset"; - private static final boolean DBG = false; + private static final boolean DBG = true; /** * Intent used to broadcast the change in connection state of the Headset @@ -219,7 +219,38 @@ public final class BluetoothHeadset implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; private IBluetoothHeadset mService; - BluetoothAdapter mAdapter; + private BluetoothAdapter mAdapter; + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (DBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (DBG) Log.d(TAG,"Binding service..."); + if (!mContext.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Headset Service"); + } + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; /** * Create a BluetoothHeadset proxy object. @@ -228,6 +259,16 @@ public final class BluetoothHeadset implements BluetoothProfile { mContext = context; mServiceListener = l; mAdapter = BluetoothAdapter.getDefaultAdapter(); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) { Log.e(TAG, "Could not bind to Bluetooth Headset Service"); } @@ -239,11 +280,27 @@ public final class BluetoothHeadset implements BluetoothProfile { * results once close() has been called. Multiple invocations of close() * are ok. */ - /*package*/ synchronized void close() { + /*package*/ void close() { if (DBG) log("close()"); - if (mConnection != null) { - mContext.unbindService(mConnection); - mConnection = null; + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } } mServiceListener = null; } @@ -562,25 +619,6 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Cancel the outgoing connection. - * Note: This is an internal function and shouldn't be exposed - * - * @hide - */ - public boolean cancelConnectThread() { - if (DBG) log("cancelConnectThread"); - if (mService != null && isEnabled()) { - try { - return mService.cancelConnectThread(); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } - return false; - } - - /** * Accept the incoming connection. * Note: This is an internal function and shouldn't be exposed * @@ -600,25 +638,6 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Create the connect thread for the incoming connection. - * Note: This is an internal function and shouldn't be exposed - * - * @hide - */ - public boolean createIncomingConnect(BluetoothDevice device) { - if (DBG) log("createIncomingConnect"); - if (mService != null && isEnabled()) { - try { - return mService.createIncomingConnect(device); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } - return false; - } - - /** * Reject the incoming connection. * @hide */ @@ -636,55 +655,63 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Connect to a Bluetooth Headset. + * Get the current audio state of the Headset. * Note: This is an internal function and shouldn't be exposed * * @hide */ - public boolean connectHeadsetInternal(BluetoothDevice device) { - if (DBG) log("connectHeadsetInternal"); - if (mService != null && isEnabled()) { + public int getAudioState(BluetoothDevice device) { + if (DBG) log("getAudioState"); + if (mService != null && !isDisabled()) { try { - return mService.connectHeadsetInternal(device); + return mService.getAudioState(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } - return false; + return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; } /** - * Disconnect a Bluetooth Headset. - * Note: This is an internal function and shouldn't be exposed + * Check if Bluetooth SCO audio is connected. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * + * @return true if SCO is connected, + * false otherwise or on error * @hide */ - public boolean disconnectHeadsetInternal(BluetoothDevice device) { - if (DBG) log("disconnectHeadsetInternal"); - if (mService != null && !isDisabled()) { + public boolean isAudioOn() { + if (DBG) log("isAudioOn()"); + if (mService != null && isEnabled()) { try { - return mService.disconnectHeadsetInternal(device); - } catch (RemoteException e) {Log.e(TAG, e.toString());} - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + return mService.isAudioOn(); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); return false; + } /** - * Set the audio state of the Headset. - * Note: This is an internal function and shouldn't be exposed + * Initiates a connection of headset audio. + * It setup SCO channel with remote connected headset device. * + * @return true if successful + * false if there was some error such as + * there is no connected headset * @hide */ - public boolean setAudioState(BluetoothDevice device, int state) { - if (DBG) log("setAudioState"); - if (mService != null && !isDisabled()) { + public boolean connectAudio() { + if (mService != null && isEnabled()) { try { - return mService.setAudioState(device, state); - } catch (RemoteException e) {Log.e(TAG, e.toString());} + return mService.connectAudio(); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); @@ -693,22 +720,26 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Get the current audio state of the Headset. - * Note: This is an internal function and shouldn't be exposed + * Initiates a disconnection of headset audio. + * It tears down the SCO channel from remote headset device. * + * @return true if successful + * false if there was some error such as + * there is no connected SCO channel * @hide */ - public int getAudioState(BluetoothDevice device) { - if (DBG) log("getAudioState"); - if (mService != null && !isDisabled()) { + public boolean disconnectAudio() { + if (mService != null && isEnabled()) { try { - return mService.getAudioState(device); - } catch (RemoteException e) {Log.e(TAG, e.toString());} + return mService.disconnectAudio(); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } - return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; + return false; } /** @@ -760,6 +791,68 @@ public final class BluetoothHeadset implements BluetoothProfile { return false; } + /** + * Notify Headset of phone state change. + * This is a backdoor for phone app to call BluetoothHeadset since + * there is currently not a good way to get precise call state change outside + * of phone app. + * + * @hide + */ + public void phoneStateChanged(int numActive, int numHeld, int callState, String number, + int type) { + if (mService != null && isEnabled()) { + try { + mService.phoneStateChanged(numActive, numHeld, callState, number, type); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + } + + /** + * Notify Headset of phone roam state change. + * This is a backdoor for phone app to call BluetoothHeadset since + * there is currently not a good way to get roaming state change outside + * of phone app. + * + * @hide + */ + public void roamChanged(boolean roaming) { + if (mService != null && isEnabled()) { + try { + mService.roamChanged(roaming); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + } + + /** + * Send Headset of CLCC response + * + * @hide + */ + public void clccResponse(int index, int direction, int status, int mode, boolean mpty, + String number, int type) { + if (mService != null && isEnabled()) { + try { + mService.clccResponse(index, direction, status, mode, mpty, number, type); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + } + private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java index f850c02..4a0bc7e 100644 --- a/core/java/android/bluetooth/BluetoothHealth.java +++ b/core/java/android/bluetooth/BluetoothHealth.java @@ -16,7 +16,10 @@ package android.bluetooth; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -54,7 +57,7 @@ import java.util.List; */ public final class BluetoothHealth implements BluetoothProfile { private static final String TAG = "BluetoothHealth"; - private static final boolean DBG = false; + private static final boolean DBG = true; /** * Health Profile Source Role - the health device. @@ -94,6 +97,37 @@ public final class BluetoothHealth implements BluetoothProfile { /** @hide */ public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005; + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (DBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (DBG) Log.d(TAG,"Binding service..."); + if (!mContext.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Health Service"); + } + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + /** * Register an application configuration that acts as a Health SINK. @@ -427,35 +461,74 @@ public final class BluetoothHealth implements BluetoothProfile { /** Health App Configuration un-registration failure */ public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; + private Context mContext; private ServiceListener mServiceListener; - private IBluetooth mService; + private IBluetoothHealth mService; BluetoothAdapter mAdapter; /** * Create a BluetoothHealth proxy object. */ - /*package*/ BluetoothHealth(Context mContext, ServiceListener l) { - IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); + /*package*/ BluetoothHealth(Context context, ServiceListener l) { + mContext = context; mServiceListener = l; mAdapter = BluetoothAdapter.getDefaultAdapter(); - if (b != null) { - mService = IBluetooth.Stub.asInterface(b); - if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, this); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); } - } else { - Log.w(TAG, "Bluetooth Service not available!"); + } - // Instead of throwing an exception which prevents people from going - // into Wireless settings in the emulator. Let it crash later when it is actually used. - mService = null; + if (!context.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Health Service"); } } /*package*/ void close() { + if (DBG) log("close()"); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } mServiceListener = null; } + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "Proxy object connected"); + mService = IBluetoothHealth.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH); + } + } + }; + private boolean isEnabled() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java index 1a9e011..bff966d 100644..100755 --- a/core/java/android/bluetooth/BluetoothInputDevice.java +++ b/core/java/android/bluetooth/BluetoothInputDevice.java @@ -18,7 +18,10 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -41,7 +44,7 @@ import java.util.List; */ public final class BluetoothInputDevice implements BluetoothProfile { private static final String TAG = "BluetoothInputDevice"; - private static final boolean DBG = false; + private static final boolean DBG = true; /** * Intent used to broadcast the change in connection state of the Input @@ -66,6 +69,22 @@ public final class BluetoothInputDevice implements BluetoothProfile { "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; /** + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PROTOCOL_MODE_CHANGED = + "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED"; + + + /** + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_VIRTUAL_UNPLUG_STATUS = + "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS"; + + + /** * Return codes for the connect and disconnect Bluez / Dbus calls. * @hide */ @@ -91,34 +110,159 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public static final int INPUT_OPERATION_SUCCESS = 5004; + /** + * @hide + */ + public static final int PROTOCOL_REPORT_MODE = 0; + + /** + * @hide + */ + public static final int PROTOCOL_BOOT_MODE = 1; + + /** + * @hide + */ + public static final int PROTOCOL_UNSUPPORTED_MODE = 255; + + /* int reportType, int reportType, int bufferSize */ + /** + * @hide + */ + public static final byte REPORT_TYPE_INPUT = 0; + + /** + * @hide + */ + public static final byte REPORT_TYPE_OUTPUT = 1; + + /** + * @hide + */ + public static final byte REPORT_TYPE_FEATURE = 2; + + /** + * @hide + */ + public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0; + + /** + * @hide + */ + public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1; + + /** + * @hide + */ + public static final String EXTRA_PROTOCOL_MODE = "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE"; + + /** + * @hide + */ + public static final String EXTRA_REPORT_TYPE = "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE"; + + /** + * @hide + */ + public static final String EXTRA_REPORT_ID = "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID"; + + /** + * @hide + */ + public static final String EXTRA_REPORT_BUFFER_SIZE = "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE"; + + /** + * @hide + */ + public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT"; + + /** + * @hide + */ + public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS"; + + private Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; - private IBluetooth mService; + private IBluetoothInputDevice mService; + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (DBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (DBG) Log.d(TAG,"Binding service..."); + if (!mContext.bindService(new Intent(IBluetoothInputDevice.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth HID Service"); + } + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; /** * Create a BluetoothInputDevice proxy object for interacting with the local * Bluetooth Service which handles the InputDevice profile * */ - /*package*/ BluetoothInputDevice(Context mContext, ServiceListener l) { - IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); + /*package*/ BluetoothInputDevice(Context context, ServiceListener l) { + mContext = context; mServiceListener = l; mAdapter = BluetoothAdapter.getDefaultAdapter(); - if (b != null) { - mService = IBluetooth.Stub.asInterface(b); - if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, this); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); } - } else { - Log.w(TAG, "Bluetooth Service not available!"); + } - // Instead of throwing an exception which prevents people from going - // into Wireless settings in the emulator. Let it crash later when it is actually used. - mService = null; + if (!context.bindService(new Intent(IBluetoothInputDevice.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth HID Service"); } } /*package*/ void close() { + if (DBG) log("close()"); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } mServiceListener = null; } @@ -144,10 +288,9 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); - if (mService != null && isEnabled() && - isValidDevice(device)) { + if (mService != null && isEnabled() && isValidDevice(device)) { try { - return mService.connectInputDevice(device); + return mService.connect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -185,10 +328,9 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mService != null && isEnabled() && - isValidDevice(device)) { + if (mService != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnectInputDevice(device); + return mService.disconnect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -205,7 +347,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { if (DBG) log("getConnectedDevices()"); if (mService != null && isEnabled()) { try { - return mService.getConnectedInputDevices(); + return mService.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -222,7 +364,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { if (DBG) log("getDevicesMatchingStates()"); if (mService != null && isEnabled()) { try { - return mService.getInputDevicesMatchingConnectionStates(states); + return mService.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -237,10 +379,9 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public int getConnectionState(BluetoothDevice device) { if (DBG) log("getState(" + device + ")"); - if (mService != null && isEnabled() - && isValidDevice(device)) { + if (mService != null && isEnabled() && isValidDevice(device)) { try { - return mService.getInputDeviceConnectionState(device); + return mService.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -267,14 +408,13 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null && isEnabled() - && isValidDevice(device)) { + if (mService != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setInputDevicePriority(device, priority); + return mService.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -299,10 +439,9 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (DBG) log("getPriority(" + device + ")"); - if (mService != null && isEnabled() - && isValidDevice(device)) { + if (mService != null && isEnabled() && isValidDevice(device)) { try { - return mService.getInputDevicePriority(device); + return mService.getPriority(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.PRIORITY_OFF; @@ -312,6 +451,24 @@ public final class BluetoothInputDevice implements BluetoothProfile { return BluetoothProfile.PRIORITY_OFF; } + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "Proxy object connected"); + mService = IBluetoothInputDevice.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, BluetoothInputDevice.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE); + } + } + }; + private boolean isEnabled() { if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; return false; @@ -324,6 +481,158 @@ public final class BluetoothInputDevice implements BluetoothProfile { return false; } + + /** + * Initiate virtual unplug for a HID input device. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean virtualUnplug(BluetoothDevice device) { + if (DBG) log("virtualUnplug(" + device + ")"); + if (mService != null && isEnabled() && isValidDevice(device)) { + try { + return mService.virtualUnplug(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + + } + + /** + * Send Get_Protocol_Mode command to the connected HID input device. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + *true otherwise + * @hide + */ + public boolean getProtocolMode(BluetoothDevice device) { + if (DBG) log("getProtocolMode(" + device + ")"); + if (mService != null && isEnabled() && isValidDevice(device)) { + try { + return mService.getProtocolMode(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Send Set_Protocol_Mode command to the connected HID input device. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { + if (DBG) log("setProtocolMode(" + device + ")"); + if (mService != null && isEnabled() && isValidDevice(device)) { + try { + return mService.setProtocolMode(device, protocolMode); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Send Get_Report command to the connected HID input device. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param device Remote Bluetooth Device + * @param reportType Report type + * @param reportId Report ID + * @param bufferSize Report receiving buffer size + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) { + if (DBG) log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + "bufferSize=" + bufferSize); + if (mService != null && isEnabled() && isValidDevice(device)) { + try { + return mService.getReport(device, reportType, reportId, bufferSize); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Send Set_Report command to the connected HID input device. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param device Remote Bluetooth Device + * @param reportType Report type + * @param report Report receiving buffer size + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean setReport(BluetoothDevice device, byte reportType, String report) { + if (DBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report); + if (mService != null && isEnabled() && isValidDevice(device)) { + try { + return mService.setReport(device, reportType, report); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Send Send_Data command to the connected HID input device. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param device Remote Bluetooth Device + * @param data Data to send + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean sendData(BluetoothDevice device, String report) { + if (DBG) log("sendData(" + device + "), report=" + report); + if (mService != null && isEnabled() && isValidDevice(device)) { + try { + return mService.sendData(device, report); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } private static void log(String msg) { Log.d(TAG, msg); } diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 5d9d8be..cae7a73 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -18,7 +18,10 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -27,7 +30,6 @@ import android.util.Log; import java.util.ArrayList; import java.util.List; - /** * This class provides the APIs to control the Bluetooth Pan * Profile. @@ -41,7 +43,7 @@ import java.util.List; */ public final class BluetoothPan implements BluetoothProfile { private static final String TAG = "BluetoothPan"; - private static final boolean DBG = false; + private static final boolean DBG = true; /** * Intent used to broadcast the change in connection state of the Pan @@ -76,15 +78,18 @@ public final class BluetoothPan implements BluetoothProfile { */ public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; + public static final int PAN_ROLE_NONE = 0; /** * The local device is acting as a Network Access Point. */ public static final int LOCAL_NAP_ROLE = 1; + public static final int REMOTE_NAP_ROLE = 1; /** * The local device is acting as a PAN User. */ public static final int LOCAL_PANU_ROLE = 2; + public static final int REMOTE_PANU_ROLE = 2; /** * Return codes for the connect and disconnect Bluez / Dbus calls. @@ -112,37 +117,77 @@ public final class BluetoothPan implements BluetoothProfile { */ public static final int PAN_OPERATION_SUCCESS = 1004; + private Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; - private IBluetooth mService; + private IBluetoothPan mPanService; /** * Create a BluetoothPan proxy object for interacting with the local * Bluetooth Service which handles the Pan profile * */ - /*package*/ BluetoothPan(Context mContext, ServiceListener l) { - IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); + /*package*/ BluetoothPan(Context context, ServiceListener l) { + mContext = context; mServiceListener = l; mAdapter = BluetoothAdapter.getDefaultAdapter(); - if (b != null) { - mService = IBluetooth.Stub.asInterface(b); - if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.PAN, this); - } - } else { - Log.w(TAG, "Bluetooth Service not available!"); - - // Instead of throwing an exception which prevents people from going - // into Wireless settings in the emulator. Let it crash later when it is actually used. - mService = null; + try { + mAdapter.getBluetoothManager().registerStateChangeCallback(mStateChangeCallback); + } catch (RemoteException re) { + Log.w(TAG,"Unable to register BluetoothStateChangeCallback",re); + } + Log.d(TAG, "BluetoothPan() call bindService"); + if (!context.bindService(new Intent(IBluetoothPan.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth HID Service"); } + Log.d(TAG, "BluetoothPan(), bindService called"); } /*package*/ void close() { + if (DBG) log("close()"); + if (mConnection != null) { + mContext.unbindService(mConnection); + mConnection = null; + } mServiceListener = null; + try { + mAdapter.getBluetoothManager().unregisterStateChangeCallback(mStateChangeCallback); + } catch (RemoteException re) { + Log.w(TAG,"Unable to register BluetoothStateChangeCallback",re); + } } + protected void finalize() { + close(); + } + + private IBluetoothStateChangeCallback mStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { + + @Override + public void onBluetoothStateChange(boolean on) throws RemoteException { + //Handle enable request to bind again. + if (on) { + Log.d(TAG, "onBluetoothStateChange(on) call bindService"); + if (!mContext.bindService(new Intent(IBluetoothPan.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth HID Service"); + } + Log.d(TAG, "BluetoothPan(), bindService called"); + } else { + if (DBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mPanService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + /** * Initiate connection to a profile of the remote bluetooth device. * @@ -163,16 +208,16 @@ public final class BluetoothPan implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); - if (mService != null && isEnabled() && + if (mPanService != null && isEnabled() && isValidDevice(device)) { try { - return mService.connectPanDevice(device); + return mPanService.connect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -204,16 +249,16 @@ public final class BluetoothPan implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mService != null && isEnabled() && + if (mPanService != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnectPanDevice(device); + return mPanService.disconnect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -222,15 +267,15 @@ public final class BluetoothPan implements BluetoothProfile { */ public List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); - if (mService != null && isEnabled()) { + if (mPanService != null && isEnabled()) { try { - return mService.getConnectedPanDevices(); + return mPanService.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -239,15 +284,15 @@ public final class BluetoothPan implements BluetoothProfile { */ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + if (mPanService != null && isEnabled()) { try { - return mService.getPanDevicesMatchingConnectionStates(states); + return mPanService.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -256,23 +301,23 @@ public final class BluetoothPan implements BluetoothProfile { */ public int getConnectionState(BluetoothDevice device) { if (DBG) log("getState(" + device + ")"); - if (mService != null && isEnabled() + if (mPanService != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPanDeviceConnectionState(device); + return mPanService.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } public void setBluetoothTethering(boolean value) { if (DBG) log("setBluetoothTethering(" + value + ")"); try { - mService.setBluetoothTethering(value); + mPanService.setBluetoothTethering(value); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); } @@ -281,13 +326,32 @@ public final class BluetoothPan implements BluetoothProfile { public boolean isTetheringOn() { if (DBG) log("isTetheringOn()"); try { - return mService.isTetheringOn(); + return mPanService.isTetheringOn(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); - return false; } + return false; } + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "BluetoothPAN Proxy object connected"); + mPanService = IBluetoothPan.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.PAN, + BluetoothPan.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "BluetoothPAN Proxy object disconnected"); + mPanService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.PAN); + } + } + }; + private boolean isEnabled() { if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; return false; diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index 639ae1a..7de2ef6 100644..100755 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -70,6 +70,7 @@ public class BluetoothPbap { private IBluetoothPbap mService; private final Context mContext; private ServiceListener mServiceListener; + private BluetoothAdapter mAdapter; /** There was an error trying to obtain the state */ public static final int STATE_ERROR = -1; @@ -96,7 +97,7 @@ public class BluetoothPbap { * this callback before making IPC calls on the BluetoothPbap * service. */ - public void onServiceConnected(); + public void onServiceConnected(BluetoothPbap proxy); /** * Called to notify the client that this proxy object has been @@ -108,12 +109,54 @@ public class BluetoothPbap { public void onServiceDisconnected(); } + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (DBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (DBG) Log.d(TAG,"Binding service..."); + if (!mContext.bindService( + new Intent(IBluetoothPbap.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth PBAP Service"); + } + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + /** * Create a BluetoothPbap proxy object. */ public BluetoothPbap(Context context, ServiceListener l) { mContext = context; mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } if (!context.bindService(new Intent(IBluetoothPbap.class.getName()), mConnection, 0)) { Log.e(TAG, "Could not bind to Bluetooth Pbap Service"); } @@ -134,9 +177,25 @@ public class BluetoothPbap { * are ok. */ public synchronized void close() { - if (mConnection != null) { - mContext.unbindService(mConnection); - mConnection = null; + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + mConnection = null; + } catch (Exception re) { + Log.e(TAG,"",re); + } + } } mServiceListener = null; } @@ -240,7 +299,7 @@ public class BluetoothPbap { if (DBG) log("Proxy object connected"); mService = IBluetoothPbap.Stub.asInterface(service); if (mServiceListener != null) { - mServiceListener.onServiceConnected(); + mServiceListener.onServiceConnected(BluetoothPbap.this); } } public void onServiceDisconnected(ComponentName className) { diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 1920efa..1920efa 100644..100755 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java deleted file mode 100644 index b0c0a0b..0000000 --- a/core/java/android/bluetooth/BluetoothProfileState.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.bluetooth; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Message; -import android.util.Log; - -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; - -/** - * This state machine is used to serialize the connections - * to a particular profile. Currently, we only allow one device - * to be connected to a particular profile. - * States: - * {@link StableState} : No pending commands. Send the - * command to the appropriate remote device specific state machine. - * - * {@link PendingCommandState} : A profile connection / disconnection - * command is being executed. This will result in a profile state - * change. Defer all commands. - * @hide - */ - -public class BluetoothProfileState extends StateMachine { - private static final boolean DBG = true; - private static final String TAG = "BluetoothProfileState"; - - public static final int HFP = 0; - public static final int A2DP = 1; - public static final int HID = 2; - - static final int TRANSITION_TO_STABLE = 100; - - private int mProfile; - private BluetoothDevice mPendingDevice; - private PendingCommandState mPendingCommandState = new PendingCommandState(); - private StableState mStableState = new StableState(); - - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device == null) { - return; - } - if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - if (mProfile == HFP && (newState == BluetoothProfile.STATE_CONNECTED || - newState == BluetoothProfile.STATE_DISCONNECTED)) { - sendMessage(TRANSITION_TO_STABLE); - } - } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - if (mProfile == A2DP && (newState == BluetoothProfile.STATE_CONNECTED || - newState == BluetoothProfile.STATE_DISCONNECTED)) { - sendMessage(TRANSITION_TO_STABLE); - } - } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - if (mProfile == HID && (newState == BluetoothProfile.STATE_CONNECTED || - newState == BluetoothProfile.STATE_DISCONNECTED)) { - sendMessage(TRANSITION_TO_STABLE); - } - } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { - if (device.equals(mPendingDevice)) { - sendMessage(TRANSITION_TO_STABLE); - } - } - } - }; - - public BluetoothProfileState(Context context, int profile) { - super("BluetoothProfileState:" + profile); - mProfile = profile; - addState(mStableState); - addState(mPendingCommandState); - setInitialState(mStableState); - - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); - context.registerReceiver(mBroadcastReceiver, filter); - } - - private class StableState extends State { - @Override - public void enter() { - log("Entering Stable State"); - mPendingDevice = null; - } - - @Override - public boolean processMessage(Message msg) { - if (msg.what != TRANSITION_TO_STABLE) { - transitionTo(mPendingCommandState); - } - return true; - } - } - - private class PendingCommandState extends State { - @Override - public void enter() { - log("Entering PendingCommandState State"); - dispatchMessage(getCurrentMessage()); - } - - @Override - public boolean processMessage(Message msg) { - if (msg.what == TRANSITION_TO_STABLE) { - transitionTo(mStableState); - } else { - dispatchMessage(msg); - } - return true; - } - - private void dispatchMessage(Message msg) { - BluetoothDeviceProfileState deviceProfileMgr = - (BluetoothDeviceProfileState)msg.obj; - int cmd = msg.arg1; - if (mPendingDevice == null || mPendingDevice.equals(deviceProfileMgr.getDevice())) { - mPendingDevice = deviceProfileMgr.getDevice(); - deviceProfileMgr.sendMessage(cmd); - } else { - Message deferMsg = new Message(); - deferMsg.arg1 = cmd; - deferMsg.obj = deviceProfileMgr; - deferMessage(deferMsg); - } - } - } - - private void log(String message) { - if (DBG) { - Log.i(TAG, "Message:" + message); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index 4021f7b..96be8a2 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -17,6 +17,8 @@ package android.bluetooth; import android.os.Handler; +import android.os.Message; +import android.os.ParcelUuid; import java.io.Closeable; import java.io.IOException; @@ -86,6 +88,22 @@ public final class BluetoothServerSocket implements Closeable { } /** + * Construct a socket for incoming connections. + * @param type type of socket + * @param auth require the remote device to be authenticated + * @param encrypt require the connection to be encrypted + * @param uuid uuid + * @throws IOException On error, for example Bluetooth not available, or + * insufficient privileges + */ + /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid) + throws IOException { + mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid); + mChannel = mSocket.getPort(); + } + + + /** * Block until a connection is established. * <p>Returns a connected {@link BluetoothSocket} on successful connection. * <p>Once this call returns, it can be called again to accept subsequent @@ -133,7 +151,9 @@ public final class BluetoothServerSocket implements Closeable { mHandler = handler; mMessage = message; } - + /*package*/ void setServiceName(String ServiceName) { + mSocket.setServiceName(ServiceName); + } /** * Returns the channel on which this socket is bound. * @hide diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 19d13ef..1bc640f 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -1,32 +1,27 @@ /* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (C) 2012 Google Inc. */ package android.bluetooth; -import android.bluetooth.IBluetoothCallback; +import android.os.IBinder; import android.os.ParcelUuid; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Log; import java.io.Closeable; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.concurrent.locks.ReentrantReadWriteLock; - +import java.util.List; +import android.net.LocalSocket; +import java.nio.ByteOrder; +import java.nio.ByteBuffer; /** * A connected or connecting Bluetooth socket. * @@ -89,31 +84,40 @@ public final class BluetoothSocket implements Closeable { /*package*/ static final int EBADFD = 77; /*package*/ static final int EADDRINUSE = 98; + /*package*/ static final int SEC_FLAG_ENCRYPT = 1; + /*package*/ static final int SEC_FLAG_AUTH = 1 << 1; + private final int mType; /* one of TYPE_RFCOMM etc */ - private final BluetoothDevice mDevice; /* remote device */ - private final String mAddress; /* remote address */ + private BluetoothDevice mDevice; /* remote device */ + private String mAddress; /* remote address */ private final boolean mAuth; private final boolean mEncrypt; private final BluetoothInputStream mInputStream; private final BluetoothOutputStream mOutputStream; - private final SdpHelper mSdp; - + private final ParcelUuid mUuid; + private ParcelFileDescriptor mPfd; + private LocalSocket mSocket; + private InputStream mSocketIS; + private OutputStream mSocketOS; private int mPort; /* RFCOMM channel or L2CAP psm */ + private int mFd; + private String mServiceName; + private static int PROXY_CONNECTION_TIMEOUT = 5000; + + private static int SOCK_SIGNAL_SIZE = 16; private enum SocketState { INIT, CONNECTED, - CLOSED + LISTENING, + CLOSED, } /** prevents all native calls after destroyNative() */ - private SocketState mSocketState; + private volatile SocketState mSocketState; /** protects mSocketState */ - private final ReentrantReadWriteLock mLock; - - /** used by native code only */ - private int mSocketData; + //private final ReentrantReadWriteLock mLock; /** * Construct a BluetoothSocket. @@ -134,33 +138,52 @@ public final class BluetoothSocket implements Closeable { throw new IOException("Invalid RFCOMM channel: " + port); } } - if (uuid == null) { - mPort = port; - mSdp = null; - } else { - mSdp = new SdpHelper(device, uuid); - mPort = -1; - } + mUuid = uuid; mType = type; mAuth = auth; mEncrypt = encrypt; mDevice = device; + mPort = port; + mFd = fd; + + mSocketState = SocketState.INIT; + if (device == null) { - mAddress = null; + // Server socket + mAddress = BluetoothAdapter.getDefaultAdapter().getAddress(); } else { + // Remote socket mAddress = device.getAddress(); } - if (fd == -1) { - initSocketNative(); - } else { - initSocketFromFdNative(fd); - } mInputStream = new BluetoothInputStream(this); mOutputStream = new BluetoothOutputStream(this); - mSocketState = SocketState.INIT; - mLock = new ReentrantReadWriteLock(); } - + private BluetoothSocket(BluetoothSocket s) { + mUuid = s.mUuid; + mType = s.mType; + mAuth = s.mAuth; + mEncrypt = s.mEncrypt; + mPort = s.mPort; + mInputStream = new BluetoothInputStream(this); + mOutputStream = new BluetoothOutputStream(this); + mServiceName = s.mServiceName; + } + private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException { + BluetoothSocket as = new BluetoothSocket(this); + as.mSocketState = SocketState.CONNECTED; + FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors(); + Log.d(TAG, "socket fd passed by stack fds: " + fds); + if(fds == null || fds.length != 1) { + Log.e(TAG, "socket fd passed from stack failed, fds: " + fds); + throw new IOException("bt socket acept failed"); + } + as.mSocket = new LocalSocket(fds[0]); + as.mSocketIS = as.mSocket.getInputStream(); + as.mSocketOS = as.mSocket.getOutputStream(); + as.mAddress = RemoteAddr; + as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(RemoteAddr); + return as; + } /** * Construct a BluetoothSocket from address. Used by native code. * @param type type of socket @@ -186,67 +209,13 @@ public final class BluetoothSocket implements Closeable { super.finalize(); } } - - /** - * Attempt to connect to a remote device. - * <p>This method will block until a connection is made or the connection - * fails. If this method returns without an exception then this socket - * is now connected. - * <p>Creating new connections to - * remote Bluetooth devices should not be attempted while device discovery - * is in progress. Device discovery is a heavyweight procedure on the - * Bluetooth adapter and will significantly slow a device connection. - * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing - * discovery. Discovery is not managed by the Activity, - * but is run as a system service, so an application should always call - * {@link BluetoothAdapter#cancelDiscovery()} even if it - * did not directly request a discovery, just to be sure. - * <p>{@link #close} can be used to abort this call from another thread. - * @throws IOException on error, for example connection failure - */ - public void connect() throws IOException { - mLock.readLock().lock(); - try { - if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - - if (mSdp != null) { - mPort = mSdp.doSdp(); // blocks - } - - connectNative(); // blocks - mSocketState = SocketState.CONNECTED; - } finally { - mLock.readLock().unlock(); - } - } - - /** - * Immediately close this socket, and release all associated resources. - * <p>Causes blocked calls on this socket in other threads to immediately - * throw an IOException. - */ - public void close() throws IOException { - // abort blocking operations on the socket - mLock.readLock().lock(); - try { - if (mSocketState == SocketState.CLOSED) return; - if (mSdp != null) { - mSdp.cancel(); - } - abortNative(); - } finally { - mLock.readLock().unlock(); - } - - // all native calls are guaranteed to immediately return after - // abortNative(), so this lock should immediately acquire - mLock.writeLock().lock(); - try { - mSocketState = SocketState.CLOSED; - destroyNative(); - } finally { - mLock.writeLock().unlock(); - } + private int getSecurityFlags() { + int flags = 0; + if(mAuth) + flags |= SEC_FLAG_AUTH; + if(mEncrypt) + flags |= SEC_FLAG_ENCRYPT; + return flags; } /** @@ -286,137 +255,235 @@ public final class BluetoothSocket implements Closeable { * false if not connected */ public boolean isConnected() { - return (mSocketState == SocketState.CONNECTED); + return mSocketState == SocketState.CONNECTED; + } + + /*package*/ void setServiceName(String name) { + mServiceName = name; } /** - * Currently returns unix errno instead of throwing IOException, - * so that BluetoothAdapter can check the error code for EADDRINUSE + * Attempt to connect to a remote device. + * <p>This method will block until a connection is made or the connection + * fails. If this method returns without an exception then this socket + * is now connected. + * <p>Creating new connections to + * remote Bluetooth devices should not be attempted while device discovery + * is in progress. Device discovery is a heavyweight procedure on the + * Bluetooth adapter and will significantly slow a device connection. + * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing + * discovery. Discovery is not managed by the Activity, + * but is run as a system service, so an application should always call + * {@link BluetoothAdapter#cancelDiscovery()} even if it + * did not directly request a discovery, just to be sure. + * <p>{@link #close} can be used to abort this call from another thread. + * @throws IOException on error, for example connection failure */ - /*package*/ int bindListen() { - mLock.readLock().lock(); - try { - if (mSocketState == SocketState.CLOSED) return EBADFD; - return bindListenNative(); - } finally { - mLock.readLock().unlock(); - } - } + public void connect() throws IOException { + if (mDevice == null) throw new IOException("Connect is called on null device"); - /*package*/ BluetoothSocket accept(int timeout) throws IOException { - mLock.readLock().lock(); try { + // TODO(BT) derive flag from auth and encrypt if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - - BluetoothSocket acceptedSocket = acceptNative(timeout); - mSocketState = SocketState.CONNECTED; - return acceptedSocket; - } finally { - mLock.readLock().unlock(); + IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); + if (bluetoothProxy == null) throw new IOException("Bluetooth is off"); + mPfd = bluetoothProxy.connectSocket(mDevice, mType, + mUuid, mPort, getSecurityFlags()); + synchronized(this) + { + Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd); + if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); + if (mPfd == null) throw new IOException("bt socket connect failed"); + FileDescriptor fd = mPfd.getFileDescriptor(); + mSocket = new LocalSocket(fd); + mSocketIS = mSocket.getInputStream(); + mSocketOS = mSocket.getOutputStream(); + } + int channel = readInt(mSocketIS); + if (channel <= 0) + throw new IOException("bt socket connect failed"); + mPort = channel; + waitSocketSignal(mSocketIS); + synchronized(this) + { + if (mSocketState == SocketState.CLOSED) + throw new IOException("bt socket closed"); + mSocketState = SocketState.CONNECTED; + } + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - /*package*/ int available() throws IOException { - mLock.readLock().lock(); + /** + * Currently returns unix errno instead of throwing IOException, + * so that BluetoothAdapter can check the error code for EADDRINUSE + */ + /*package*/ int bindListen() { + int ret; + if (mSocketState == SocketState.CLOSED) return EBADFD; + IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); + if (bluetoothProxy == null) { + Log.e(TAG, "bindListen fail, reason: bluetooth is off"); + return -1; + } try { - if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - return availableNative(); - } finally { - mLock.readLock().unlock(); + mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName, + mUuid, mPort, getSecurityFlags()); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + // TODO(BT) right error code? + return -1; } - } - /*package*/ int read(byte[] b, int offset, int length) throws IOException { - mLock.readLock().lock(); + // read out port number try { - if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - return readNative(b, offset, length); - } finally { - mLock.readLock().unlock(); + synchronized(this) { + Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " + mPfd); + if(mSocketState != SocketState.INIT) return EBADFD; + if(mPfd == null) return -1; + FileDescriptor fd = mPfd.getFileDescriptor(); + Log.d(TAG, "bindListen(), new LocalSocket "); + mSocket = new LocalSocket(fd); + Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() "); + mSocketIS = mSocket.getInputStream(); + mSocketOS = mSocket.getOutputStream(); + } + Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS); + int channel = readInt(mSocketIS); + synchronized(this) { + if(mSocketState == SocketState.INIT) + mSocketState = SocketState.LISTENING; + } + Log.d(TAG, "channel: " + channel); + if (mPort == -1) { + mPort = channel; + } // else ASSERT(mPort == channel) + ret = 0; + } catch (IOException e) { + Log.e(TAG, "bindListen, fail to get port number, exception: " + e); + return -1; } + return ret; } - /*package*/ int write(byte[] b, int offset, int length) throws IOException { - mLock.readLock().lock(); - try { - if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - return writeNative(b, offset, length); - } finally { - mLock.readLock().unlock(); + /*package*/ BluetoothSocket accept(int timeout) throws IOException { + BluetoothSocket acceptedSocket; + if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state"); + // TODO(BT) wait on an incoming connection + String RemoteAddr = waitSocketSignal(mSocketIS); + synchronized(this) + { + if (mSocketState != SocketState.LISTENING) + throw new IOException("bt socket is not in listen state"); + acceptedSocket = acceptSocket(RemoteAddr); + //quick drop the reference of the file handle } + // TODO(BT) rfcomm socket only supports one connection, return this? + // return this; + return acceptedSocket; } - private native void initSocketNative() throws IOException; - private native void initSocketFromFdNative(int fd) throws IOException; - private native void connectNative() throws IOException; - private native int bindListenNative(); - private native BluetoothSocket acceptNative(int timeout) throws IOException; - private native int availableNative() throws IOException; - private native int readNative(byte[] b, int offset, int length) throws IOException; - private native int writeNative(byte[] b, int offset, int length) throws IOException; - private native void abortNative() throws IOException; - private native void destroyNative() throws IOException; - /** - * Throws an IOException for given posix errno. Done natively so we can - * use strerr to convert to string error. - */ - /*package*/ native void throwErrnoNative(int errno) throws IOException; + /*package*/ int available() throws IOException { + Log.d(TAG, "available: " + mSocketIS); + return mSocketIS.available(); + } - /** - * Helper to perform blocking SDP lookup. - */ - private static class SdpHelper extends IBluetoothCallback.Stub { - private final IBluetooth service; - private final ParcelUuid uuid; - private final BluetoothDevice device; - private int channel; - private boolean canceled; - public SdpHelper(BluetoothDevice device, ParcelUuid uuid) { - service = BluetoothDevice.getService(); - this.device = device; - this.uuid = uuid; - canceled = false; - } - /** - * Returns the RFCOMM channel for the UUID, or throws IOException - * on failure. - */ - public synchronized int doSdp() throws IOException { - if (canceled) throw new IOException("Service discovery canceled"); - channel = -1; + /*package*/ int read(byte[] b, int offset, int length) throws IOException { - boolean inProgress = false; - try { - inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this); - } catch (RemoteException e) {Log.e(TAG, "", e);} + Log.d(TAG, "read in: " + mSocketIS + " len: " + length); + int ret = mSocketIS.read(b, offset, length); + if(ret < 0) + throw new IOException("bt socket closed, read return: " + ret); + Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret); + return ret; + } - if (!inProgress) throw new IOException("Unable to start Service Discovery"); + /*package*/ int write(byte[] b, int offset, int length) throws IOException { - try { - /* 12 second timeout as a precaution - onRfcommChannelFound - * should always occur before the timeout */ - wait(12000); // block + Log.d(TAG, "write: " + mSocketOS + " length: " + length); + mSocketOS.write(b, offset, length); + // There is no good way to confirm since the entire process is asynchronous anyway + Log.d(TAG, "write out: " + mSocketOS + " length: " + length); + return length; + } - } catch (InterruptedException e) {} + @Override + public void close() throws IOException { + Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState); + if(mSocketState == SocketState.CLOSED) + return; + else + { + synchronized(this) + { + if(mSocketState == SocketState.CLOSED) + return; + mSocketState = SocketState.CLOSED; + Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS + + ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket); + if(mSocket != null) { + Log.d(TAG, "Closing mSocket: " + mSocket); + mSocket.shutdownInput(); + mSocket.shutdownOutput(); + mSocket.close(); + mSocket = null; + } + if(mPfd != null) + mPfd.detachFd(); + } + } + // TODO(BT) unbind proxy, + } - if (canceled) throw new IOException("Service discovery canceled"); - if (channel < 1) throw new IOException("Service discovery failed"); + /*package */ void removeChannel() { + } - return channel; - } - /** Object cannot be re-used after calling cancel() */ - public synchronized void cancel() { - if (!canceled) { - canceled = true; - channel = -1; - notifyAll(); // unblock - } - } - public synchronized void onRfcommChannelFound(int channel) { - if (!canceled) { - this.channel = channel; - notifyAll(); // unblock - } + /*package */ int getPort() { + return mPort; + } + private String convertAddr(final byte[] addr) { + return String.format("%02X:%02X:%02X:%02X:%02X:%02X", + addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]); + } + private String waitSocketSignal(InputStream is) throws IOException { + byte [] sig = new byte[SOCK_SIGNAL_SIZE]; + int ret = readAll(is, sig); + Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret); + ByteBuffer bb = ByteBuffer.wrap(sig); + bb.order(ByteOrder.nativeOrder()); + int size = bb.getShort(); + byte [] addr = new byte[6]; + bb.get(addr); + int channel = bb.getInt(); + int status = bb.getInt(); + String RemoteAddr = convertAddr(addr); + Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: " + + RemoteAddr + ", channel: " + channel + ", status: " + status); + if(status != 0) + throw new IOException("Connection failure, status: " + status); + return RemoteAddr; + } + private int readAll(InputStream is, byte[] b) throws IOException { + int left = b.length; + while(left > 0) { + int ret = is.read(b, b.length - left, left); + if(ret <= 0) + throw new IOException("read failed, socket might closed, read ret: " + ret); + left -= ret; + if(left != 0) + Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) + + ", expect size: " + b.length); } + return b.length; + } + + private int readInt(InputStream is) throws IOException { + byte[] ibytes = new byte[4]; + int ret = readAll(is, ibytes); + Log.d(TAG, "inputStream.read ret: " + ret); + ByteBuffer bb = ByteBuffer.wrap(ibytes); + bb.order(ByteOrder.nativeOrder()); + return bb.getInt(); } } diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java index 83d1bda..30406e9 100644 --- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java +++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java @@ -16,6 +16,9 @@ package android.bluetooth; +import android.os.IBinder; +import android.os.ServiceManager; +import android.os.INetworkManagementService; import android.content.Context; import android.net.ConnectivityManager; import android.net.DhcpInfoInternal; @@ -28,6 +31,11 @@ import android.net.NetworkUtils; import android.os.Handler; import android.os.Message; import android.util.Log; +import java.net.InterfaceAddress; +import android.net.LinkAddress; +import android.net.RouteInfo; +import java.net.Inet4Address; +import android.os.SystemProperties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -54,9 +62,8 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker { private NetworkInfo mNetworkInfo; private BluetoothPan mBluetoothPan; - private BluetoothDevice mDevice; private static String mIface; - + private Thread mDhcpThread; /* For sending events to connectivity service handler */ private Handler mCsHandler; private Context mContext; @@ -92,8 +99,10 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker { * Begin monitoring connectivity */ public void startMonitoring(Context context, Handler target) { + Log.d(TAG, "startMonitoring: target: " + target); mContext = context; mCsHandler = target; + Log.d(TAG, "startMonitoring: mCsHandler: " + mCsHandler); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null) { adapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.PAN); @@ -124,6 +133,11 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker { return true; } + @Override + public void captivePortalCheckComplete() { + // not implemented + } + /** * Re-enable connectivity to a network after a {@link #teardown()}. */ @@ -259,38 +273,91 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker { return "net.tcp.buffersize.wifi"; } + private static short countPrefixLength(byte [] mask) { + short count = 0; + for (byte b : mask) { + for (int i = 0; i < 8; ++i) { + if ((b & (1 << i)) != 0) { + ++count; + } + } + } + return count; + } + - public synchronized void startReverseTether(String iface, BluetoothDevice device) { + private boolean readLinkProperty(String iface) { + String DhcpPrefix = "dhcp." + iface + "."; + String ip = SystemProperties.get(DhcpPrefix + "ipaddress"); + String dns1 = SystemProperties.get(DhcpPrefix + "dns1"); + String dns2 = SystemProperties.get(DhcpPrefix + "dns2"); + String gateway = SystemProperties.get(DhcpPrefix + "gateway"); + String mask = SystemProperties.get(DhcpPrefix + "mask"); + if(ip.isEmpty() || gateway.isEmpty()) { + Log.e(TAG, "readLinkProperty, ip: " + ip + ", gateway: " + gateway + ", can not be empty"); + return false; + } + int PrefixLen = countPrefixLength(NetworkUtils.numericToInetAddress(mask).getAddress()); + mLinkProperties.addLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(ip), PrefixLen)); + RouteInfo ri = new RouteInfo(NetworkUtils.numericToInetAddress(gateway)); + mLinkProperties.addRoute(ri); + if(!dns1.isEmpty()) + mLinkProperties.addDns(NetworkUtils.numericToInetAddress(dns1)); + if(!dns2.isEmpty()) + mLinkProperties.addDns(NetworkUtils.numericToInetAddress(dns2)); + mLinkProperties.setInterfaceName(iface); + return true; + } + public synchronized void startReverseTether(String iface) { mIface = iface; - mDevice = device; - Thread dhcpThread = new Thread(new Runnable() { + Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler); + mDhcpThread = new Thread(new Runnable() { public void run() { //TODO(): Add callbacks for failure and success case. //Currently this thread runs independently. - DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal(); - if (!NetworkUtils.runDhcp(mIface, dhcpInfoInternal)) { - Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError()); - return; + Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler); + String DhcpResultName = "dhcp." + mIface + ".result";; + String result = ""; + Log.d(TAG, "waiting for change of sys prop dhcp result: " + DhcpResultName); + for(int i = 0; i < 30*5; i++) { + try { Thread.sleep(200); } catch (InterruptedException ie) { return;} + result = SystemProperties.get(DhcpResultName); + Log.d(TAG, "read " + DhcpResultName + ": " + result); + if(result.equals("failed")) { + Log.e(TAG, "startReverseTether, failed to start dhcp service"); + return; + } + if(result.equals("ok")) { + Log.d(TAG, "startReverseTether, dhcp resut: " + result); + if(readLinkProperty(mIface)) { + + mNetworkInfo.setIsAvailable(true); + mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); + + Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler); + if(mCsHandler != null) { + Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo); + msg.sendToTarget(); + + msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo); + msg.sendToTarget(); + } + } + return; + } } - mLinkProperties = dhcpInfoInternal.makeLinkProperties(); - mLinkProperties.setInterfaceName(mIface); - - mNetworkInfo.setIsAvailable(true); - mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); - - Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo); - msg.sendToTarget(); - - msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo); - msg.sendToTarget(); + Log.d(TAG, "startReverseTether, dhcp failed, resut: " + result); } }); - dhcpThread.start(); + mDhcpThread.start(); } - public synchronized void stopReverseTether(String iface) { - NetworkUtils.stopDhcp(iface); - + public synchronized void stopReverseTether() { + //NetworkUtils.stopDhcp(iface); + if(mDhcpThread != null && mDhcpThread.isAlive()) { + mDhcpThread.interrupt(); + try { mDhcpThread.join(); } catch (InterruptedException ie) { return; } + } mLinkProperties.clear(); mNetworkInfo.setIsAvailable(false); mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null); diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java deleted file mode 100644 index 9ef2eb5..0000000 --- a/core/java/android/bluetooth/HeadsetBase.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.os.Handler; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.util.Log; - -/** - * The Android Bluetooth API is not finalized, and *will* change. Use at your - * own risk. - * - * The base RFCOMM (service) connection for a headset or handsfree device. - * - * In the future this class will be removed. - * - * @hide - */ -public final class HeadsetBase { - private static final String TAG = "Bluetooth HeadsetBase"; - private static final boolean DBG = false; - - public static final int RFCOMM_DISCONNECTED = 1; - - public static final int DIRECTION_INCOMING = 1; - public static final int DIRECTION_OUTGOING = 2; - - private static int sAtInputCount = 0; /* TODO: Consider not using a static variable */ - - private final BluetoothAdapter mAdapter; - private final BluetoothDevice mRemoteDevice; - private final String mAddress; // for native code - private final int mRfcommChannel; - private int mNativeData; - private Thread mEventThread; - private volatile boolean mEventThreadInterrupted; - private Handler mEventThreadHandler; - private int mTimeoutRemainingMs; - private final int mDirection; - private final long mConnectTimestamp; - - protected AtParser mAtParser; - - private WakeLock mWakeLock; // held while processing an AT command - - private native static void classInitNative(); - static { - classInitNative(); - } - - protected void finalize() throws Throwable { - try { - cleanupNativeDataNative(); - releaseWakeLock(); - } finally { - super.finalize(); - } - } - - private native void cleanupNativeDataNative(); - - public HeadsetBase(PowerManager pm, BluetoothAdapter adapter, - BluetoothDevice device, int rfcommChannel) { - mDirection = DIRECTION_OUTGOING; - mConnectTimestamp = System.currentTimeMillis(); - mAdapter = adapter; - mRemoteDevice = device; - mAddress = device.getAddress(); - mRfcommChannel = rfcommChannel; - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase"); - mWakeLock.setReferenceCounted(false); - initializeAtParser(); - // Must be called after this.mAddress is set. - initializeNativeDataNative(-1); - } - - /* Create from an existing rfcomm connection */ - public HeadsetBase(PowerManager pm, BluetoothAdapter adapter, - BluetoothDevice device, - int socketFd, int rfcommChannel, Handler handler) { - mDirection = DIRECTION_INCOMING; - mConnectTimestamp = System.currentTimeMillis(); - mAdapter = adapter; - mRemoteDevice = device; - mAddress = device.getAddress(); - mRfcommChannel = rfcommChannel; - mEventThreadHandler = handler; - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase"); - mWakeLock.setReferenceCounted(false); - initializeAtParser(); - // Must be called after this.mAddress is set. - initializeNativeDataNative(socketFd); - } - - private native void initializeNativeDataNative(int socketFd); - - /* Process an incoming AT command line - */ - protected void handleInput(String input) { - acquireWakeLock(); - long timestamp; - - synchronized(HeadsetBase.class) { - if (sAtInputCount == Integer.MAX_VALUE) { - sAtInputCount = 0; - } else { - sAtInputCount++; - } - } - - if (DBG) timestamp = System.currentTimeMillis(); - AtCommandResult result = mAtParser.process(input); - if (DBG) Log.d(TAG, "Processing " + input + " took " + - (System.currentTimeMillis() - timestamp) + " ms"); - - if (result.getResultCode() == AtCommandResult.ERROR) { - Log.i(TAG, "Error processing <" + input + ">"); - } - - sendURC(result.toString()); - - releaseWakeLock(); - } - - /** - * Register AT commands that are common to all Headset / Handsets. This - * function is called by the HeadsetBase constructor. - */ - protected void initializeAtParser() { - mAtParser = new AtParser(); - - //TODO(): Get rid of this as there are no parsers registered. But because of dependencies - // it needs to be done as part of refactoring HeadsetBase and BluetoothHandsfree - } - - public AtParser getAtParser() { - return mAtParser; - } - - public void startEventThread() { - mEventThread = - new Thread("HeadsetBase Event Thread") { - public void run() { - int last_read_error; - while (!mEventThreadInterrupted) { - String input = readNative(500); - if (input != null) { - handleInput(input); - } else { - last_read_error = getLastReadStatusNative(); - if (last_read_error != 0) { - Log.i(TAG, "headset read error " + last_read_error); - if (mEventThreadHandler != null) { - mEventThreadHandler.obtainMessage(RFCOMM_DISCONNECTED) - .sendToTarget(); - } - disconnectNative(); - break; - } - } - } - } - }; - mEventThreadInterrupted = false; - mEventThread.start(); - } - - private native String readNative(int timeout_ms); - private native int getLastReadStatusNative(); - - private void stopEventThread() { - mEventThreadInterrupted = true; - mEventThread.interrupt(); - try { - mEventThread.join(); - } catch (java.lang.InterruptedException e) { - // FIXME: handle this, - } - mEventThread = null; - } - - public boolean connect(Handler handler) { - if (mEventThread == null) { - if (!connectNative()) return false; - mEventThreadHandler = handler; - } - return true; - } - private native boolean connectNative(); - - /* - * Returns true when either the asynchronous connect is in progress, or - * the connect is complete. Call waitForAsyncConnect() to find out whether - * the connect is actually complete, or disconnect() to cancel. - */ - - public boolean connectAsync() { - int ret = connectAsyncNative(); - return (ret == 0) ? true : false; - } - private native int connectAsyncNative(); - - public int getRemainingAsyncConnectWaitingTimeMs() { - return mTimeoutRemainingMs; - } - - /* - * Returns 1 when an async connect is complete, 0 on timeout, and -1 on - * error. On error, handler will be called, and you need to re-initiate - * the async connect. - */ - public int waitForAsyncConnect(int timeout_ms, Handler handler) { - int res = waitForAsyncConnectNative(timeout_ms); - if (res > 0) { - mEventThreadHandler = handler; - } - return res; - } - private native int waitForAsyncConnectNative(int timeout_ms); - - public void disconnect() { - if (mEventThread != null) { - stopEventThread(); - } - disconnectNative(); - } - private native void disconnectNative(); - - - /* - * Note that if a remote side disconnects, this method will still return - * true until disconnect() is called. You know when a remote side - * disconnects because you will receive the intent - * IBluetoothService.REMOTE_DEVICE_DISCONNECTED_ACTION. If, when you get - * this intent, method isConnected() returns true, you know that the - * disconnect was initiated by the remote device. - */ - - public boolean isConnected() { - return mEventThread != null; - } - - public BluetoothDevice getRemoteDevice() { - return mRemoteDevice; - } - - public int getDirection() { - return mDirection; - } - - public long getConnectTimestamp() { - return mConnectTimestamp; - } - - public synchronized boolean sendURC(String urc) { - if (urc.length() > 0) { - boolean ret = sendURCNative(urc); - return ret; - } - return true; - } - private native boolean sendURCNative(String urc); - - private synchronized void acquireWakeLock() { - if (!mWakeLock.isHeld()) { - mWakeLock.acquire(); - } - } - - private synchronized void releaseWakeLock() { - if (mWakeLock.isHeld()) { - mWakeLock.release(); - } - } - - public static int getAtInputCount() { - return sAtInputCount; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 6075363..d016c26 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -18,9 +18,7 @@ package android.bluetooth; import android.bluetooth.IBluetoothCallback; import android.bluetooth.IBluetoothStateChangeCallback; -import android.bluetooth.IBluetoothHealthCallback; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHealthAppConfiguration; import android.os.ParcelUuid; import android.os.ParcelFileDescriptor; @@ -32,15 +30,15 @@ import android.os.ParcelFileDescriptor; interface IBluetooth { boolean isEnabled(); - int getBluetoothState(); + int getState(); boolean enable(); boolean enableNoAutoConnect(); - boolean disable(boolean persistSetting); + boolean disable(); String getAddress(); - String getName(); - boolean setName(in String name); ParcelUuid[] getUuids(); + boolean setName(in String name); + String getName(); int getScanMode(); boolean setScanMode(int mode, int duration); @@ -51,77 +49,34 @@ interface IBluetooth boolean startDiscovery(); boolean cancelDiscovery(); boolean isDiscovering(); - byte[] readOutOfBandData(); int getAdapterConnectionState(); int getProfileConnectionState(int profile); - boolean changeApplicationBluetoothState(boolean on, - in IBluetoothStateChangeCallback callback, in - IBinder b); - - boolean createBond(in String address); - boolean createBondOutOfBand(in String address, in byte[] hash, in byte[] randomizer); - boolean cancelBondProcess(in String address); - boolean removeBond(in String address); - String[] listBonds(); - int getBondState(in String address); - boolean setDeviceOutOfBandData(in String address, in byte[] hash, in byte[] randomizer); - String getRemoteName(in String address); - String getRemoteAlias(in String address); - boolean setRemoteAlias(in String address, in String name); - int getRemoteClass(in String address); - ParcelUuid[] getRemoteUuids(in String address); - boolean fetchRemoteUuids(in String address, in ParcelUuid uuid, in IBluetoothCallback callback); - int getRemoteServiceChannel(in String address, in ParcelUuid uuid); + BluetoothDevice[] getBondedDevices(); + boolean createBond(in BluetoothDevice device); + boolean cancelBondProcess(in BluetoothDevice device); + boolean removeBond(in BluetoothDevice device); + int getBondState(in BluetoothDevice device); - boolean setPin(in String address, in byte[] pin); - boolean setPasskey(in String address, int passkey); - boolean setPairingConfirmation(in String address, boolean confirm); - boolean setRemoteOutOfBandData(in String addres); - boolean cancelPairingUserInput(in String address); + String getRemoteName(in BluetoothDevice device); + String getRemoteAlias(in BluetoothDevice device); + boolean setRemoteAlias(in BluetoothDevice device, in String name); + int getRemoteClass(in BluetoothDevice device); + ParcelUuid[] getRemoteUuids(in BluetoothDevice device); + boolean fetchRemoteUuids(in BluetoothDevice device); - boolean setTrust(in String address, in boolean value); - boolean getTrustState(in String address); - boolean isBluetoothDock(in String address); + boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode); + boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[] + passkey); + boolean setPairingConfirmation(in BluetoothDevice device, boolean accept); - int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b); - void removeServiceRecord(int handle); - boolean allowIncomingProfileConnect(in BluetoothDevice device, boolean value); - - boolean connectHeadset(String address); - boolean disconnectHeadset(String address); - boolean notifyIncomingConnection(String address, boolean rejected); - - // HID profile APIs - boolean connectInputDevice(in BluetoothDevice device); - boolean disconnectInputDevice(in BluetoothDevice device); - List<BluetoothDevice> getConnectedInputDevices(); - List<BluetoothDevice> getInputDevicesMatchingConnectionStates(in int[] states); - int getInputDeviceConnectionState(in BluetoothDevice device); - boolean setInputDevicePriority(in BluetoothDevice device, int priority); - int getInputDevicePriority(in BluetoothDevice device); - - boolean isTetheringOn(); - void setBluetoothTethering(boolean value); - int getPanDeviceConnectionState(in BluetoothDevice device); - List<BluetoothDevice> getConnectedPanDevices(); - List<BluetoothDevice> getPanDevicesMatchingConnectionStates(in int[] states); - boolean connectPanDevice(in BluetoothDevice device); - boolean disconnectPanDevice(in BluetoothDevice device); + void sendConnectionStateChange(in BluetoothDevice device, int profile, int state, int prevState); - // HDP profile APIs - boolean registerAppConfiguration(in BluetoothHealthAppConfiguration config, - in IBluetoothHealthCallback callback); - boolean unregisterAppConfiguration(in BluetoothHealthAppConfiguration config); - boolean connectChannelToSource(in BluetoothDevice device, in BluetoothHealthAppConfiguration config); - boolean connectChannelToSink(in BluetoothDevice device, in BluetoothHealthAppConfiguration config, - int channelType); - boolean disconnectChannel(in BluetoothDevice device, in BluetoothHealthAppConfiguration config, int id); - ParcelFileDescriptor getMainChannelFd(in BluetoothDevice device, in BluetoothHealthAppConfiguration config); - List<BluetoothDevice> getConnectedHealthDevices(); - List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(in int[] states); - int getHealthDeviceConnectionState(in BluetoothDevice device); + void registerCallback(in IBluetoothCallback callback); + void unregisterCallback(in IBluetoothCallback callback); - void sendConnectionStateChange(in BluetoothDevice device, int profile, int state, int prevState); + // For Socket + ParcelFileDescriptor connectSocket(in BluetoothDevice device, int type, in ParcelUuid uuid, int port, int flag); + ParcelFileDescriptor createSocketChannel(int type, in String serviceName, in ParcelUuid uuid, int port, int flag); } diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 444dd1e..1f10998 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -33,12 +33,4 @@ interface IBluetoothA2dp { boolean setPriority(in BluetoothDevice device, int priority); int getPriority(in BluetoothDevice device); boolean isA2dpPlaying(in BluetoothDevice device); - - // Internal APIs - boolean suspendSink(in BluetoothDevice device); - boolean resumeSink(in BluetoothDevice device); - boolean connectSinkInternal(in BluetoothDevice device); - boolean disconnectSinkInternal(in BluetoothDevice device); - boolean allowIncomingConnect(in BluetoothDevice device, boolean value); - } diff --git a/core/java/android/bluetooth/IBluetoothCallback.aidl b/core/java/android/bluetooth/IBluetoothCallback.aidl index 8edb3f4..e280978 100644 --- a/core/java/android/bluetooth/IBluetoothCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothCallback.aidl @@ -23,5 +23,6 @@ package android.bluetooth; */ interface IBluetoothCallback { - void onRfcommChannelFound(int channel); + //void onRfcommChannelFound(int channel); + void onBluetoothStateChange(int prevState, int newState); } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index ec00527..fc7627a 100644 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -40,15 +40,17 @@ interface IBluetoothHeadset { int getBatteryUsageHint(in BluetoothDevice device); // Internal functions, not be made public - boolean createIncomingConnect(in BluetoothDevice device); boolean acceptIncomingConnect(in BluetoothDevice device); boolean rejectIncomingConnect(in BluetoothDevice device); - boolean cancelConnectThread(); - boolean connectHeadsetInternal(in BluetoothDevice device); - boolean disconnectHeadsetInternal(in BluetoothDevice device); - boolean setAudioState(in BluetoothDevice device, int state); int getAudioState(in BluetoothDevice device); + boolean isAudioOn(); + boolean connectAudio(); + boolean disconnectAudio(); boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device); boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device); + void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type); + void roamChanged(boolean roam); + void clccResponse(int index, int direction, int status, int mode, boolean mpty, + String number, int type); } diff --git a/core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl b/core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl new file mode 100644 index 0000000..163e4e2 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2012 Google Inc. + */ + +package android.bluetooth; + +/** + * API for Bluetooth Headset Phone Service in phone app + * + * {@hide} + */ +interface IBluetoothHeadsetPhone { + // Internal functions, not be made public + boolean answerCall(); + boolean hangupCall(); + boolean sendDtmf(int dtmf); + boolean processChld(int chld); + String getNetworkOperator(); + String getSubscriberNumber(); + boolean listCurrentCalls(); + boolean queryPhoneState(); + + // Internal for phone app to call + void updateBtHandsfreeAfterRadioTechnologyChange(); + void cdmaSwapSecondCallState(); + void cdmaSetSecondCallState(boolean state); +} diff --git a/core/java/android/bluetooth/IBluetoothHealth.aidl b/core/java/android/bluetooth/IBluetoothHealth.aidl new file mode 100644 index 0000000..e741da4 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothHealth.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 Google Inc. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHealthAppConfiguration; +import android.bluetooth.IBluetoothHealthCallback; +import android.os.ParcelFileDescriptor; + +/** + * API for Bluetooth Health service + * + * {@hide} + */ +interface IBluetoothHealth +{ + boolean registerAppConfiguration(in BluetoothHealthAppConfiguration config, + in IBluetoothHealthCallback callback); + boolean unregisterAppConfiguration(in BluetoothHealthAppConfiguration config); + boolean connectChannelToSource(in BluetoothDevice device, in BluetoothHealthAppConfiguration config); + boolean connectChannelToSink(in BluetoothDevice device, in BluetoothHealthAppConfiguration config, + int channelType); + boolean disconnectChannel(in BluetoothDevice device, in BluetoothHealthAppConfiguration config, int id); + ParcelFileDescriptor getMainChannelFd(in BluetoothDevice device, in BluetoothHealthAppConfiguration config); + List<BluetoothDevice> getConnectedHealthDevices(); + List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(in int[] states); + int getHealthDeviceConnectionState(in BluetoothDevice device); +} diff --git a/core/java/android/bluetooth/IBluetoothInputDevice.aidl b/core/java/android/bluetooth/IBluetoothInputDevice.aidl new file mode 100755 index 0000000..23e6d50 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothInputDevice.aidl @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 Google Inc. + */ +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * API for Bluetooth HID service + * + * {@hide} + */ +interface IBluetoothInputDevice { + // Public API + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + List<BluetoothDevice> getConnectedDevices(); + List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setPriority(in BluetoothDevice device, int priority); + int getPriority(in BluetoothDevice device); + /** + * @hide + */ + boolean getProtocolMode(in BluetoothDevice device); + /** + * @hide + */ + boolean virtualUnplug(in BluetoothDevice device); + /** + * @hide + */ + boolean setProtocolMode(in BluetoothDevice device, int protocolMode); + /** + * @hide + */ + boolean getReport(in BluetoothDevice device, byte reportType, byte reportId, int bufferSize); + /** + * @hide + */ + boolean setReport(in BluetoothDevice device, byte reportType, String report); + /** + * @hide + */ + boolean sendData(in BluetoothDevice device, String report); +} diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl new file mode 100755 index 0000000..de8fe91 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothManager.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 Google Inc. + */ + +package android.bluetooth; + +import android.bluetooth.IBluetooth; +import android.bluetooth.IBluetoothManagerCallback; +import android.bluetooth.IBluetoothStateChangeCallback; + +/** + * System private API for talking with the Bluetooth service. + * + * {@hide} + */ +interface IBluetoothManager +{ + IBluetooth registerAdapter(in IBluetoothManagerCallback callback); + void unregisterAdapter(in IBluetoothManagerCallback callback); + void registerStateChangeCallback(in IBluetoothStateChangeCallback callback); + void unregisterStateChangeCallback(in IBluetoothStateChangeCallback callback); + boolean isEnabled(); + boolean enable(); + boolean enableNoAutoConnect(); + boolean disable(boolean persist); + + String getAddress(); + String getName(); +} diff --git a/core/java/android/bluetooth/IBluetoothManagerCallback.aidl b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl new file mode 100644 index 0000000..3e795ea --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2012 Google Inc. + */ + +package android.bluetooth; + +import android.bluetooth.IBluetooth; + +/** + * API for Communication between BluetoothAdapter and BluetoothManager + * + * {@hide} + */ +interface IBluetoothManagerCallback { + void onBluetoothServiceUp(in IBluetooth bluetoothService); + void onBluetoothServiceDown(); +}
\ No newline at end of file diff --git a/core/java/android/bluetooth/IBluetoothPan.aidl b/core/java/android/bluetooth/IBluetoothPan.aidl new file mode 100644 index 0000000..b91bd7d --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothPan.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 Google Inc. + */ +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * API for Bluetooth Pan service + * + * {@hide} + */ +interface IBluetoothPan { + // Public API + boolean isTetheringOn(); + void setBluetoothTethering(boolean value); + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + List<BluetoothDevice> getConnectedDevices(); + List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); +} diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 446f1af..1500b00 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -237,16 +237,17 @@ public abstract class BroadcastReceiver { final boolean mOrderedHint; final boolean mInitialStickyHint; final IBinder mToken; + final int mSendingUser; int mResultCode; String mResultData; Bundle mResultExtras; boolean mAbortBroadcast; boolean mFinished; - + /** @hide */ public PendingResult(int resultCode, String resultData, Bundle resultExtras, - int type, boolean ordered, boolean sticky, IBinder token) { + int type, boolean ordered, boolean sticky, IBinder token, int userId) { mResultCode = resultCode; mResultData = resultData; mResultExtras = resultExtras; @@ -254,6 +255,7 @@ public abstract class BroadcastReceiver { mOrderedHint = ordered; mInitialStickyHint = sticky; mToken = token; + mSendingUser = userId; } /** @@ -425,7 +427,12 @@ public abstract class BroadcastReceiver { } } } - + + /** @hide */ + public int getSendingUserId() { + return mSendingUser; + } + void checkSynchronousHint() { // Note that we don't assert when receiving the initial sticky value, // since that may have come from an ordered broadcast. We'll catch @@ -733,6 +740,11 @@ public abstract class BroadcastReceiver { return mPendingResult; } + /** @hide */ + public int getSendingUserId() { + return mPendingResult.mSendingUser; + } + /** * Control inclusion of debugging help for mismatched * calls to {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index 1866830..88f1a3d 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -563,7 +563,7 @@ public class ClipData implements Parcelable { private String uriToHtml(String uri) { StringBuilder builder = new StringBuilder(256); builder.append("<a href=\""); - builder.append(uri); + builder.append(Html.escapeHtml(uri)); builder.append("\">"); builder.append(Html.escapeHtml(uri)); builder.append("</a>"); diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index b22179e..23d8f46 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -35,7 +35,7 @@ import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; -import android.os.UserId; +import android.os.UserHandle; import android.util.Log; import java.io.File; @@ -279,7 +279,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { final int uid = Binder.getCallingUid(); String missingPerm = null; - if (uid == mMyUid) { + if (UserHandle.isSameApp(uid, mMyUid)) { return; } @@ -340,7 +340,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { final int uid = Binder.getCallingUid(); String missingPerm = null; - if (uid == mMyUid) { + if (UserHandle.isSameApp(uid, mMyUid)) { return; } diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 5c315ce..204f963 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -231,6 +231,19 @@ public class ContentProviderClient { } } + /** See {@link ContentProvider#call(String, String, Bundle)} */ + public Bundle call(String method, String arg, Bundle extras) + throws RemoteException { + try { + return mContentProvider.call(method, arg, extras); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } + } + /** * Call this to indicate to the system that the associated {@link ContentProvider} is no * longer needed by this {@link ContentProviderClient}. diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 0a5a26a..4ab8272 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -39,6 +39,7 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -230,7 +231,8 @@ public abstract class ContentResolver { } try { - String type = ActivityManagerNative.getDefault().getProviderMimeType(url); + String type = ActivityManagerNative.getDefault().getProviderMimeType( + url, UserHandle.myUserId()); return type; } catch (RemoteException e) { // Arbitrary and not worth documenting, as Activity @@ -1217,9 +1219,16 @@ public abstract class ContentResolver { public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer) { + registerContentObserver(uri, notifyForDescendents, observer, UserHandle.getCallingUserId()); + } + + /** @hide - designated user version */ + public final void registerContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer, int userHandle) + { try { getContentService().registerContentObserver(uri, notifyForDescendents, - observer.getContentObserver()); + observer.getContentObserver(), userHandle); } catch (RemoteException e) { } } @@ -1274,10 +1283,21 @@ public abstract class ContentResolver { * @see #requestSync(android.accounts.Account, String, android.os.Bundle) */ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + notifyChange(uri, observer, syncToNetwork, UserHandle.getCallingUserId()); + } + + /** + * Notify registered observers within the designated user(s) that a row was updated. + * + * @hide + */ + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork, + int userHandle) { try { getContentService().notifyChange( uri, observer == null ? null : observer.getContentObserver(), - observer != null && observer.deliverSelfNotifications(), syncToNetwork); + observer != null && observer.deliverSelfNotifications(), syncToNetwork, + userHandle); } catch (RemoteException e) { } } diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index 1a07504..0f6488a 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -17,6 +17,7 @@ package android.content; import android.accounts.Account; +import android.app.ActivityManager; import android.database.IContentObserver; import android.database.sqlite.SQLiteException; import android.net.Uri; @@ -26,13 +27,14 @@ import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserId; +import android.os.UserHandle; import android.util.Log; import android.util.SparseIntArray; import android.Manifest; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -138,19 +140,49 @@ public final class ContentService extends IContentService.Stub { getSyncManager(); } - public void registerContentObserver(Uri uri, boolean notifyForDescendents, - IContentObserver observer) { + /** + * Register a content observer tied to a specific user's view of the provider. + * @param userHandle the user whose view of the provider is to be observed. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly handled; all other pseudousers are forbidden. + */ + @Override + public void registerContentObserver(Uri uri, boolean notifyForDescendants, + IContentObserver observer, int userHandle) { if (observer == null || uri == null) { throw new IllegalArgumentException("You must pass a valid uri and observer"); } + + final int callingUser = UserHandle.getCallingUserId(); + if (callingUser != userHandle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "no permission to observe other users' provider view"); + } + + if (userHandle < 0) { + if (userHandle == UserHandle.USER_CURRENT) { + userHandle = ActivityManager.getCurrentUser(); + } else if (userHandle != UserHandle.USER_ALL) { + throw new InvalidParameterException("Bad user handle for registerContentObserver: " + + userHandle); + } + } + synchronized (mRootNode) { - mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode, - Binder.getCallingUid(), Binder.getCallingPid()); + mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode, + Binder.getCallingUid(), Binder.getCallingPid(), userHandle); if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri + - " with notifyForDescendents " + notifyForDescendents); + " with notifyForDescendants " + notifyForDescendants); } } + public void registerContentObserver(Uri uri, boolean notifyForDescendants, + IContentObserver observer) { + registerContentObserver(uri, notifyForDescendants, observer, + UserHandle.getCallingUserId()); + } + public void unregisterContentObserver(IContentObserver observer) { if (observer == null) { throw new IllegalArgumentException("You must pass a valid observer"); @@ -161,14 +193,39 @@ public final class ContentService extends IContentService.Stub { } } + /** + * Notify observers of a particular user's view of the provider. + * @param userHandle the user whose view of the provider is to be notified. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly interpreted; no other pseudousers are allowed. + */ + @Override public void notifyChange(Uri uri, IContentObserver observer, - boolean observerWantsSelfNotifications, boolean syncToNetwork) { + boolean observerWantsSelfNotifications, boolean syncToNetwork, + int userHandle) { if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Notifying update of " + uri + " from observer " + observer - + ", syncToNetwork " + syncToNetwork); + Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle + + " from observer " + observer + ", syncToNetwork " + syncToNetwork); + } + + // Notify for any user other than the caller's own requires permission. + final int callingUserHandle = UserHandle.getCallingUserId(); + if (userHandle != callingUserHandle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "no permission to notify other users"); + } + + // We passed the permission check; resolve pseudouser targets as appropriate + if (userHandle < 0) { + if (userHandle == UserHandle.USER_CURRENT) { + userHandle = ActivityManager.getCurrentUser(); + } else if (userHandle != UserHandle.USER_ALL) { + throw new InvalidParameterException("Bad user handle for notifyChange: " + + userHandle); + } } - 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(); @@ -176,7 +233,7 @@ public final class ContentService extends IContentService.Stub { ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>(); synchronized (mRootNode) { mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications, - calls); + userHandle, calls); } final int numCalls = calls.size(); for (int i=0; i<numCalls; i++) { @@ -207,7 +264,7 @@ public final class ContentService extends IContentService.Stub { if (syncToNetwork) { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - syncManager.scheduleLocalSync(null /* all accounts */, userId, + syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uri.getAuthority()); } } @@ -216,6 +273,12 @@ public final class ContentService extends IContentService.Stub { } } + public void notifyChange(Uri uri, IContentObserver observer, + boolean observerWantsSelfNotifications, boolean syncToNetwork) { + notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork, + UserHandle.getCallingUserId()); + } + /** * Hide this class since it is not part of api, * but current unittest framework requires it to be public @@ -236,7 +299,7 @@ public final class ContentService extends IContentService.Stub { public void requestSync(Account account, String authority, Bundle extras) { ContentResolver.validateSyncExtrasBundle(extras); - int userId = UserId.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. @@ -259,7 +322,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. @@ -294,7 +357,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -312,7 +375,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -330,7 +393,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -344,7 +407,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -358,7 +421,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -372,7 +435,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -390,7 +453,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -407,7 +470,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -424,7 +487,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -440,7 +503,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -458,7 +521,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -471,7 +534,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -489,7 +552,7 @@ 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(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { @@ -543,16 +606,18 @@ public final class ContentService extends IContentService.Stub { public final IContentObserver observer; public final int uid; public final int pid; - public final boolean notifyForDescendents; + public final boolean notifyForDescendants; + private final int userHandle; private final Object observersLock; public ObserverEntry(IContentObserver o, boolean n, Object observersLock, - int _uid, int _pid) { + int _uid, int _pid, int _userHandle) { this.observersLock = observersLock; observer = o; uid = _uid; pid = _pid; - notifyForDescendents = n; + userHandle = _userHandle; + notifyForDescendants = n; try { observer.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { @@ -571,7 +636,8 @@ public final class ContentService extends IContentService.Stub { pidCounts.put(pid, pidCounts.get(pid)+1); pw.print(prefix); pw.print(name); pw.print(": pid="); pw.print(pid); pw.print(" uid="); - pw.print(uid); pw.print(" target="); + pw.print(uid); pw.print(" user="); + pw.print(userHandle); pw.print(" target="); pw.println(Integer.toHexString(System.identityHashCode( observer != null ? observer.asBinder() : null))); } @@ -639,17 +705,21 @@ public final class ContentService extends IContentService.Stub { return uri.getPathSegments().size() + 1; } + // Invariant: userHandle is either a hard user number or is USER_ALL public void addObserverLocked(Uri uri, IContentObserver observer, - boolean notifyForDescendents, Object observersLock, int uid, int pid) { - addObserverLocked(uri, 0, observer, notifyForDescendents, observersLock, uid, pid); + boolean notifyForDescendants, Object observersLock, + int uid, int pid, int userHandle) { + addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock, + uid, pid, userHandle); } private void addObserverLocked(Uri uri, int index, IContentObserver observer, - boolean notifyForDescendents, Object observersLock, int uid, int pid) { + boolean notifyForDescendants, Object observersLock, + int uid, int pid, int userHandle) { // If this is the leaf node add the observer if (index == countUriSegments(uri)) { - mObservers.add(new ObserverEntry(observer, notifyForDescendents, observersLock, - uid, pid)); + mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock, + uid, pid, userHandle)); return; } @@ -662,8 +732,8 @@ public final class ContentService extends IContentService.Stub { for (int i = 0; i < N; i++) { ObserverNode node = mChildren.get(i); if (node.mName.equals(segment)) { - node.addObserverLocked(uri, index + 1, observer, notifyForDescendents, - observersLock, uid, pid); + node.addObserverLocked(uri, index + 1, observer, notifyForDescendants, + observersLock, uid, pid, userHandle); return; } } @@ -671,8 +741,8 @@ public final class ContentService extends IContentService.Stub { // No child found, create one ObserverNode node = new ObserverNode(segment); mChildren.add(node); - node.addObserverLocked(uri, index + 1, observer, notifyForDescendents, - observersLock, uid, pid); + node.addObserverLocked(uri, index + 1, observer, notifyForDescendants, + observersLock, uid, pid, userHandle); } public boolean removeObserverLocked(IContentObserver observer) { @@ -705,37 +775,49 @@ public final class ContentService extends IContentService.Stub { } private void collectMyObserversLocked(boolean leaf, IContentObserver observer, - boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) { + boolean observerWantsSelfNotifications, int targetUserHandle, + ArrayList<ObserverCall> calls) { int N = mObservers.size(); IBinder observerBinder = observer == null ? null : observer.asBinder(); for (int i = 0; i < N; i++) { ObserverEntry entry = mObservers.get(i); - // Don't notify the observer if it sent the notification and isn't interesed + // Don't notify the observer if it sent the notification and isn't interested // in self notifications boolean selfChange = (entry.observer.asBinder() == observerBinder); if (selfChange && !observerWantsSelfNotifications) { continue; } - // Make sure the observer is interested in the notification - if (leaf || (!leaf && entry.notifyForDescendents)) { - calls.add(new ObserverCall(this, entry.observer, selfChange)); + // Does this observer match the target user? + if (targetUserHandle == UserHandle.USER_ALL + || entry.userHandle == UserHandle.USER_ALL + || targetUserHandle == entry.userHandle) { + // Make sure the observer is interested in the notification + if (leaf || (!leaf && entry.notifyForDescendants)) { + calls.add(new ObserverCall(this, entry.observer, selfChange)); + } } } } + /** + * targetUserHandle is either a hard user handle or is USER_ALL + */ public void collectObserversLocked(Uri uri, int index, IContentObserver observer, - boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) { + boolean observerWantsSelfNotifications, int targetUserHandle, + ArrayList<ObserverCall> calls) { String segment = null; int segmentCount = countUriSegments(uri); if (index >= segmentCount) { // This is the leaf node, notify all observers - collectMyObserversLocked(true, observer, observerWantsSelfNotifications, calls); + collectMyObserversLocked(true, observer, observerWantsSelfNotifications, + targetUserHandle, calls); } else if (index < segmentCount){ segment = getUriSegment(uri, index); - // Notify any observers at this level who are interested in descendents - collectMyObserversLocked(false, observer, observerWantsSelfNotifications, calls); + // Notify any observers at this level who are interested in descendants + collectMyObserversLocked(false, observer, observerWantsSelfNotifications, + targetUserHandle, calls); } int N = mChildren.size(); @@ -744,7 +826,7 @@ public final class ContentService extends IContentService.Stub { if (segment == null || node.mName.equals(segment)) { // We found the child, node.collectObserversLocked(uri, index + 1, - observer, observerWantsSelfNotifications, calls); + observer, observerWantsSelfNotifications, targetUserHandle, calls); if (segment != null) { break; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index ffff9be..201b43f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -19,6 +19,7 @@ package android.content; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.DatabaseErrorHandler; @@ -31,7 +32,11 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.UserHandle; import android.util.AttributeSet; +import android.view.CompatibilityInfoHolder; +import android.view.Display; +import android.view.WindowManager; import java.io.File; import java.io.FileInputStream; @@ -58,18 +63,34 @@ public abstract class Context { */ public static final int MODE_PRIVATE = 0x0000; /** + * @deprecated Creating world-readable files is very dangerous, and likely + * to cause security holes in applications. It is strongly discouraged; + * instead, applications should use more formal mechanism for interactions + * such as {@link ContentProvider}, {@link BroadcastReceiver}, and + * {@link android.app.Service}. There are no guarantees that this + * access mode will remain on a file, such as when it goes through a + * backup and restore. * File creation mode: allow all other applications to have read access * to the created file. * @see #MODE_PRIVATE * @see #MODE_WORLD_WRITEABLE */ + @Deprecated public static final int MODE_WORLD_READABLE = 0x0001; /** + * @deprecated Creating world-writable files is very dangerous, and likely + * to cause security holes in applications. It is strongly discouraged; + * instead, applications should use more formal mechanism for interactions + * such as {@link ContentProvider}, {@link BroadcastReceiver}, and + * {@link android.app.Service}. There are no guarantees that this + * access mode will remain on a file, such as when it goes through a + * backup and restore. * File creation mode: allow all other applications to have write access * to the created file. * @see #MODE_PRIVATE * @see #MODE_WORLD_READABLE */ + @Deprecated public static final int MODE_WORLD_WRITEABLE = 0x0002; /** * File creation mode: for use with {@link #openFileOutput}, if the file @@ -157,7 +178,7 @@ public abstract class Context { * Flag for {@link #bindService}: indicates that the client application * binding to this service considers the service to be more important than * the app itself. When set, the platform will try to have the out of - * memory kill the app before it kills the service it is bound to, though + * memory killer kill the app before it kills the service it is bound to, though * this is not guaranteed to be the case. */ public static final int BIND_ABOVE_CLIENT = 0x0008; @@ -198,6 +219,19 @@ public abstract class Context { public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080; /** + * @hide An idea that is not yet implemented. + * Flag for {@link #bindService}: If binding from an activity, consider + * this service to be visible like the binding activity is. That is, + * it will be treated as something more important to keep around than + * invisible background activities. This will impact the number of + * recent activities the user can switch between without having them + * restart. There is no guarantee this will be respected, as the system + * tries to balance such requests from one app vs. the importantance of + * keeping other apps around. + */ + public static final int BIND_VISIBLE = 0x0100; + + /** * Flag for {@link #bindService}: Don't consider the bound service to be * visible, even if the caller is visible. * @hide @@ -640,8 +674,12 @@ public abstract class Context { * are some important differences: * * <ul> - * <li>The platform does not monitor the space available in external storage, - * and thus will not automatically delete these files. Note that you should + * <li>The platform does not always monitor the space available in external + * storage, and thus may not automatically delete these files. Currently + * the only time files here will be deleted by the platform is when running + * on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and + * {@link android.os.Environment#isExternalStorageEmulated() + * Environment.isExternalStorageEmulated()} returns true. Note that you should * be managing the maximum space you will use for these anyway, just like * with {@link #getCacheDir()}. * <li>External files are not always available: they will disappear if the @@ -851,6 +889,20 @@ public abstract class Context { public abstract void startActivity(Intent intent); /** + * Version of {@link #startActivity(Intent)} that allows you to specify the + * user the activity will be started for. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS_FULL permission. + * @param intent The description of the activity to start. + * @param user The UserHandle of the user to start this activity for. + * @throws ActivityNotFoundException + * @hide + */ + public void startActivityAsUser(Intent intent, UserHandle user) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Launch a new activity. You will not receive any information about when * the activity exits. * @@ -878,6 +930,24 @@ public abstract class Context { public abstract void startActivity(Intent intent, Bundle options); /** + * Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the + * user the activity will be started for. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS_FULL permission. + * @param intent The description of the activity to start. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. See {@link android.app.ActivityOptions} + * for how to build the Bundle supplied here; there are no supported definitions + * for building it manually. + * @param user The UserHandle of the user to start this activity for. + * @throws ActivityNotFoundException + * @hide + */ + public void startActivityAsUser(Intent intent, Bundle options, UserHandle userId) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Same as {@link #startActivities(Intent[], Bundle)} with no options * specified. * @@ -917,6 +987,36 @@ public abstract class Context { public abstract void startActivities(Intent[] intents, Bundle options); /** + * @hide + * Launch multiple new activities. This is generally the same as calling + * {@link #startActivity(Intent)} for the first Intent in the array, + * that activity during its creation calling {@link #startActivity(Intent)} + * for the second entry, etc. Note that unlike that approach, generally + * none of the activities except the last in the array will be created + * at this point, but rather will be created when the user first visits + * them (due to pressing back from the activity on top). + * + * <p>This method throws {@link ActivityNotFoundException} + * if there was no Activity found for <em>any</em> given Intent. In this + * case the state of the activity stack is undefined (some Intents in the + * list may be on it, some not), so you probably want to avoid such situations. + * + * @param intents An array of Intents to be started. + * @param options Additional options for how the Activity should be started. + * @param userHandle The user for whom to launch the activities + * See {@link android.content.Context#startActivity(Intent, Bundle) + * Context.startActivity(Intent, Bundle)} for more details. + * + * @throws ActivityNotFoundException + * + * @see {@link #startActivities(Intent[])} + * @see PackageManager#resolveActivity + */ + public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Same as {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)} * with no options specified. * @@ -988,16 +1088,6 @@ public abstract class Context { public abstract void sendBroadcast(Intent intent); /** - * Same as #sendBroadcast(Intent intent), but for a specific user. Used by the system only. - * @param intent the intent to broadcast - * @param userId user to send the intent to - * @hide - */ - public void sendBroadcast(Intent intent, int userId) { - throw new RuntimeException("Not implemented. Must override in a subclass."); - } - - /** * Broadcast the given intent to all interested BroadcastReceivers, allowing * an optional required permission to be enforced. This * call is asynchronous; it returns immediately, and you will continue @@ -1095,6 +1185,69 @@ public abstract class Context { Bundle initialExtras); /** + * Version of {@link #sendBroadcast(Intent)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * @param intent The intent to broadcast + * @param user UserHandle to send the intent to. + * @see #sendBroadcast(Intent) + */ + public abstract void sendBroadcastAsUser(Intent intent, UserHandle user); + + /** + * Version of {@link #sendBroadcast(Intent, String)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param user UserHandle to send the intent to. + * @param receiverPermission (optional) String naming a permission that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * + * @see #sendBroadcast(Intent, String) + */ + public abstract void sendBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission); + + /** + * Version of + * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)} + * that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param user UserHandle to send the intent to. + * @param receiverPermission String naming a permissions that + * a receiver must hold in order to receive your broadcast. + * If null, no permission is required. + * @param resultReceiver Your own BroadcastReceiver to treat as the final + * receiver of the broadcast. + * @param scheduler A custom Handler with which to schedule the + * resultReceiver callback; if null it will be + * scheduled in the Context's main thread. + * @param initialCode An initial value for the result code. Often + * Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often + * null. + * @param initialExtras An initial value for the result extras. Often + * null. + * + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + */ + public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras); + + /** * Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the * Intent you are sending stays around after the broadcast is complete, * so that others can quickly retrieve that data through the return @@ -1160,7 +1313,6 @@ public abstract class Context { Handler scheduler, int initialCode, String initialData, Bundle initialExtras); - /** * Remove the data previously sent with {@link #sendStickyBroadcast}, * so that it is as if the sticky broadcast had never happened. @@ -1176,6 +1328,70 @@ public abstract class Context { public abstract void removeStickyBroadcast(Intent intent); /** + * Version of {@link #sendStickyBroadcast(Intent)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * @param user UserHandle to send the intent to. + * + * @see #sendBroadcast(Intent) + */ + public abstract void sendStickyBroadcastAsUser(Intent intent, UserHandle user); + + /** + * Version of + * {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)} + * that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param user UserHandle to send the intent to. + * @param resultReceiver Your own BroadcastReceiver to treat as the final + * receiver of the broadcast. + * @param scheduler A custom Handler with which to schedule the + * resultReceiver callback; if null it will be + * scheduled in the Context's main thread. + * @param initialCode An initial value for the result code. Often + * Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often + * null. + * @param initialExtras An initial value for the result extras. Often + * null. + * + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + public abstract void sendStickyOrderedBroadcastAsUser(Intent intent, + UserHandle user, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras); + + /** + * Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the + * user the broadcast will be sent to. This is not available to applications + * that are not pre-installed on the system image. Using it requires holding + * the INTERACT_ACROSS_USERS permission. + * + * <p>You must hold the {@link android.Manifest.permission#BROADCAST_STICKY} + * permission in order to use this API. If you do not hold that + * permission, {@link SecurityException} will be thrown. + * + * @param intent The Intent that was previously broadcast. + * @param user UserHandle to remove the sticky broadcast from. + * + * @see #sendStickyBroadcastAsUser + */ + public abstract void removeStickyBroadcastAsUser(Intent intent, UserHandle user); + + /** * Register a BroadcastReceiver to be run in the main activity thread. The * <var>receiver</var> will be called with any broadcast Intent that * matches <var>filter</var>, in the main application thread. @@ -1258,9 +1474,35 @@ public abstract class Context { * @see #unregisterReceiver */ public abstract Intent registerReceiver(BroadcastReceiver receiver, - IntentFilter filter, - String broadcastPermission, - Handler scheduler); + IntentFilter filter, String broadcastPermission, Handler scheduler); + + /** + * @hide + * Same as {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) + * but for a specific user. This receiver will receiver broadcasts that + * are sent to the requested user. It + * requires holding the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} + * permission. + * + * @param receiver The BroadcastReceiver to handle the broadcast. + * @param user UserHandle to send the intent to. + * @param filter Selects the Intent broadcasts to be received. + * @param broadcastPermission String naming a permissions that a + * broadcaster must hold in order to send an Intent to you. If null, + * no permission is required. + * @param scheduler Handler identifying the thread that will receive + * the Intent. If null, the main thread of the process will be used. + * + * @return The first sticky intent found that matches <var>filter</var>, + * or null if there are none. + * + * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler + * @see #sendBroadcast + * @see #unregisterReceiver + */ + public abstract Intent registerReceiverAsUser(BroadcastReceiver receiver, + UserHandle user, IntentFilter filter, String broadcastPermission, + Handler scheduler); /** * Unregister a previously registered BroadcastReceiver. <em>All</em> @@ -1351,6 +1593,16 @@ public abstract class Context { public abstract boolean stopService(Intent service); /** + * @hide like {@link #startService(Intent)} but for a specific user. + */ + public abstract ComponentName startServiceAsUser(Intent service, UserHandle user); + + /** + * @hide like {@link #stopService(Intent)} but for a specific user. + */ + public abstract boolean stopServiceAsUser(Intent service, UserHandle user); + + /** * 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 it is created and be @@ -1379,6 +1631,7 @@ public abstract class Context { * description (action, category, etc) to match an * {@link IntentFilter} published by a service. * @param conn Receives information as the service is started and stopped. + * This must be a valid ServiceConnection object; it must not be null. * @param flags Operation options for the binding. May be 0, * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND}, * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT}, @@ -1400,11 +1653,11 @@ public abstract class Context { int flags); /** - * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userId + * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle * argument for use by system server and other multi-user aware code. * @hide */ - public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) { + public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -1414,7 +1667,7 @@ public abstract class Context { * stop at any time. * * @param conn The connection interface previously supplied to - * bindService(). + * bindService(). This parameter must not be null. * * @see #bindService */ @@ -1906,6 +2159,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.bluetooth.BluetoothAdapter} for using Bluetooth. + * + * @see #getSystemService + * @hide + */ + public static final String BLUETOOTH_SERVICE = "bluetooth"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.net.sip.SipManager} for accessing the SIP related service. * * @see #getSystemService @@ -1945,6 +2207,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.display.DisplayManager} for interacting with display devices. + * + * @see #getSystemService + * @see android.hardware.display.DisplayManager + */ + public static final String DISPLAY_SERVICE = "display"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.os.SchedulingPolicyService} for managing scheduling policy. * * @see #getSystemService @@ -1955,6 +2226,15 @@ public abstract class Context { public static final String SCHEDULING_POLICY_SERVICE = "scheduling_policy"; /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.UserManager} for managing users on devices that support multiple users. + * + * @see #getSystemService + * @see android.os.UserManager + */ + public static final String USER_SERVICE = "user"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * @@ -2357,6 +2637,65 @@ public abstract class Context { int flags) throws PackageManager.NameNotFoundException; /** + * Similar to {@link #createPackageContext(String, int)}, but with a + * different {@link UserHandle}. For example, {@link #getContentResolver()} + * will open any {@link Uri} as the given user. + * + * @hide + */ + public abstract Context createPackageContextAsUser( + String packageName, int flags, UserHandle user) + throws PackageManager.NameNotFoundException; + + /** + * Return a new Context object for the current Context but whose resources + * are adjusted to match the given Configuration. Each call to this method + * returns a new instance of a Context object; Context objects are not + * shared, however common state (ClassLoader, other Resources for the + * same configuration) may be so the Context itself can be fairly lightweight. + * + * @param overrideConfiguration A {@link Configuration} specifying what + * values to modify in the base Configuration of the original Context's + * resources. If the base configuration changes (such as due to an + * orientation change), the resources of this context will also change except + * for those that have been explicitly overridden with a value here. + * + * @return A Context with the given configuration override. + */ + public abstract Context createConfigurationContext(Configuration overrideConfiguration); + + /** + * Return a new Context object for the current Context but whose resources + * are adjusted to match the metrics of the given Display. Each call to this method + * returns a new instance of a Context object; Context objects are not + * shared, however common state (ClassLoader, other Resources for the + * same configuration) may be so the Context itself can be fairly lightweight. + * + * The returned display Context provides a {@link WindowManager} + * (see {@link #getSystemService(String)}) that is configured to show windows + * on the given display. The WindowManager's {@link WindowManager#getDefaultDisplay} + * method can be used to retrieve the Display from the returned Context. + * + * @param display A {@link Display} object specifying the display + * for whose metrics the Context's resources should be tailored and upon which + * new windows should be shown. + * + * @return A Context for the display. + */ + public abstract Context createDisplayContext(Display display); + + /** + * Gets the compatibility info holder for this context. This information + * is provided on a per-application basis and is used to simulate lower density + * display metrics for legacy applications. + * + * @param displayId The display id for which to get compatibility info. + * @return The compatibility info holder, or null if not required by the application. + * @hide + */ + public abstract CompatibilityInfoHolder getCompatibilityInfo(int displayId); + + /** * Indicates whether this Context is restricted. * * @return True if this Context is restricted, false otherwise. diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 6b950e0..84ad667 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -16,9 +16,13 @@ package android.content; +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.app.LoadedApk; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -29,6 +33,10 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.RemoteException; +import android.os.UserHandle; +import android.view.CompatibilityInfoHolder; +import android.view.Display; import java.io.File; import java.io.FileInputStream; @@ -276,11 +284,23 @@ public class ContextWrapper extends Context { mBase.startActivity(intent); } + /** @hide */ + @Override + public void startActivityAsUser(Intent intent, UserHandle user) { + mBase.startActivityAsUser(intent, user); + } + @Override public void startActivity(Intent intent, Bundle options) { mBase.startActivity(intent, options); } + /** @hide */ + @Override + public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { + mBase.startActivityAsUser(intent, options, user); + } + @Override public void startActivities(Intent[] intents) { mBase.startActivities(intents); @@ -291,6 +311,12 @@ public class ContextWrapper extends Context { mBase.startActivities(intents, options); } + /** @hide */ + @Override + public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) { + mBase.startActivitiesAsUser(intents, options, userHandle); + } + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) @@ -312,12 +338,6 @@ public class ContextWrapper extends Context { mBase.sendBroadcast(intent); } - /** @hide */ - @Override - public void sendBroadcast(Intent intent, int userId) { - mBase.sendBroadcast(intent, userId); - } - @Override public void sendBroadcast(Intent intent, String receiverPermission) { mBase.sendBroadcast(intent, receiverPermission); @@ -340,6 +360,25 @@ public class ContextWrapper extends Context { } @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user) { + mBase.sendBroadcastAsUser(intent, user); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission) { + mBase.sendBroadcastAsUser(intent, user, receiverPermission); + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver, + scheduler, initialCode, initialData, initialExtras); + } + + @Override public void sendStickyBroadcast(Intent intent) { mBase.sendStickyBroadcast(intent); } @@ -360,6 +399,25 @@ public class ContextWrapper extends Context { } @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { + mBase.sendStickyBroadcastAsUser(intent, user); + } + + @Override + public void sendStickyOrderedBroadcastAsUser(Intent intent, + UserHandle user, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + mBase.sendStickyOrderedBroadcastAsUser(intent, user, resultReceiver, + scheduler, initialCode, initialData, initialExtras); + } + + @Override + public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { + mBase.removeStickyBroadcastAsUser(intent, user); + } + + @Override public Intent registerReceiver( BroadcastReceiver receiver, IntentFilter filter) { return mBase.registerReceiver(receiver, filter); @@ -373,6 +431,15 @@ public class ContextWrapper extends Context { scheduler); } + /** @hide */ + @Override + public Intent registerReceiverAsUser( + BroadcastReceiver receiver, UserHandle user, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + return mBase.registerReceiverAsUser(receiver, user, filter, broadcastPermission, + scheduler); + } + @Override public void unregisterReceiver(BroadcastReceiver receiver) { mBase.unregisterReceiver(receiver); @@ -388,6 +455,18 @@ public class ContextWrapper extends Context { return mBase.stopService(name); } + /** @hide */ + @Override + public ComponentName startServiceAsUser(Intent service, UserHandle user) { + return mBase.startServiceAsUser(service, user); + } + + /** @hide */ + @Override + public boolean stopServiceAsUser(Intent name, UserHandle user) { + return mBase.stopServiceAsUser(name, user); + } + @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { @@ -396,8 +475,8 @@ public class ContextWrapper extends Context { /** @hide */ @Override - public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) { - return mBase.bindService(service, conn, flags, userId); + public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) { + return mBase.bindService(service, conn, flags, userHandle); } @Override @@ -513,8 +592,31 @@ public class ContextWrapper extends Context { return mBase.createPackageContext(packageName, flags); } + /** @hide */ + @Override + public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) + throws PackageManager.NameNotFoundException { + return mBase.createPackageContextAsUser(packageName, flags, user); + } + + @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + return mBase.createConfigurationContext(overrideConfiguration); + } + + @Override + public Context createDisplayContext(Display display) { + return mBase.createDisplayContext(display); + } + @Override public boolean isRestricted() { return mBase.isRestricted(); } + + /** @hide */ + @Override + public CompatibilityInfoHolder getCompatibilityInfo(int displayId) { + return mBase.getCompatibilityInfo(displayId); + } } diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl index 86a9392..f956bcf 100644 --- a/core/java/android/content/IContentService.aidl +++ b/core/java/android/content/IContentService.aidl @@ -30,12 +30,28 @@ import android.database.IContentObserver; * @hide */ interface IContentService { - void registerContentObserver(in Uri uri, boolean notifyForDescendentsn, - IContentObserver observer); void unregisterContentObserver(IContentObserver observer); + /** + * Register a content observer tied to a specific user's view of the provider. + * @param userHandle the user whose view of the provider is to be observed. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly handled. + */ + void registerContentObserver(in Uri uri, boolean notifyForDescendants, + IContentObserver observer, int userHandle); + + /** + * Notify observers of a particular user's view of the provider. + * @param userHandle the user whose view of the provider is to be notified. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL + * USER_CURRENT are properly interpreted. + */ void notifyChange(in Uri uri, IContentObserver observer, - boolean observerWantsSelfNotifications, boolean syncToNetwork); + boolean observerWantsSelfNotifications, boolean syncToNetwork, + int userHandle); void requestSync(in Account account, String authority, in Bundle extras); void cancelSync(in Account account, String authority); diff --git a/core/java/android/content/IIntentReceiver.aidl b/core/java/android/content/IIntentReceiver.aidl index 6f2f7c4..3d92723 100755 --- a/core/java/android/content/IIntentReceiver.aidl +++ b/core/java/android/content/IIntentReceiver.aidl @@ -27,7 +27,7 @@ import android.os.Bundle; * {@hide} */ oneway interface IIntentReceiver { - void performReceive(in Intent intent, int resultCode, - String data, in Bundle extras, boolean ordered, boolean sticky); + void performReceive(in Intent intent, int resultCode, String data, + in Bundle extras, boolean ordered, boolean sticky, int sendingUser); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 3fdf451..c14a703 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -27,7 +27,6 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; -import android.media.RemoteControlClient; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -571,7 +570,9 @@ import java.util.Set; * <li> {@link #EXTRA_INITIAL_INTENTS} * <li> {@link #EXTRA_INTENT} * <li> {@link #EXTRA_KEY_EVENT} + * <li> {@link #EXTRA_ORIGINATING_URI} * <li> {@link #EXTRA_PHONE_NUMBER} + * <li> {@link #EXTRA_REFERRER} * <li> {@link #EXTRA_REMOTE_INTENT_TOKEN} * <li> {@link #EXTRA_REPLACING} * <li> {@link #EXTRA_SHORTCUT_ICON} @@ -1253,7 +1254,9 @@ public class Intent implements Parcelable, Cloneable { * Activity Action: Launch application installer. * <p> * Input: The data must be a content: or file: URI at which the application - * can be retrieved. You can optionally supply + * can be retrieved. As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, + * you can also use "package:<package-name>" to install an application for the + * current user that is already installed for another user. You can optionally supply * {@link #EXTRA_INSTALLER_PACKAGE_NAME}, {@link #EXTRA_NOT_UNKNOWN_SOURCE}, * {@link #EXTRA_ALLOW_REPLACE}, and {@link #EXTRA_RETURN_RESULT}. * <p> @@ -1286,6 +1289,30 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.extra.NOT_UNKNOWN_SOURCE"; /** + * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and + * {@link #ACTION_VIEW} to indicate the URI from which the local APK in the Intent + * data field originated from. + */ + public static final String EXTRA_ORIGINATING_URI + = "android.intent.extra.ORIGINATING_URI"; + + /** + * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and + * {@link #ACTION_VIEW} to indicate the HTTP referrer URI associated with the Intent + * data field or {@link #EXTRA_ORIGINATING_URI}. + */ + public static final String EXTRA_REFERRER + = "android.intent.extra.REFERRER"; + + /** + * Used as an int extra field with {@link #ACTION_INSTALL_PACKAGE} and + * {@link} #ACTION_VIEW} to indicate the uid of the package that initiated the install + * @hide + */ + public static final String EXTRA_ORIGINATING_UID + = "android.intent.extra.ORIGINATING_UID"; + + /** * Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} to install a * package. Tells the installer UI to skip the confirmation with the user * if the .apk is replacing an existing one. @@ -1328,6 +1355,13 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE"; /** + * Specify whether the package should be uninstalled for all users. + * @hide because these should not be part of normal application flow. + */ + public static final String EXTRA_UNINSTALL_ALL_USERS + = "android.intent.extra.UNINSTALL_ALL_USERS"; + + /** * A string associated with a {@link #ACTION_UPGRADE_SETUP} activity * describing the last run version of the platform that was setup. * @hide @@ -1356,6 +1390,24 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON"; /** + * Broadcast Action: Sent after the system stops dreaming. + * + * <p class="note">This is a protected intent that can only be sent by the system. + * It is only sent to registered receivers.</p> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED"; + + /** + * Broadcast Action: Sent after the system starts dreaming. + * + * <p class="note">This is a protected intent that can only be sent by the system. + * It is only sent to registered receivers.</p> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED"; + + /** * Broadcast Action: Sent when the user is present after device wakes up (e.g when the * keyguard is gone). * @@ -1456,7 +1508,7 @@ public class Intent implements Parcelable, Cloneable { * Broadcast Action: A new application package has been installed on the * device. The data contains the name of the package. Note that the * newly installed package does <em>not</em> receive this broadcast. - * <p>My include the following extras: + * <p>May include the following extras: * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package. * <li> {@link #EXTRA_REPLACING} is set to true if this is following @@ -1472,7 +1524,7 @@ public class Intent implements Parcelable, Cloneable { * Broadcast Action: A new version of an application package has been * installed, replacing an existing version that was previously installed. * The data contains the name of the package. - * <p>My include the following extras: + * <p>May include the following extras: * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package. * </ul> @@ -1624,6 +1676,15 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION"; /** + * Broadcast Action: Sent to the system package verifier when a package is + * verified. The data contains the package URI. + * <p class="note"> + * This is a protected intent that can only be sent by the system. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED"; + + /** * Broadcast Action: Resources for a set of packages (which were * previously unavailable) are currently * available since the media on which they exist is available. @@ -2261,29 +2322,104 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.PRE_BOOT_COMPLETED"; /** - * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USERID that has the - * userid of the new user. + * Sent the first time a user is starting, to allow system apps to + * perform one time initialization. (This will not be seen by third + * party applications because a newly initialized user does not have any + * third party applications installed for it.) This is sent early in + * starting the user, around the time the home app is started, before + * {@link #ACTION_BOOT_COMPLETED} is sent. + */ + public static final String ACTION_USER_INITIALIZE = + "android.intent.action.USER_INITIALIZE"; + + /** + * Sent when a user switch is happening, causing the process's user to be + * brought to the foreground. This is only sent to receivers registered + * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver}. It is sent to the user that is going to the + * foreground. + */ + public static final String ACTION_USER_FOREGROUND = + "android.intent.action.USER_FOREGROUND"; + + /** + * Sent when a user switch is happening, causing the process's user to be + * sent to the background. This is only sent to receivers registered + * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) + * Context.registerReceiver}. It is sent to the user that is going to the + * background. + */ + public static final String ACTION_USER_BACKGROUND = + "android.intent.action.USER_BACKGROUND"; + + /** + * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USER_HANDLE that has the + * userHandle of the new user. It is sent to all running users. You must hold + * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast. * @hide */ public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED"; /** - * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USERID that has - * the userid of the user. + * Broadcast sent to the system when a user is started. Carries an extra EXTRA_USER_HANDLE that has + * the userHandle of the user. This is only sent to + * registered receivers, not manifest receivers. It is sent to the user + * that has been started. + * @hide + */ + public static final String ACTION_USER_STARTED = + "android.intent.action.USER_STARTED"; + + /** + * Broadcast sent to the system when a user is stopped. Carries an extra EXTRA_USER_HANDLE that has + * the userHandle of the user. This is similar to {@link #ACTION_PACKAGE_RESTARTED}, + * but for an entire user instead of a specific package. This is only sent to + * registered receivers, not manifest receivers. It is sent to all running + * users <em>except</em> the one that has just been stopped (which is no + * longer running). + * @hide + */ + public static final String ACTION_USER_STOPPED = + "android.intent.action.USER_STOPPED"; + + /** + * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has + * the userHandle of the user. It is sent to all running users except the + * one that has been removed. You must hold + * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast. * @hide */ public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; /** - * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USERID that has - * the userid of the user to become the current one. + * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USER_HANDLE that has + * the userHandle of the user to become the current one. This is only sent to + * registered receivers, not manifest receivers. It is sent to all running users. + * You must hold + * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast. * @hide */ public static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED"; + /** + * Broadcast sent to the system when a user's information changes. Carries an extra + * {@link #EXTRA_USER_HANDLE} to indicate which user's information changed. + * This is only sent to registered receivers, not manifest receivers. It is sent to the user + * whose information has changed. + * @hide + */ + public static final String ACTION_USER_INFO_CHANGED = + "android.intent.action.USER_INFO_CHANGED"; + + /** + * Sent when the user taps on the clock widget in the system's "quick settings" area. + */ + public static final String ACTION_QUICK_CLOCK = + "android.intent.action.QUICK_CLOCK"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -2693,6 +2829,15 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; /** + * @hide + * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} + * intents to indicate that at this point the package has been removed for + * all users on the device. + */ + public static final String EXTRA_REMOVED_FOR_ALL_USERS + = "android.intent.extra.REMOVED_FOR_ALL_USERS"; + + /** * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} * intents to indicate that this is a replacement of the package, so this * broadcast will immediately be followed by an add broadcast for a @@ -2834,12 +2979,12 @@ public class Intent implements Parcelable, Cloneable { "android.intent.extra.LOCAL_ONLY"; /** - * The userid carried with broadcast intents related to addition, removal and switching of users + * The userHandle carried with broadcast intents related to addition, removal and switching of users * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}. * @hide */ - public static final String EXTRA_USERID = - "android.intent.extra.user_id"; + public static final String EXTRA_USER_HANDLE = + "android.intent.extra.user_handle"; // --------------------------------------------------------------------- // --------------------------------------------------------------------- diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index 4db4bdc..166495b 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.util.AndroidException; @@ -113,8 +114,8 @@ public class IntentSender implements Parcelable { mWho = who; mHandler = handler; } - public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean serialized, boolean sticky) { + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean serialized, boolean sticky, int sendingUser) { mIntent = intent; mResultCode = resultCode; mResultData = data; @@ -204,6 +205,20 @@ public class IntentSender implements Parcelable { } /** + * @deprecated Renamed to {@link #getCreatorPackage()}. + */ + @Deprecated + public String getTargetPackage() { + try { + return ActivityManagerNative.getDefault() + .getPackageForIntentSender(mTarget); + } catch (RemoteException e) { + // Should never happen. + return null; + } + } + + /** * Return the package name of the application that created this * IntentSender, that is the identity under which you will actually be * sending the Intent. The returned string is supplied by the system, so @@ -212,7 +227,7 @@ public class IntentSender implements Parcelable { * @return The package name of the PendingIntent, or null if there is * none associated with it. */ - public String getTargetPackage() { + public String getCreatorPackage() { try { return ActivityManagerNative.getDefault() .getPackageForIntentSender(mTarget); @@ -223,6 +238,47 @@ public class IntentSender implements Parcelable { } /** + * Return the uid of the application that created this + * PendingIntent, that is the identity under which you will actually be + * sending the Intent. The returned integer is supplied by the system, so + * that an application can not spoof its uid. + * + * @return The uid of the PendingIntent, or -1 if there is + * none associated with it. + */ + public int getCreatorUid() { + try { + return ActivityManagerNative.getDefault() + .getUidForIntentSender(mTarget); + } catch (RemoteException e) { + // Should never happen. + return -1; + } + } + + /** + * Return the user handle of the application that created this + * PendingIntent, that is the user under which you will actually be + * sending the Intent. The returned UserHandle is supplied by the system, so + * that an application can not spoof its user. See + * {@link android.os.Process#myUserHandle() Process.myUserHandle()} for + * more explanation of user handles. + * + * @return The user handle of the PendingIntent, or null if there is + * none associated with it. + */ + public UserHandle getCreatorUserHandle() { + try { + int uid = ActivityManagerNative.getDefault() + .getUidForIntentSender(mTarget); + return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null; + } catch (RemoteException e) { + // Should never happen. + return null; + } + } + + /** * Comparison operator on two IntentSender objects, such that true * is returned then they both represent the same operation from the * same package. diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index badcb03..564a804 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -16,10 +16,6 @@ package android.content; -import com.android.internal.R; -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; - import android.accounts.Account; import android.accounts.AccountAndUser; import android.accounts.AccountManager; @@ -27,7 +23,6 @@ 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; @@ -52,7 +47,8 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.UserId; +import android.os.UserHandle; +import android.os.UserManager; import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; @@ -61,6 +57,11 @@ import android.util.EventLog; import android.util.Log; import android.util.Pair; +import com.android.internal.R; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; +import com.google.android.collect.Sets; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -74,6 +75,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.concurrent.CountDownLatch; /** @@ -150,7 +152,7 @@ public class SyncManager implements OnAccountsUpdateListener { private AlarmManager mAlarmService = null; private SyncStorageEngine mSyncStorageEngine; - public SyncQueue mSyncQueue; + final public SyncQueue mSyncQueue; protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList(); @@ -173,7 +175,7 @@ public class SyncManager implements OnAccountsUpdateListener { Log.v(TAG, "Internal storage is low."); } mStorageIsLow = true; - cancelActiveSync(null /* any account */, UserId.USER_ALL, + cancelActiveSync(null /* any account */, UserHandle.USER_ALL, null /* any authority */); } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -194,28 +196,38 @@ public class SyncManager implements OnAccountsUpdateListener { private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (getConnectivityManager().getBackgroundDataSetting()) { - scheduleSync(null /* account */, UserId.USER_ALL, null /* authority */, + scheduleSync(null /* account */, UserHandle.USER_ALL, null /* authority */, new Bundle(), 0 /* delay */, false /* onlyThoseWithUnknownSyncableState */); } } }; + private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + onAccountsUpdated(null); + } + }; + private final PowerManager mPowerManager; // Use this as a random offset to seed all periodic syncs private int mSyncRandomOffsetMillis; + private UserManager mUserManager; + private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours - private List<UserInfo> getAllUsers() { - try { - return AppGlobals.getPackageManager().getUsers(); - } catch (RemoteException re) { - // Local to system process, shouldn't happen + private UserManager getUserManager() { + if (mUserManager == null) { + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); } - return null; + return mUserManager; + } + + private List<UserInfo> getAllUsers() { + return getUserManager().getUsers(); } private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) { @@ -282,7 +294,7 @@ public class SyncManager implements OnAccountsUpdateListener { // a chance to set their syncable state. boolean onlyThoseWithUnkownSyncableState = justBootedUp; - scheduleSync(null, UserId.USER_ALL, null, null, 0 /* no delay */, + scheduleSync(null, UserHandle.USER_ALL, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState); } } @@ -323,7 +335,21 @@ public class SyncManager implements OnAccountsUpdateListener { private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - onUserRemoved(intent); + String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (Intent.ACTION_USER_REMOVED.equals(action)) { + Log.i(TAG, "User removed - cleanup: u" + userId); + onUserRemoved(intent); + } else if (Intent.ACTION_USER_STARTED.equals(action)) { + Log.i(TAG, "User started - check alarms: u" + userId); + sendCheckAlarmsMessage(); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + Log.i(TAG, "User stopped - stop syncs: u" + userId); + cancelActiveSync( + null /* any account */, + userId, + null /* any authority */); + } } }; @@ -366,7 +392,7 @@ public class SyncManager implements OnAccountsUpdateListener { mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { public void onServiceChanged(SyncAdapterType type, boolean removed) { if (!removed) { - scheduleSync(null, UserId.USER_ALL, type.authority, null, 0 /* no delay */, + scheduleSync(null, UserHandle.USER_ALL, type.authority, null, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } @@ -396,7 +422,9 @@ public class SyncManager implements OnAccountsUpdateListener { intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiver(mUserIntentReceiver, intentFilter); + intentFilter.addAction(Intent.ACTION_USER_STARTED); + mContext.registerReceiverAsUser( + mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); if (!factoryTest) { mNotificationMgr = (NotificationManager) @@ -434,8 +462,11 @@ public class SyncManager implements OnAccountsUpdateListener { }); if (!factoryTest) { - AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this, - mSyncHandler, false /* updateImmediately */); + // Register for account list updates for all users + mContext.registerReceiverAsUser(mAccountsUpdatedReceiver, + UserHandle.ALL, + new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION), + null, null); // do this synchronously to ensure we have the accounts before this call returns onAccountsUpdated(null); } @@ -512,7 +543,7 @@ public class SyncManager implements OnAccountsUpdateListener { } AccountAndUser[] accounts; - if (requestedAccount != null && userId != UserId.USER_ALL) { + if (requestedAccount != null && userId != UserHandle.USER_ALL) { accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) }; } else { // if the accounts aren't configured yet then we can't support an account-less @@ -746,8 +777,8 @@ public class SyncManager implements OnAccountsUpdateListener { } // Cap the delay - long maxSyncRetryTimeInSeconds = Settings.Secure.getLong(mContext.getContentResolver(), - Settings.Secure.SYNC_MAX_RETRY_DELAY_IN_SECONDS, + long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(), + Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS, DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS); if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) { newDelayInMs = maxSyncRetryTimeInSeconds * 1000; @@ -890,9 +921,12 @@ public class SyncManager implements OnAccountsUpdateListener { } private void onUserRemoved(Intent intent) { - int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1); + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userId == -1) return; + removeUser(userId); + } + private void removeUser(int userId) { // Clean up the storage engine database mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId); onAccountsUpdated(null); @@ -975,7 +1009,7 @@ public class SyncManager implements OnAccountsUpdateListener { mSyncHandler.sendMessage(msg); } - boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info) { + boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this); } @@ -984,8 +1018,9 @@ public class SyncManager implements OnAccountsUpdateListener { intent.setComponent(info.componentName); intent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.sync_binding_label); - intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( - mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0)); + intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser( + mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0, + null, new UserHandle(userId))); mBound = true; final boolean bindResult = mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND @@ -1261,7 +1296,8 @@ public class SyncManager implements OnAccountsUpdateListener { final String accountKey; if (authority != null) { authorityName = authority.authority; - accountKey = authority.account.name + "/" + authority.account.type; + accountKey = authority.account.name + "/" + authority.account.type + + " u" + authority.userId; } else { authorityName = "Unknown"; accountKey = "Unknown"; @@ -1388,7 +1424,8 @@ public class SyncManager implements OnAccountsUpdateListener { final String accountKey; if (authority != null) { authorityName = authority.authority; - accountKey = authority.account.name + "/" + authority.account.type; + accountKey = authority.account.name + "/" + authority.account.type + + " u" + authority.userId; } else { authorityName = "Unknown"; accountKey = "Unknown"; @@ -1918,6 +1955,10 @@ public class SyncManager implements OnAccountsUpdateListener { } Iterator<SyncOperation> operationIterator = mSyncQueue.mOperationsMap.values().iterator(); + + final ActivityManager activityManager + = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + final Set<Integer> removedUsers = Sets.newHashSet(); while (operationIterator.hasNext()) { final SyncOperation op = operationIterator.next(); @@ -1937,6 +1978,15 @@ public class SyncManager implements OnAccountsUpdateListener { continue; } + // if the user in not running, drop the request + if (!activityManager.isUserRunning(op.userId)) { + final UserInfo userInfo = mUserManager.getUserInfo(op.userId); + if (userInfo == null) { + removedUsers.add(op.userId); + } + continue; + } + // if the next run time is in the future, meaning there are no syncs ready // to run, return the time if (op.effectiveRunTime > now) { @@ -1977,6 +2027,12 @@ public class SyncManager implements OnAccountsUpdateListener { operations.add(op); } + for (Integer user : removedUsers) { + // if it's still removed + if (mUserManager.getUserInfo(user) == null) { + removeUser(user); + } + } } // find the next operation to dispatch, if one is ready @@ -2127,7 +2183,7 @@ public class SyncManager implements OnAccountsUpdateListener { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext); } - if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) { + if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) { Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo); closeActiveSyncContext(activeSyncContext); return false; @@ -2162,20 +2218,20 @@ public class SyncManager implements OnAccountsUpdateListener { new ArrayList<ActiveSyncContext>(mActiveSyncContexts); for (ActiveSyncContext activeSyncContext : activeSyncs) { if (activeSyncContext != null) { - // if an authority was specified then only cancel the sync if it matches + // if an account was specified then only cancel the sync if it matches if (account != null) { if (!account.equals(activeSyncContext.mSyncOperation.account)) { continue; } } - // if an account was specified then only cancel the sync if it matches + // if an authority was specified then only cancel the sync if it matches if (authority != null) { if (!authority.equals(activeSyncContext.mSyncOperation.authority)) { continue; } } // check if the userid matches - if (userId != UserId.USER_ALL + if (userId != UserHandle.USER_ALL && userId != activeSyncContext.mSyncOperation.userId) { continue; } @@ -2250,10 +2306,12 @@ public class SyncManager implements OnAccountsUpdateListener { if (syncResult != null && syncResult.tooManyDeletions) { installHandleTooManyDeletesNotification(syncOperation.account, - syncOperation.authority, syncResult.stats.numDeletes); + syncOperation.authority, syncResult.stats.numDeletes, + syncOperation.userId); } else { - mNotificationMgr.cancel( - syncOperation.account.hashCode() ^ syncOperation.authority.hashCode()); + mNotificationMgr.cancelAsUser(null, + syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(), + new UserHandle(syncOperation.userId)); } if (syncResult != null && syncResult.fullSyncRequested) { @@ -2466,7 +2524,7 @@ public class SyncManager implements OnAccountsUpdateListener { } private void installHandleTooManyDeletesNotification(Account account, String authority, - long numDeletes) { + long numDeletes, int userId) { if (mNotificationMgr == null) return; final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider( @@ -2488,7 +2546,8 @@ public class SyncManager implements OnAccountsUpdateListener { } final PendingIntent pendingIntent = PendingIntent - .getActivity(mContext, 0, clickIntent, PendingIntent.FLAG_CANCEL_CURRENT); + .getActivityAsUser(mContext, 0, clickIntent, + PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(userId)); CharSequence tooManyDeletesDescFormat = mContext.getResources().getText( R.string.contentServiceTooManyDeletesNotificationDesc); @@ -2502,7 +2561,8 @@ public class SyncManager implements OnAccountsUpdateListener { String.format(tooManyDeletesDescFormat.toString(), authorityName), pendingIntent); notification.flags |= Notification.FLAG_ONGOING_EVENT; - mNotificationMgr.notify(account.hashCode() ^ authority.hashCode(), notification); + mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(), + notification, new UserHandle(userId)); } /** diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java index 9fcc22d..6611fcd 100644 --- a/core/java/android/content/SyncOperation.java +++ b/core/java/android/content/SyncOperation.java @@ -95,13 +95,18 @@ public class SyncOperation implements Comparable { } public String dump(boolean useOneLine) { - StringBuilder sb = new StringBuilder(); - sb.append(account.name); - sb.append(" (" + account.type + ")"); - sb.append(", " + authority); - sb.append(", "); - sb.append(SyncStorageEngine.SOURCES[syncSource]); - sb.append(", earliestRunTime " + earliestRunTime); + StringBuilder sb = new StringBuilder() + .append(account.name) + .append(" u") + .append(userId).append(" (") + .append(account.type) + .append(")") + .append(", ") + .append(authority) + .append(", ") + .append(SyncStorageEngine.SOURCES[syncSource]) + .append(", earliestRunTime ") + .append(earliestRunTime); if (expedited) { sb.append(", EXPEDITED"); } diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 7a9fc65..de97481 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -16,7 +16,6 @@ package android.content; -import com.android.internal.os.AtomicFile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; @@ -38,7 +37,7 @@ import android.os.Message; import android.os.Parcel; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.SystemClock; +import android.util.AtomicFile; import android.util.Log; import android.util.SparseArray; import android.util.Xml; diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 6b16e74..b884b98 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -172,6 +172,20 @@ public class ActivityInfo extends ComponentInfo */ public static final int FLAG_IMMERSIVE = 0x0400; /** + * @hide Bit in {@link #flags}: If set, this component will only be seen + * by the primary user. Only works with broadcast receivers. Set from the + * {@link android.R.attr#primaryUserOnly} attribute. + */ + public static final int FLAG_PRIMARY_USER_ONLY = 0x20000000; + /** + * Bit in {@link #flags}: If set, a single instance of the receiver will + * run for all users on the device. Set from the + * {@link android.R.attr#singleUser} attribute. Note that this flag is + * only relevant for ActivityInfo structures that are describing receiver + * components; it is not applied to activities. + */ + public static final int FLAG_SINGLE_USER = 0x40000000; + /** * Options that have been set in the activity declaration in the * manifest. * These include: @@ -181,7 +195,7 @@ public class ActivityInfo extends ComponentInfo * {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS}, * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY}, * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS}, - * {@link #FLAG_HARDWARE_ACCELERATED} + * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}. */ public int flags; @@ -358,6 +372,18 @@ public class ActivityInfo extends ComponentInfo public static final int CONFIG_SMALLEST_SCREEN_SIZE = 0x0800; /** * Bit in {@link #configChanges} that indicates that the activity + * can itself handle density changes. Set from the + * {@link android.R.attr#configChanges} attribute. + */ + public static final int CONFIG_DENSITY = 0x1000; + /** + * Bit in {@link #configChanges} that indicates that the activity + * can itself handle the change to layout direction. Set from the + * {@link android.R.attr#configChanges} attribute. + */ + public static final int CONFIG_LAYOUT_DIRECTION = 0x2000; + /** + * Bit in {@link #configChanges} that indicates that the activity * can itself handle changes to the font scaling factor. Set from the * {@link android.R.attr#configChanges} attribute. This is * not a core resource configutation, but a higher-level value, so its @@ -383,6 +409,8 @@ public class ActivityInfo extends ComponentInfo 0x1000, // UI MODE 0x0200, // SCREEN SIZE 0x2000, // SMALLEST SCREEN SIZE + 0x0100, // DENSITY + 0x4000, // LAYOUT DIRECTION }; /** @hide * Convert Java change bits to native. @@ -419,8 +447,9 @@ public class ActivityInfo extends ComponentInfo * {@link #CONFIG_MCC}, {@link #CONFIG_MNC}, * {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN}, * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION}, - * {@link #CONFIG_ORIENTATION}, and {@link #CONFIG_SCREEN_LAYOUT}. Set from the - * {@link android.R.attr#configChanges} attribute. + * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT} and + * {@link #CONFIG_LAYOUT_DIRECTION}. Set from the {@link android.R.attr#configChanges} + * attribute. */ public int configChanges; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index e1434b3..a0283d3 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -298,11 +298,23 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * activity's manifest. * * Default value is false (no support for RTL). - * @hide */ public static final int FLAG_SUPPORTS_RTL = 1<<22; /** + * Value for {@link #flags}: true if the application is currently + * installed for the calling user. + */ + public static final int FLAG_INSTALLED = 1<<23; + + /** + * Value for {@link #flags}: true if the application only has its + * data installed; the application package itself does not currently + * exist on the device. + */ + public static final int FLAG_IS_DATA_ONLY = 1<<24; + + /** * Value for {@link #flags}: Set to true if the application has been * installed using the forward lock option. * @@ -335,7 +347,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_SUPPORTS_NORMAL_SCREENS}, * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_SUPPORTS_XLARGE_SCREENS}, * {@link #FLAG_RESIZEABLE_FOR_SCREENS}, - * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE} + * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE}, + * {@link #FLAG_INSTALLED}. */ public int flags = 0; diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 90b4247..b0ae5da 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -32,6 +32,7 @@ import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.ManifestDigest; +import android.content.pm.PackageCleanItem; import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.PermissionGroupInfo; @@ -39,8 +40,10 @@ import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; +import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.net.Uri; +import android.os.ParcelFileDescriptor; import android.content.IntentSender; /** @@ -124,7 +127,7 @@ interface IPackageManager { * limit that kicks in when flags are included that bloat up the data * returned. */ - ParceledListSlice getInstalledPackages(int flags, in String lastRead); + ParceledListSlice getInstalledPackages(int flags, in String lastRead, in int userId); /** * This implements getInstalledApplications via a "last returned row" @@ -199,7 +202,7 @@ interface IPackageManager { List<PackageInfo> getPreferredPackages(int flags); void addPreferredActivity(in IntentFilter filter, int match, - in ComponentName[] set, in ComponentName activity); + in ComponentName[] set, in ComponentName activity, int userId); void replacePreferredActivity(in IntentFilter filter, int match, in ComponentName[] set, in ComponentName activity); @@ -303,10 +306,11 @@ interface IPackageManager { * Get package statistics including the code, data and cache size for * an already installed package * @param packageName The package name of the application + * @param userHandle Which user the size should be retrieved for * @param observer a callback to use to notify when the asynchronous * retrieval of information is complete. */ - void getPackageSizeInfo(in String packageName, IPackageStatsObserver observer); + void getPackageSizeInfo(in String packageName, int userHandle, IPackageStatsObserver observer); /** * Get a list of shared libraries that are available on the @@ -348,7 +352,7 @@ interface IPackageManager { */ void updateExternalMediaStatus(boolean mounted, boolean reportStatus); - String nextPackageToClean(String lastPackage); + PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage); void movePackage(String packageName, IPackageMoveObserver observer, int flags); @@ -357,23 +361,24 @@ interface IPackageManager { boolean setInstallLocation(int loc); int getInstallLocation(); - UserInfo createUser(in String name, int flags); - boolean removeUser(int userId); - void updateUserName(int userId, String name); - void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer, int flags, in String installerPackageName, in Uri verificationURI, in ManifestDigest manifestDigest, in ContainerEncryptionParams encryptionParams); + void installPackageWithVerificationAndEncryption(in Uri packageURI, + in IPackageInstallObserver observer, int flags, in String installerPackageName, + in VerificationParams verificationParams, + in ContainerEncryptionParams encryptionParams); + + int installExistingPackage(String packageName); + void verifyPendingInstall(int id, int verificationCode); + void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay); VerifierDeviceIdentity getVerifierDeviceIdentity(); boolean isFirstBoot(); - List<UserInfo> getUsers(); - UserInfo getUser(int userId); - void setPermissionEnforced(String permission, boolean enforced); boolean isPermissionEnforced(String permission); diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java index ea47e8e..a977e41 100644 --- a/core/java/android/content/pm/InstrumentationInfo.java +++ b/core/java/android/content/pm/InstrumentationInfo.java @@ -18,10 +18,6 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; - -import java.text.Collator; -import java.util.Comparator; /** * Information you can retrieve about a particular piece of test diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java index f5e72e0..75505bc 100644 --- a/core/java/android/content/pm/ManifestDigest.java +++ b/core/java/android/content/pm/ManifestDigest.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.content.pm; import android.os.Parcel; diff --git a/core/java/android/content/pm/PackageCleanItem.aidl b/core/java/android/content/pm/PackageCleanItem.aidl new file mode 100644 index 0000000..9bb203e --- /dev/null +++ b/core/java/android/content/pm/PackageCleanItem.aidl @@ -0,0 +1,18 @@ +/* Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.content.pm; + +parcelable PackageCleanItem; diff --git a/core/java/android/content/pm/PackageCleanItem.java b/core/java/android/content/pm/PackageCleanItem.java new file mode 100644 index 0000000..b1896aa --- /dev/null +++ b/core/java/android/content/pm/PackageCleanItem.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public class PackageCleanItem { + public final int userId; + public final String packageName; + public final boolean andCode; + + public PackageCleanItem(int userId, String packageName, boolean andCode) { + this.userId = userId; + this.packageName = packageName; + this.andCode = andCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + try { + if (obj != null) { + PackageCleanItem other = (PackageCleanItem)obj; + return userId == other.userId && packageName.equals(other.packageName) + && andCode == other.andCode; + } + } catch (ClassCastException e) { + } + return false; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + userId; + result = 31 * result + packageName.hashCode(); + result = 31 * result + (andCode ? 1 : 0); + return result; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeInt(userId); + dest.writeString(packageName); + dest.writeInt(andCode ? 1 : 0); + } + + public static final Parcelable.Creator<PackageCleanItem> CREATOR + = new Parcelable.Creator<PackageCleanItem>() { + public PackageCleanItem createFromParcel(Parcel source) { + return new PackageCleanItem(source); + } + + public PackageCleanItem[] newArray(int size) { + return new PackageCleanItem[size]; + } + }; + + private PackageCleanItem(Parcel source) { + userId = source.readInt(); + packageName = source.readString(); + andCode = source.readInt() != 0; + } +} diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java index 9625944..a1566da 100644 --- a/core/java/android/content/pm/PackageInfoLite.java +++ b/core/java/android/content/pm/PackageInfoLite.java @@ -32,6 +32,11 @@ public class PackageInfoLite implements Parcelable { public String packageName; /** + * The android:versionCode of the package. + */ + public int versionCode; + + /** * Specifies the recommended install location. Can be one of * {@link #PackageHelper.RECOMMEND_INSTALL_INTERNAL} to install on internal storage * {@link #PackageHelper.RECOMMEND_INSTALL_EXTERNAL} to install on external media @@ -58,6 +63,7 @@ public class PackageInfoLite implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeString(packageName); + dest.writeInt(versionCode); dest.writeInt(recommendedInstallLocation); dest.writeInt(installLocation); @@ -82,6 +88,7 @@ public class PackageInfoLite implements Parcelable { private PackageInfoLite(Parcel source) { packageName = source.readString(); + versionCode = source.readInt(); recommendedInstallLocation = source.readInt(); installLocation = source.readInt(); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 7e96ae6..8ba1988 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -28,6 +28,7 @@ import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Environment; +import android.os.UserHandle; import android.util.AndroidException; import android.util.DisplayMetrics; @@ -309,6 +310,23 @@ public abstract class PackageManager { public static final int INSTALL_FROM_ADB = 0x00000020; /** + * Flag parameter for {@link #installPackage} to indicate that this install + * should immediately be visible to all users. + * + * @hide + */ + public static final int INSTALL_ALL_USERS = 0x00000040; + + /** + * Flag parameter for {@link #installPackage} to indicate that it is okay + * to install an update to an app where the newly installed app has a lower + * version code than the currently installed app. + * + * @hide + */ + public static final int INSTALL_ALLOW_DOWNGRADE = 0x00000080; + + /** * Flag parameter for * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate * that you don't want to kill the app containing the component. Be careful when you set this @@ -528,6 +546,14 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_UID_CHANGED = -24; /** + * Installation return code: this is passed to the {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package has an older version code than the currently installed package. + * @hide + */ + public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25; + + /** * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser was given a path that is not a file, or does not end with the expected @@ -624,7 +650,15 @@ public abstract class PackageManager { * * @hide */ - public static final int DONT_DELETE_DATA = 0x00000001; + public static final int DELETE_KEEP_DATA = 0x00000001; + + /** + * Flag parameter for {@link #deletePackage} to indicate that you want the + * package deleted for all users. + * + * @hide + */ + public static final int DELETE_ALL_USERS = 0x00000002; /** * Return code for when package deletion succeeds. This is passed to the @@ -761,6 +795,14 @@ public abstract class PackageManager { public static final int VERIFICATION_REJECT = -1; /** + * Can be used as the {@code millisecondsToDelay} argument for + * {@link PackageManager#extendVerificationTimeout}. This is the + * maximum time {@code PackageManager} waits for the verification + * agent to return (in milliseconds). + */ + public static final long MAXIMUM_VERIFICATION_TIMEOUT = 60*60*1000; + + /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or * lag in sound input or output. @@ -793,6 +835,14 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has at least one camera pointing in + * some direction. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_ANY = "android.hardware.camera.any"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device's camera supports flash. */ @SdkConstant(SdkConstantType.FEATURE) @@ -1109,6 +1159,38 @@ public abstract class PackageManager { = "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS"; /** + * Extra field name for the uid of who is requesting to install + * the package. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_INSTALLER_UID + = "android.content.pm.extra.VERIFICATION_INSTALLER_UID"; + + /** + * Extra field name for the package name of a package pending verification. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_PACKAGE_NAME + = "android.content.pm.extra.VERIFICATION_PACKAGE_NAME"; + /** + * Extra field name for the result of a verification, either + * {@link #VERIFICATION_ALLOW}, or {@link #VERIFICATION_REJECT}. + * Passed to package verifiers after a package is verified. + */ + public static final String EXTRA_VERIFICATION_RESULT + = "android.content.pm.extra.VERIFICATION_RESULT"; + + /** + * Extra field name for the version code of a package pending verification. + * + * @hide + */ + public static final String EXTRA_VERIFICATION_VERSION_CODE + = "android.content.pm.extra.VERIFICATION_VERSION_CODE"; + + /** * Retrieve overall information about an application package that is * installed on the system. * <p> @@ -1419,6 +1501,45 @@ public abstract class PackageManager { public abstract List<PackageInfo> getInstalledPackages(int flags); /** + * Return a List of all packages that are installed on the device, for a specific user. + * Requesting a list of installed packages for another user + * will require the permission INTERACT_ACROSS_USERS_FULL. + * @param flags Additional option flags. Use any combination of + * {@link #GET_ACTIVITIES}, + * {@link #GET_GIDS}, + * {@link #GET_CONFIGURATIONS}, + * {@link #GET_INSTRUMENTATION}, + * {@link #GET_PERMISSIONS}, + * {@link #GET_PROVIDERS}, + * {@link #GET_RECEIVERS}, + * {@link #GET_SERVICES}, + * {@link #GET_SIGNATURES}, + * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned. + * @param userId The user for whom the installed packages are to be listed + * + * @return A List of PackageInfo objects, one for each package that is + * installed on the device. In the unlikely case of there being no + * installed packages, an empty list is returned. + * If flag GET_UNINSTALLED_PACKAGES is set, a list of all + * applications including those deleted with DONT_DELETE_DATA + * (partially installed apps with data directory) will be returned. + * + * @see #GET_ACTIVITIES + * @see #GET_GIDS + * @see #GET_CONFIGURATIONS + * @see #GET_INSTRUMENTATION + * @see #GET_PERMISSIONS + * @see #GET_PROVIDERS + * @see #GET_RECEIVERS + * @see #GET_SERVICES + * @see #GET_SIGNATURES + * @see #GET_UNINSTALLED_PACKAGES + * + * @hide + */ + public abstract List<PackageInfo> getInstalledPackages(int flags, int userId); + + /** * Check whether a particular package has been granted a particular * permission. * @@ -1684,6 +1805,39 @@ public abstract class PackageManager { public abstract ResolveInfo resolveActivity(Intent intent, int flags); /** + * Determine the best action to perform for a given Intent for a given user. This + * is how {@link Intent#resolveActivity} finds an activity if a class has not + * been explicitly specified. + * + * <p><em>Note:</em> if using an implicit Intent (without an explicit ComponentName + * specified), be sure to consider whether to set the {@link #MATCH_DEFAULT_ONLY} + * only flag. You need to do so to resolve the activity in the same way + * that {@link android.content.Context#startActivity(Intent)} and + * {@link android.content.Intent#resolveActivity(PackageManager) + * Intent.resolveActivity(PackageManager)} do.</p> + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags. The most important is + * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only + * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}. + * @param userId The user id. + * + * @return Returns a ResolveInfo containing the final activity intent that + * was determined to be the best action. Returns null if no + * matching activity was found. If multiple matching activities are + * found and there is no default set, returns a ResolveInfo + * containing something else, such as the activity resolver. + * + * @see #MATCH_DEFAULT_ONLY + * @see #GET_INTENT_FILTERS + * @see #GET_RESOLVED_FILTER + * + * @hide + */ + public abstract ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId); + + /** * Retrieve all activities that can be performed for the given intent. * * @param intent The desired intent as per resolveActivity(). @@ -1705,6 +1859,29 @@ public abstract class PackageManager { int flags); /** + * Retrieve all activities that can be performed for the given intent, for a specific user. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags. The most important is + * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only + * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}. + * + * @return A List<ResolveInfo> containing one entry for each matching + * Activity. These are ordered from best to worst match -- that + * is, the first item in the list is what is returned by + * {@link #resolveActivity}. If there are no matching activities, an empty + * list is returned. + * + * @see #MATCH_DEFAULT_ONLY + * @see #GET_INTENT_FILTERS + * @see #GET_RESOLVED_FILTER + * @hide + */ + public abstract List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, + int flags, int userId); + + + /** * Retrieve a set of activities that should be presented to the user as * similar options. This is like {@link #queryIntentActivities}, except it * also allows you to supply a list of more explicit Intents that you would @@ -1754,6 +1931,26 @@ public abstract class PackageManager { int flags); /** + * Retrieve all receivers that can handle a broadcast of the given intent, for a specific + * user. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags. + * @param userId The userId of the user being queried. + * + * @return A List<ResolveInfo> containing one entry for each matching + * Receiver. These are ordered from first to last in priority. If + * there are no matching receivers, an empty list is returned. + * + * @see #MATCH_DEFAULT_ONLY + * @see #GET_INTENT_FILTERS + * @see #GET_RESOLVED_FILTER + * @hide + */ + public abstract List<ResolveInfo> queryBroadcastReceivers(Intent intent, + int flags, int userId); + + /** * Determine the best service to handle for a given Intent. * * @param intent An intent containing all of the desired specification @@ -1788,6 +1985,27 @@ public abstract class PackageManager { int flags); /** + * Retrieve all services that can match the given intent for a given user. + * + * @param intent The desired intent as per resolveService(). + * @param flags Additional option flags. + * @param userId The user id. + * + * @return A List<ResolveInfo> containing one entry for each matching + * ServiceInfo. These are ordered from best to worst match -- that + * is, the first item in the list is what is returned by + * resolveService(). If there are no matching services, an empty + * list is returned. + * + * @see #GET_INTENT_FILTERS + * @see #GET_RESOLVED_FILTER + * + * @hide + */ + public abstract List<ResolveInfo> queryIntentServicesAsUser(Intent intent, + int flags, int userId); + + /** * Find a single content provider by its base path name. * * @param name The name of the provider to find. @@ -2123,6 +2341,10 @@ public abstract class PackageManager { public abstract Resources getResourcesForApplication(String appPackageName) throws NameNotFoundException; + /** @hide */ + public abstract Resources getResourcesForApplicationAsUser(String appPackageName, int userId) + throws NameNotFoundException; + /** * Retrieve overall information about an application package defined * in a package archive file @@ -2166,8 +2388,8 @@ public abstract class PackageManager { if ((flags & GET_SIGNATURES) != 0) { packageParser.collectCertificates(pkg, 0); } - return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, false, - COMPONENT_ENABLED_STATE_DEFAULT); + PackageUserState state = new PackageUserState(); + return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); } /** @@ -2227,6 +2449,45 @@ public abstract class PackageManager { ContainerEncryptionParams encryptionParams); /** + * Similar to + * {@link #installPackage(Uri, IPackageInstallObserver, int, String)} but + * with an extra verification information provided. + * + * @param packageURI The location of the package file to install. This can + * be a 'file:' or a 'content:' URI. + * @param observer An observer callback to get notified when the package + * installation is complete. + * {@link IPackageInstallObserver#packageInstalled(String, int)} + * will be called when that happens. observer may be null to + * indicate that no callback is desired. + * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK}, + * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST} + * . + * @param installerPackageName Optional package name of the application that + * is performing the installation. This identifies which market + * the package came from. + * @param verificationParams an object that holds signal information to + * assist verification. May be {@code null}. + * @param encryptionParams if the package to be installed is encrypted, + * these parameters describing the encryption and authentication + * used. May be {@code null}. + * + * @hide + */ + public abstract void installPackageWithVerificationAndEncryption(Uri packageURI, + IPackageInstallObserver observer, int flags, String installerPackageName, + VerificationParams verificationParams, + ContainerEncryptionParams encryptionParams); + + /** + * If there is already an application with the given package name installed + * on the system for other users, also install it for the calling user. + * @hide + */ + public abstract int installExistingPackage(String packageName) + throws NameNotFoundException; + + /** * Allows a package listening to the * {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification * broadcast} to respond to the package manager. The response must include @@ -2238,10 +2499,47 @@ public abstract class PackageManager { * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra * @param verificationCode either {@link PackageManager#VERIFICATION_ALLOW} * or {@link PackageManager#VERIFICATION_REJECT}. + * @throws SecurityException if the caller does not have the + * PACKAGE_VERIFICATION_AGENT permission. */ public abstract void verifyPendingInstall(int id, int verificationCode); /** + * Allows a package listening to the + * {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification + * broadcast} to extend the default timeout for a response and declare what + * action to perform after the timeout occurs. The response must include + * the {@code verificationCodeAtTimeout} which is one of + * {@link PackageManager#VERIFICATION_ALLOW} or + * {@link PackageManager#VERIFICATION_REJECT}. + * + * This method may only be called once per package id. Additional calls + * will have no effect. + * + * @param id pending package identifier as passed via the + * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra + * @param verificationCodeAtTimeout either + * {@link PackageManager#VERIFICATION_ALLOW} or + * {@link PackageManager#VERIFICATION_REJECT}. If + * {@code verificationCodeAtTimeout} is neither + * {@link PackageManager#VERIFICATION_ALLOW} or + * {@link PackageManager#VERIFICATION_REJECT}, then + * {@code verificationCodeAtTimeout} will default to + * {@link PackageManager#VERIFICATION_REJECT}. + * @param millisecondsToDelay the amount of time requested for the timeout. + * Must be positive and less than + * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}. If + * {@code millisecondsToDelay} is out of bounds, + * {@code millisecondsToDelay} will be set to the closest in + * bounds value; namely, 0 or + * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}. + * @throws SecurityException if the caller does not have the + * PACKAGE_VERIFICATION_AGENT permission. + */ + public abstract void extendVerificationTimeout(int id, + int verificationCodeAtTimeout, long millisecondsToDelay); + + /** * Change the installer associated with a given package. There are limitations * on how the installer package can be changed; in particular: * <ul> @@ -2270,7 +2568,8 @@ public abstract class PackageManager { * @param observer An observer callback to get notified when the package deletion is * complete. {@link android.content.pm.IPackageDeleteObserver#packageDeleted(boolean)} will be * called when that happens. observer may be null to indicate that no callback is desired. - * @param flags - possible values: {@link #DONT_DELETE_DATA} + * @param flags - possible values: {@link #DELETE_KEEP_DATA}, + * {@link #DELETE_ALL_USERS}. * * @hide */ @@ -2376,6 +2675,7 @@ public abstract class PackageManager { * should have the {@link android.Manifest.permission#GET_PACKAGE_SIZE} permission. * * @param packageName The name of the package whose size information is to be retrieved + * @param userHandle The user whose size information should be retrieved. * @param observer An observer callback to get notified when the operation * is complete. * {@link android.content.pm.IPackageStatsObserver#onGetStatsCompleted(PackageStats, boolean)} @@ -2386,10 +2686,20 @@ public abstract class PackageManager { * * @hide */ - public abstract void getPackageSizeInfo(String packageName, + public abstract void getPackageSizeInfo(String packageName, int userHandle, IPackageStatsObserver observer); /** + * Like {@link #getPackageSizeInfo(String, int, IPackageStatsObserver)}, but + * returns the size for the calling user. + * + * @hide + */ + public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) { + getPackageSizeInfo(packageName, UserHandle.myUserId(), observer); + } + + /** * @deprecated This function no longer does anything; it was an old * approach to managing preferred activities, which has been superceeded * (and conflicts with) the modern activity-based preferences. @@ -2460,6 +2770,17 @@ public abstract class PackageManager { ComponentName[] set, ComponentName activity); /** + * Same as {@link #addPreferredActivity(IntentFilter, int, + ComponentName[], ComponentName)}, but with a specific userId to apply the preference + to. + * @hide + */ + public void addPreferredActivity(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity, int userId) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * @deprecated This is a protected API that should not have been available * to third party applications. It is the platform's responsibility for * assigning preferred activities and this can not be directly modified. @@ -2616,56 +2937,6 @@ public abstract class PackageManager { String packageName, IPackageMoveObserver observer, int flags); /** - * Creates a user with the specified name and options. - * - * @param name the user's name - * @param flags flags that identify the type of user and other properties. - * @see UserInfo - * - * @return the UserInfo object for the created user, or null if the user could not be created. - * @hide - */ - public abstract UserInfo createUser(String name, int flags); - - /** - * @return the list of users that were created - * @hide - */ - public abstract List<UserInfo> getUsers(); - - /** - * @param id the ID of the user, where 0 is the primary user. - * @hide - */ - public abstract boolean removeUser(int id); - - /** - * Updates the user's name. - * - * @param id the user's id - * @param name the new name for the user - * @hide - */ - public abstract void updateUserName(int id, String name); - - /** - * Changes the user's properties specified by the flags. - * - * @param id the user's id - * @param flags the new flags for the user - * @hide - */ - public abstract void updateUserFlags(int id, int flags); - - /** - * Returns the details for the user specified by userId. - * @param userId the user id of the user - * @return UserInfo for the specified user, or null if no such user exists. - * @hide - */ - public abstract UserInfo getUser(int userId); - - /** * Returns the device identity that verifiers can use to associate their scheme to a particular * device. This should not be used by anything other than a package verifier. * diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index f8898c1..c2b75f4 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -28,7 +28,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; -import android.os.UserId; +import android.os.UserHandle; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; @@ -203,11 +203,14 @@ public class PackageParser { */ public static class PackageLite { public final String packageName; + public final int versionCode; public final int installLocation; public final VerifierInfo[] verifiers; - public PackageLite(String packageName, int installLocation, List<VerifierInfo> verifiers) { + public PackageLite(String packageName, int versionCode, + int installLocation, List<VerifierInfo> verifiers) { this.packageName = packageName; + this.versionCode = versionCode; this.installLocation = installLocation; this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); } @@ -243,14 +246,15 @@ public class PackageParser { return name.endsWith(".apk"); } + /* public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet<String> grantedPermissions) { - + PackageUserState state = new PackageUserState(); return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, - grantedPermissions, false, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, - UserId.getCallingUserId()); + grantedPermissions, state, UserHandle.getCallingUserId()); } + */ /** * Generate and return the {@link PackageInfo} for a parsed package. @@ -260,23 +264,30 @@ public class PackageParser { */ public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, - HashSet<String> grantedPermissions, boolean stopped, int enabledState) { + HashSet<String> grantedPermissions, PackageUserState state) { return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, - grantedPermissions, stopped, enabledState, UserId.getCallingUserId()); + grantedPermissions, state, UserHandle.getCallingUserId()); + } + + private static boolean checkUseInstalled(int flags, PackageUserState state) { + return state.installed || ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0); } public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, - HashSet<String> grantedPermissions, boolean stopped, int enabledState, int userId) { + HashSet<String> grantedPermissions, PackageUserState state, int userId) { + if (!checkUseInstalled(flags, state)) { + return null; + } PackageInfo pi = new PackageInfo(); pi.packageName = p.packageName; pi.versionCode = p.mVersionCode; pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; pi.sharedUserLabel = p.mSharedUserLabel; - pi.applicationInfo = generateApplicationInfo(p, flags, stopped, enabledState, userId); + pi.applicationInfo = generateApplicationInfo(p, flags, state, userId); pi.installLocation = p.installLocation; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; @@ -312,7 +323,7 @@ public class PackageParser { if (activity.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags, - stopped, enabledState, userId); + state, userId); } } } @@ -334,7 +345,7 @@ public class PackageParser { if (activity.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags, - stopped, enabledState, userId); + state, userId); } } } @@ -355,8 +366,8 @@ public class PackageParser { final Service service = p.services.get(i); if (service.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.services[j++] = generateServiceInfo(p.services.get(i), flags, stopped, - enabledState, userId); + pi.services[j++] = generateServiceInfo(p.services.get(i), flags, + state, userId); } } } @@ -377,8 +388,8 @@ public class PackageParser { final Provider provider = p.providers.get(i); if (provider.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, stopped, - enabledState, userId); + pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, + state, userId); } } } @@ -840,11 +851,19 @@ public class PackageParser { return null; } int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; + int versionCode = 0; + int numFound = 0; for (int i = 0; i < attrs.getAttributeCount(); i++) { String attr = attrs.getAttributeName(i); if (attr.equals("installLocation")) { installLocation = attrs.getAttributeIntValue(i, PARSE_DEFAULT_INSTALL_LOCATION); + numFound++; + } else if (attr.equals("versionCode")) { + versionCode = attrs.getAttributeIntValue(i, 0); + numFound++; + } + if (numFound >= 2) { break; } } @@ -867,7 +886,7 @@ public class PackageParser { } } - return new PackageLite(pkgName.intern(), installLocation, verifiers); + return new PackageLite(pkgName.intern(), versionCode, installLocation, verifiers); } /** @@ -1468,7 +1487,8 @@ public class PackageParser { perm.info.descriptionRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestPermissionGroup_description, 0); - perm.info.flags = 0; + perm.info.flags = sa.getInt( + com.android.internal.R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags, 0); perm.info.priority = sa.getInt( com.android.internal.R.styleable.AndroidManifestPermissionGroup_priority, 0); if (perm.info.priority > 0 && (flags&PARSE_IS_SYSTEM) == 0) { @@ -1523,6 +1543,9 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestPermission_protectionLevel, PermissionInfo.PROTECTION_NORMAL); + perm.info.flags = sa.getInt( + com.android.internal.R.styleable.AndroidManifestPermission_permissionFlags, 0); + sa.recycle(); if (perm.info.protectionLevel == -1) { @@ -2040,7 +2063,7 @@ public class PackageParser { return null; } - final boolean setExported = sa.hasValue( + boolean setExported = sa.hasValue( com.android.internal.R.styleable.AndroidManifestActivity_exported); if (setExported) { a.info.exported = sa.getBoolean( @@ -2166,6 +2189,26 @@ public class PackageParser { a.info.configChanges = 0; } + if (receiver) { + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestActivity_singleUser, + false)) { + a.info.flags |= ActivityInfo.FLAG_SINGLE_USER; + if (a.info.exported) { + Slog.w(TAG, "Activity exported request ignored due to singleUser: " + + a.className + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + a.info.exported = false; + } + setExported = true; + } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly, + false)) { + a.info.flags |= ActivityInfo.FLAG_PRIMARY_USER_ONLY; + } + } + sa.recycle(); if (receiver && (owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { @@ -2428,8 +2471,18 @@ public class PackageParser { return null; } + boolean providerExportedDefault = false; + + if (owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) { + // For compatibility, applications targeting API level 16 or lower + // should have their content providers exported by default, unless they + // specify otherwise. + providerExportedDefault = true; + } + p.info.exported = sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestProvider_exported, true); + com.android.internal.R.styleable.AndroidManifestProvider_exported, + providerExportedDefault); String cpname = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestProvider_authorities, 0); @@ -2475,6 +2528,20 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_initOrder, 0); + p.info.flags = 0; + + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestProvider_singleUser, + false)) { + p.info.flags |= ProviderInfo.FLAG_SINGLE_USER; + if (p.info.exported) { + Slog.w(TAG, "Provider exported request ignored due to singleUser: " + + p.className + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + p.info.exported = false; + } + } + sa.recycle(); if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { @@ -2487,7 +2554,7 @@ public class PackageParser { } if (cpname == null) { - outError[0] = "<provider> does not incude authorities attribute"; + outError[0] = "<provider> does not include authorities attribute"; return null; } p.info.authority = cpname.intern(); @@ -2703,7 +2770,7 @@ public class PackageParser { return null; } - final boolean setExported = sa.hasValue( + boolean setExported = sa.hasValue( com.android.internal.R.styleable.AndroidManifestService_exported); if (setExported) { s.info.exported = sa.getBoolean( @@ -2729,6 +2796,18 @@ public class PackageParser { false)) { s.info.flags |= ServiceInfo.FLAG_ISOLATED_PROCESS; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestService_singleUser, + false)) { + s.info.flags |= ServiceInfo.FLAG_SINGLE_USER; + if (s.info.exported) { + Slog.w(TAG, "Service exported request ignored due to singleUser: " + + s.className + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + s.info.exported = false; + } + setExported = true; + } sa.recycle(); @@ -3407,13 +3486,25 @@ public class PackageParser { } } - private static boolean copyNeeded(int flags, Package p, int enabledState, Bundle metaData) { - if (enabledState != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { - boolean enabled = enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + private static boolean copyNeeded(int flags, Package p, + PackageUserState state, Bundle metaData, int userId) { + if (userId != 0) { + // We always need to copy for other users, since we need + // to fix up the uid. + return true; + } + if (state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + boolean enabled = state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; if (p.applicationInfo.enabled != enabled) { return true; } } + if (!state.installed) { + return true; + } + if (state.stopped) { + return true; + } if ((flags & PackageManager.GET_META_DATA) != 0 && (metaData != null || p.mAppMetaData != null)) { return true; @@ -3425,39 +3516,41 @@ public class PackageParser { return false; } - public static ApplicationInfo generateApplicationInfo(Package p, int flags, boolean stopped, - int enabledState) { - return generateApplicationInfo(p, flags, stopped, enabledState, UserId.getCallingUserId()); + public static ApplicationInfo generateApplicationInfo(Package p, int flags, + PackageUserState state) { + return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId()); } public static ApplicationInfo generateApplicationInfo(Package p, int flags, - boolean stopped, int enabledState, int userId) { + PackageUserState state, int userId) { if (p == null) return null; - if (!copyNeeded(flags, p, enabledState, null) && userId == 0) { + if (!checkUseInstalled(flags, state)) { + return null; + } + if (!copyNeeded(flags, p, state, null, userId)) { // CompatibilityMode is global state. It's safe to modify the instance // of the package. if (!sCompatibilityModeEnabled) { p.applicationInfo.disableCompatibilityMode(); } - if (stopped) { - p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED; - } else { - p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED; - } - if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + // Make sure we report as installed. Also safe to do, since the + // default state should be installed (we will always copy if we + // need to report it is not installed). + p.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; + if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { p.applicationInfo.enabled = true; - } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED - || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED + || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { p.applicationInfo.enabled = false; } - p.applicationInfo.enabledSetting = enabledState; + p.applicationInfo.enabledSetting = state.enabled; return p.applicationInfo; } // Make shallow copy so we can store the metadata/libraries safely ApplicationInfo ai = new ApplicationInfo(p.applicationInfo); if (userId != 0) { - ai.uid = UserId.getUid(userId, ai.uid); + ai.uid = UserHandle.getUid(userId, ai.uid); ai.dataDir = PackageManager.getDataDirForUser(userId, ai.packageName); } if ((flags & PackageManager.GET_META_DATA) != 0) { @@ -3469,18 +3562,23 @@ public class PackageParser { if (!sCompatibilityModeEnabled) { ai.disableCompatibilityMode(); } - if (stopped) { + if (state.stopped) { ai.flags |= ApplicationInfo.FLAG_STOPPED; } else { ai.flags &= ~ApplicationInfo.FLAG_STOPPED; } - if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + if (state.installed) { + ai.flags |= ApplicationInfo.FLAG_INSTALLED; + } else { + ai.flags &= ~ApplicationInfo.FLAG_INSTALLED; + } + if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { ai.enabled = true; - } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED - || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED + || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { ai.enabled = false; } - ai.enabledSetting = enabledState; + ai.enabledSetting = state.enabled; return ai; } @@ -3527,16 +3625,19 @@ public class PackageParser { } } - public static final ActivityInfo generateActivityInfo(Activity a, int flags, boolean stopped, - int enabledState, int userId) { + public static final ActivityInfo generateActivityInfo(Activity a, int flags, + PackageUserState state, int userId) { if (a == null) return null; - if (!copyNeeded(flags, a.owner, enabledState, a.metaData) && userId == 0) { + if (!checkUseInstalled(flags, state)) { + return null; + } + if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) { return a.info; } // Make shallow copies so we can store the metadata safely ActivityInfo ai = new ActivityInfo(a.info); ai.metaData = a.metaData; - ai.applicationInfo = generateApplicationInfo(a.owner, flags, stopped, enabledState, userId); + ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId); return ai; } @@ -3561,17 +3662,19 @@ public class PackageParser { } } - public static final ServiceInfo generateServiceInfo(Service s, int flags, boolean stopped, - int enabledState, int userId) { + public static final ServiceInfo generateServiceInfo(Service s, int flags, + PackageUserState state, int userId) { if (s == null) return null; - if (!copyNeeded(flags, s.owner, enabledState, s.metaData) - && userId == UserId.getUserId(s.info.applicationInfo.uid)) { + if (!checkUseInstalled(flags, state)) { + return null; + } + if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) { return s.info; } // Make shallow copies so we can store the metadata safely ServiceInfo si = new ServiceInfo(s.info); si.metaData = s.metaData; - si.applicationInfo = generateApplicationInfo(s.owner, flags, stopped, enabledState, userId); + si.applicationInfo = generateApplicationInfo(s.owner, flags, state, userId); return si; } @@ -3604,13 +3707,15 @@ public class PackageParser { } } - public static final ProviderInfo generateProviderInfo(Provider p, int flags, boolean stopped, - int enabledState, int userId) { + public static final ProviderInfo generateProviderInfo(Provider p, int flags, + PackageUserState state, int userId) { if (p == null) return null; - if (!copyNeeded(flags, p.owner, enabledState, p.metaData) + if (!checkUseInstalled(flags, state)) { + return null; + } + if (!copyNeeded(flags, p.owner, state, p.metaData, userId) && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0 - || p.info.uriPermissionPatterns == null) - && userId == 0) { + || p.info.uriPermissionPatterns == null)) { return p.info; } // Make shallow copies so we can store the metadata safely @@ -3619,7 +3724,7 @@ public class PackageParser { if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) { pi.uriPermissionPatterns = null; } - pi.applicationInfo = generateApplicationInfo(p.owner, flags, stopped, enabledState, userId); + pi.applicationInfo = generateApplicationInfo(p.owner, flags, state, userId); return pi; } diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java index 1205da7..cb9039b 100644 --- a/core/java/android/content/pm/PackageStats.java +++ b/core/java/android/content/pm/PackageStats.java @@ -18,6 +18,7 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; /** * implementation of PackageStats associated with a @@ -27,6 +28,9 @@ public class PackageStats implements Parcelable { /** Name of the package to which this stats applies. */ public String packageName; + /** @hide */ + public int userHandle; + /** Size of the code (e.g., APK) */ public long codeSize; @@ -78,33 +82,58 @@ public class PackageStats implements Parcelable { public String toString() { final StringBuilder sb = new StringBuilder("PackageStats{"); sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" packageName="); + sb.append(" "); sb.append(packageName); - sb.append(",codeSize="); - sb.append(codeSize); - sb.append(",dataSize="); - sb.append(dataSize); - sb.append(",cacheSize="); - sb.append(cacheSize); - sb.append(",externalCodeSize="); - sb.append(externalCodeSize); - sb.append(",externalDataSize="); - sb.append(externalDataSize); - sb.append(",externalCacheSize="); - sb.append(externalCacheSize); - sb.append(",externalMediaSize="); - sb.append(externalMediaSize); - sb.append(",externalObbSize="); - sb.append(externalObbSize); + if (codeSize != 0) { + sb.append(" code="); + sb.append(codeSize); + } + if (dataSize != 0) { + sb.append(" data="); + sb.append(dataSize); + } + if (cacheSize != 0) { + sb.append(" cache="); + sb.append(cacheSize); + } + if (externalCodeSize != 0) { + sb.append(" extCode="); + sb.append(externalCodeSize); + } + if (externalDataSize != 0) { + sb.append(" extData="); + sb.append(externalDataSize); + } + if (externalCacheSize != 0) { + sb.append(" extCache="); + sb.append(externalCacheSize); + } + if (externalMediaSize != 0) { + sb.append(" media="); + sb.append(externalMediaSize); + } + if (externalObbSize != 0) { + sb.append(" obb="); + sb.append(externalObbSize); + } + sb.append("}"); return sb.toString(); } public PackageStats(String pkgName) { packageName = pkgName; + userHandle = UserHandle.myUserId(); + } + + /** @hide */ + public PackageStats(String pkgName, int userHandle) { + this.packageName = pkgName; + this.userHandle = userHandle; } public PackageStats(Parcel source) { packageName = source.readString(); + userHandle = source.readInt(); codeSize = source.readLong(); dataSize = source.readLong(); cacheSize = source.readLong(); @@ -117,6 +146,7 @@ public class PackageStats implements Parcelable { public PackageStats(PackageStats pStats) { packageName = pStats.packageName; + userHandle = pStats.userHandle; codeSize = pStats.codeSize; dataSize = pStats.dataSize; cacheSize = pStats.cacheSize; @@ -133,6 +163,7 @@ public class PackageStats implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags){ dest.writeString(packageName); + dest.writeInt(userHandle); dest.writeLong(codeSize); dest.writeLong(dataSize); dest.writeLong(cacheSize); diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java new file mode 100644 index 0000000..7b3d8cd --- /dev/null +++ b/core/java/android/content/pm/PackageUserState.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + +import java.util.HashSet; + +/** + * Per-user state information about a package. + * @hide + */ +public class PackageUserState { + public boolean stopped; + public boolean notLaunched; + public boolean installed; + public int enabled; + + public HashSet<String> disabledComponents; + public HashSet<String> enabledComponents; + + public PackageUserState() { + this(true); + } + + /** @hide */ + public PackageUserState(boolean isSystem) { + if (!isSystem) { + stopped = notLaunched = true; + } + installed = true; + enabled = COMPONENT_ENABLED_STATE_DEFAULT; + } + + public PackageUserState(PackageUserState o) { + installed = o.installed; + stopped = o.stopped; + notLaunched = o.notLaunched; + enabled = o.enabled; + disabledComponents = o.disabledComponents != null + ? new HashSet<String>(o.disabledComponents) : null; + enabledComponents = o.enabledComponents != null + ? new HashSet<String>(o.enabledComponents) : null; + } +}
\ No newline at end of file diff --git a/core/java/android/content/pm/PermissionGroupInfo.java b/core/java/android/content/pm/PermissionGroupInfo.java index 96d30d4..452bf0d 100644 --- a/core/java/android/content/pm/PermissionGroupInfo.java +++ b/core/java/android/content/pm/PermissionGroupInfo.java @@ -44,20 +44,17 @@ public class PermissionGroupInfo extends PackageItemInfo implements Parcelable { /** * Flag for {@link #flags}, corresponding to <code>personalInfo</code> * value of {@link android.R.attr#permissionGroupFlags}. - * @hide */ public static final int FLAG_PERSONAL_INFO = 1<<0; /** * Additional flags about this group as given by * {@link android.R.attr#permissionGroupFlags}. - * @hide */ public int flags; /** * Prioritization of this group, for visually sorting with other groups. - * @hide */ public int priority; diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 69b812c..5a63e5f 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -79,11 +79,34 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public static final int PROTECTION_MASK_FLAGS = 0xf0; /** + * The level of access this permission is protecting, as per + * {@link android.R.attr#protectionLevel}. Values may be + * {@link #PROTECTION_NORMAL}, {@link #PROTECTION_DANGEROUS}, or + * {@link #PROTECTION_SIGNATURE}. May also include the additional + * flags {@link #PROTECTION_FLAG_SYSTEM} or {@link #PROTECTION_FLAG_DEVELOPMENT} + * (which only make sense in combination with the base + * {@link #PROTECTION_SIGNATURE}. + */ + public int protectionLevel; + + /** * The group this permission is a part of, as per * {@link android.R.attr#permissionGroup}. */ public String group; - + + /** + * Flag for {@link #flags}, corresponding to <code>costsMoney</code> + * value of {@link android.R.attr#permissionFlags}. + */ + public static final int FLAG_COSTS_MONEY = 1<<0; + + /** + * Additional flags about this permission as given by + * {@link android.R.attr#permissionFlags}. + */ + public int flags; + /** * A string resource identifier (in the package's resources) of this * permission's description. From the "description" attribute or, @@ -99,17 +122,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { */ public CharSequence nonLocalizedDescription; - /** - * The level of access this permission is protecting, as per - * {@link android.R.attr#protectionLevel}. Values may be - * {@link #PROTECTION_NORMAL}, {@link #PROTECTION_DANGEROUS}, or - * {@link #PROTECTION_SIGNATURE}. May also include the additional - * flags {@link #PROTECTION_FLAG_SYSTEM} or {@link #PROTECTION_FLAG_DEVELOPMENT} - * (which only make sense in combination with the base - * {@link #PROTECTION_SIGNATURE}. - */ - public int protectionLevel; - /** @hide */ public static int fixProtectionLevel(int level) { if (level == PROTECTION_SIGNATURE_OR_SYSTEM) { @@ -149,9 +161,10 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public PermissionInfo(PermissionInfo orig) { super(orig); + protectionLevel = orig.protectionLevel; + flags = orig.flags; group = orig.group; descriptionRes = orig.descriptionRes; - protectionLevel = orig.protectionLevel; nonLocalizedDescription = orig.nonLocalizedDescription; } @@ -191,9 +204,10 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { super.writeToParcel(dest, parcelableFlags); + dest.writeInt(protectionLevel); + dest.writeInt(flags); dest.writeString(group); dest.writeInt(descriptionRes); - dest.writeInt(protectionLevel); TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags); } @@ -209,9 +223,10 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { private PermissionInfo(Parcel source) { super(source); + protectionLevel = source.readInt(); + flags = source.readInt(); group = source.readString(); descriptionRes = source.readInt(); - protectionLevel = source.readInt(); nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); } } diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index ec01775..a534176 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -73,7 +73,21 @@ public final class ProviderInfo extends ComponentInfo /** Used to control initialization order of single-process providers * running in the same process. Higher goes first. */ public int initOrder = 0; - + + /** + * Bit in {@link #flags}: If set, a single instance of the provider will + * run for all users on the device. Set from the + * {@link android.R.attr#singleUser} attribute. + */ + public static final int FLAG_SINGLE_USER = 0x40000000; + + /** + * Options that have been set in the provider declaration in the + * manifest. + * These include: {@link #FLAG_SINGLE_USER}. + */ + public int flags = 0; + /** * Whether or not this provider is syncable. * @deprecated This flag is now being ignored. The current way to make a provider @@ -95,6 +109,7 @@ public final class ProviderInfo extends ComponentInfo pathPermissions = orig.pathPermissions; multiprocess = orig.multiprocess; initOrder = orig.initOrder; + flags = orig.flags; isSyncable = orig.isSyncable; } @@ -112,6 +127,7 @@ public final class ProviderInfo extends ComponentInfo out.writeTypedArray(pathPermissions, parcelableFlags); out.writeInt(multiprocess ? 1 : 0); out.writeInt(initOrder); + out.writeInt(flags); out.writeInt(isSyncable ? 1 : 0); } @@ -127,8 +143,7 @@ public final class ProviderInfo extends ComponentInfo }; public String toString() { - return "ContentProviderInfo{name=" + authority + " className=" + name - + " isSyncable=" + (isSyncable ? "true" : "false") + "}"; + return "ContentProviderInfo{name=" + authority + " className=" + name + "}"; } private ProviderInfo(Parcel in) { @@ -141,6 +156,7 @@ public final class ProviderInfo extends ComponentInfo pathPermissions = in.createTypedArray(PathPermission.CREATOR); multiprocess = in.readInt() != 0; initOrder = in.readInt(); + flags = in.readInt(); isSyncable = in.readInt() != 0; } } diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index d8f9204..7642670 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.os.Environment; import android.os.Handler; +import android.util.AtomicFile; import android.util.Log; import android.util.AttributeSet; import android.util.Xml; @@ -44,7 +45,6 @@ import java.io.PrintWriter; import java.io.IOException; import java.io.FileInputStream; -import com.android.internal.os.AtomicFile; import com.android.internal.util.FastXmlSerializer; import com.google.android.collect.Maps; @@ -331,12 +331,16 @@ public abstract class RegisteredServicesCache<V> { notifyListener(v1, true /* removed */); } if (changes.length() > 0) { - Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + - serviceInfos.size() + " services:\n" + changes); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + + serviceInfos.size() + " services:\n" + changes); + } writePersistentServicesLocked(); } else { - Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + - serviceInfos.size() + " services unchanged"); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + + serviceInfos.size() + " services unchanged"); + } } mPersistentServicesFileDidNotExist = false; } diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index e3749b4..07117fe 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -230,6 +230,21 @@ public class ResolveInfo implements Parcelable { public ResolveInfo() { } + public ResolveInfo(ResolveInfo orig) { + activityInfo = orig.activityInfo; + serviceInfo = orig.serviceInfo; + filter = orig.filter; + priority = orig.priority; + preferredOrder = orig.preferredOrder; + match = orig.match; + specificIndex = orig.specificIndex; + labelRes = orig.labelRes; + nonLocalizedLabel = orig.nonLocalizedLabel; + icon = orig.icon; + resolvePackageName = orig.resolvePackageName; + system = orig.system; + } + public String toString() { ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo; return "ResolveInfo{" diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 7ee84ab..796c2a4 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -49,10 +49,18 @@ public class ServiceInfo extends ComponentInfo public static final int FLAG_ISOLATED_PROCESS = 0x0002; /** + * Bit in {@link #flags}: If set, a single instance of the service will + * run for all users on the device. Set from the + * {@link android.R.attr#singleUser} attribute. + */ + public static final int FLAG_SINGLE_USER = 0x40000000; + + /** * Options that have been set in the service declaration in the * manifest. * These include: - * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS}. + * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS}, + * {@link #FLAG_SINGLE_USER}. */ public int flags; diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index ba5331c..593f826 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -18,12 +18,23 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; /** * Per-user information. * @hide */ public class UserInfo implements Parcelable { + + /** 6 bits for user type */ + public static final int FLAG_MASK_USER_TYPE = 0x0000003F; + + /** + * *************************** NOTE *************************** + * These flag values CAN NOT CHANGE because they are written + * directly to storage. + */ + /** * Primary user. Only one user can have this flag set. Meaning of this * flag TBD. @@ -41,14 +52,37 @@ public class UserInfo implements Parcelable { */ public static final int FLAG_GUEST = 0x00000004; + /** + * Indicates the user has restrictions in privileges, in addition to those for normal users. + * Exact meaning TBD. For instance, maybe they can't install apps or administer WiFi access pts. + */ + public static final int FLAG_RESTRICTED = 0x00000008; + + /** + * Indicates that this user has gone through its first-time initialization. + */ + public static final int FLAG_INITIALIZED = 0x00000010; + public int id; + public int serialNumber; public String name; + public String iconPath; public int flags; + public long creationTime; + public long lastLoggedInTime; + + /** User is only partially created. */ + public boolean partial; public UserInfo(int id, String name, int flags) { + this(id, name, null, flags); + } + + public UserInfo(int id, String name, String iconPath, int flags) { this.id = id; this.name = name; this.flags = flags; + this.iconPath = iconPath; } public boolean isPrimary() { @@ -68,8 +102,17 @@ public class UserInfo implements Parcelable { public UserInfo(UserInfo orig) { name = orig.name; + iconPath = orig.iconPath; id = orig.id; flags = orig.flags; + serialNumber = orig.serialNumber; + creationTime = orig.creationTime; + lastLoggedInTime = orig.lastLoggedInTime; + partial = orig.partial; + } + + public UserHandle getUserHandle() { + return new UserHandle(id); } @Override @@ -84,7 +127,12 @@ public class UserInfo implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(id); dest.writeString(name); + dest.writeString(iconPath); dest.writeInt(flags); + dest.writeInt(serialNumber); + dest.writeLong(creationTime); + dest.writeLong(lastLoggedInTime); + dest.writeInt(partial ? 1 : 0); } public static final Parcelable.Creator<UserInfo> CREATOR @@ -100,6 +148,11 @@ public class UserInfo implements Parcelable { private UserInfo(Parcel source) { id = source.readInt(); name = source.readString(); + iconPath = source.readString(); flags = source.readInt(); + serialNumber = source.readInt(); + creationTime = source.readLong(); + lastLoggedInTime = source.readLong(); + partial = source.readInt() != 0; } } diff --git a/core/java/android/content/pm/VerificationParams.aidl b/core/java/android/content/pm/VerificationParams.aidl new file mode 100644 index 0000000..5bb7f69 --- /dev/null +++ b/core/java/android/content/pm/VerificationParams.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +parcelable VerificationParams; diff --git a/core/java/android/content/pm/VerificationParams.java b/core/java/android/content/pm/VerificationParams.java new file mode 100644 index 0000000..22e1a85 --- /dev/null +++ b/core/java/android/content/pm/VerificationParams.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.content.pm.ManifestDigest; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents verification parameters used to verify packages to be installed. + * + * @hide + */ +public class VerificationParams implements Parcelable { + /** A constant used to indicate that a uid value is not present. */ + public static final int NO_UID = -1; + + /** What we print out first when toString() is called. */ + private static final String TO_STRING_PREFIX = "VerificationParams{"; + + /** The location of the supplementary verification file. */ + private final Uri mVerificationURI; + + /** URI referencing where the package was downloaded from. */ + private final Uri mOriginatingURI; + + /** HTTP referrer URI associated with the originatingURI. */ + private final Uri mReferrer; + + /** UID of the application that the install request originated from. */ + private final int mOriginatingUid; + + /** UID of application requesting the install */ + private int mInstallerUid; + + /** + * An object that holds the digest of the package which can be used to + * verify ownership. + */ + private final ManifestDigest mManifestDigest; + + /** + * Creates verification specifications for installing with application verification. + * + * @param verificationURI The location of the supplementary verification + * file. This can be a 'file:' or a 'content:' URI. May be {@code null}. + * @param originatingURI URI referencing where the package was downloaded + * from. May be {@code null}. + * @param referrer HTTP referrer URI associated with the originatingURI. + * May be {@code null}. + * @param originatingUid UID of the application that the install request originated + * from, or NO_UID if not present + * @param manifestDigest an object that holds the digest of the package + * which can be used to verify ownership. May be {@code null}. + */ + public VerificationParams(Uri verificationURI, Uri originatingURI, Uri referrer, + int originatingUid, ManifestDigest manifestDigest) { + mVerificationURI = verificationURI; + mOriginatingURI = originatingURI; + mReferrer = referrer; + mOriginatingUid = originatingUid; + mManifestDigest = manifestDigest; + mInstallerUid = NO_UID; + } + + public Uri getVerificationURI() { + return mVerificationURI; + } + + public Uri getOriginatingURI() { + return mOriginatingURI; + } + + public Uri getReferrer() { + return mReferrer; + } + + /** return NO_UID if not available */ + public int getOriginatingUid() { + return mOriginatingUid; + } + + public ManifestDigest getManifestDigest() { + return mManifestDigest; + } + + /** @return NO_UID when not set */ + public int getInstallerUid() { + return mInstallerUid; + } + + public void setInstallerUid(int uid) { + mInstallerUid = uid; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof VerificationParams)) { + return false; + } + + final VerificationParams other = (VerificationParams) o; + + if (mVerificationURI == null) { + if (other.mVerificationURI != null) { + return false; + } + } else if (!mVerificationURI.equals(other.mVerificationURI)) { + return false; + } + + if (mOriginatingURI == null) { + if (other.mOriginatingURI != null) { + return false; + } + } else if (!mOriginatingURI.equals(other.mOriginatingURI)) { + return false; + } + + if (mReferrer == null) { + if (other.mReferrer != null) { + return false; + } + } else if (!mReferrer.equals(other.mReferrer)) { + return false; + } + + if (mOriginatingUid != other.mOriginatingUid) { + return false; + } + + if (mManifestDigest == null) { + if (other.mManifestDigest != null) { + return false; + } + } else if (!mManifestDigest.equals(other.mManifestDigest)) { + return false; + } + + if (mInstallerUid != other.mInstallerUid) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int hash = 3; + + hash += 5 * (mVerificationURI == null ? 1 : mVerificationURI.hashCode()); + hash += 7 * (mOriginatingURI == null ? 1 : mOriginatingURI.hashCode()); + hash += 11 * (mReferrer == null ? 1 : mReferrer.hashCode()); + hash += 13 * mOriginatingUid; + hash += 17 * (mManifestDigest == null ? 1 : mManifestDigest.hashCode()); + hash += 19 * mInstallerUid; + + return hash; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(TO_STRING_PREFIX); + + sb.append("mVerificationURI="); + sb.append(mVerificationURI.toString()); + sb.append(",mOriginatingURI="); + sb.append(mOriginatingURI.toString()); + sb.append(",mReferrer="); + sb.append(mReferrer.toString()); + sb.append(",mOriginatingUid="); + sb.append(mOriginatingUid); + sb.append(",mManifestDigest="); + sb.append(mManifestDigest.toString()); + sb.append(",mInstallerUid="); + sb.append(mInstallerUid); + sb.append('}'); + + return sb.toString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mVerificationURI, 0); + dest.writeParcelable(mOriginatingURI, 0); + dest.writeParcelable(mReferrer, 0); + dest.writeInt(mOriginatingUid); + dest.writeParcelable(mManifestDigest, 0); + dest.writeInt(mInstallerUid); + } + + + private VerificationParams(Parcel source) { + mVerificationURI = source.readParcelable(Uri.class.getClassLoader()); + mOriginatingURI = source.readParcelable(Uri.class.getClassLoader()); + mReferrer = source.readParcelable(Uri.class.getClassLoader()); + mOriginatingUid = source.readInt(); + mManifestDigest = source.readParcelable(ManifestDigest.class.getClassLoader()); + mInstallerUid = source.readInt(); + } + + public static final Parcelable.Creator<VerificationParams> CREATOR = + new Parcelable.Creator<VerificationParams>() { + public VerificationParams createFromParcel(Parcel source) { + return new VerificationParams(source); + } + + public VerificationParams[] newArray(int size) { + return new VerificationParams[size]; + } + }; +} diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 1c9285e..28c751c 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -439,7 +439,7 @@ public class CompatibilityInfo implements Parcelable { if (isScalingRequired()) { float invertedRatio = applicationInvertedScale; inoutDm.density = inoutDm.noncompatDensity * invertedRatio; - inoutDm.densityDpi = (int)((inoutDm.density*DisplayMetrics.DENSITY_DEFAULT)+.5f); + inoutDm.densityDpi = (int)((inoutDm.noncompatDensityDpi * invertedRatio) + .5f); inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio; inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio; inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio; @@ -448,7 +448,7 @@ public class CompatibilityInfo implements Parcelable { } } - public void applyToConfiguration(Configuration inoutConfig) { + public void applyToConfiguration(int displayDensity, Configuration inoutConfig) { if (!supportsScreen()) { // This is a larger screen device and the app is not // compatible with large screens, so we are forcing it to @@ -460,6 +460,11 @@ public class CompatibilityInfo implements Parcelable { inoutConfig.screenHeightDp = inoutConfig.compatScreenHeightDp; inoutConfig.smallestScreenWidthDp = inoutConfig.compatSmallestScreenWidthDp; } + inoutConfig.densityDpi = displayDensity; + if (isScalingRequired()) { + float invertedRatio = applicationInvertedScale; + inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f); + } } /** diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 423b9af..86d6ee7 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -19,7 +19,7 @@ package android.content.res; import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; -import android.util.LocaleUtil; +import android.text.TextUtils; import android.view.View; import java.util.Locale; @@ -35,6 +35,9 @@ import java.util.Locale; * <pre>Configuration config = getResources().getConfiguration();</pre> */ public final class Configuration implements Parcelable, Comparable<Configuration> { + /** @hide */ + public static final Configuration EMPTY = new Configuration(); + /** * Current user preference for the scaling factor for fonts, relative * to the base density scaling. @@ -122,7 +125,25 @@ public final class Configuration implements Parcelable, Comparable<Configuration * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenAspectQualifier">long</a> * resource qualifier. */ public static final int SCREENLAYOUT_LONG_YES = 0x20; - + + /** Constant for {@link #screenLayout}: bits that encode the layout direction. */ + public static final int SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0; + /** Constant for {@link #screenLayout}: bits shift to get the layout direction. */ + public static final int SCREENLAYOUT_LAYOUTDIR_SHIFT = 6; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK} + * value indicating that no layout dir has been set. */ + public static final int SCREENLAYOUT_LAYOUTDIR_UNDEFINED = 0x00; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK} + * value indicating that a layout dir has been set to LTR. */ + public static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x01 << SCREENLAYOUT_LAYOUTDIR_SHIFT; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK} + * value indicating that a layout dir has been set to RTL. */ + public static final int SCREENLAYOUT_LAYOUTDIR_RTL = 0x02 << SCREENLAYOUT_LAYOUTDIR_SHIFT; + + /** Constant for {@link #screenLayout}: a value indicating that screenLayout is undefined */ + public static final int SCREENLAYOUT_UNDEFINED = SCREENLAYOUT_SIZE_UNDEFINED | + SCREENLAYOUT_LONG_UNDEFINED | SCREENLAYOUT_LAYOUTDIR_UNDEFINED; + /** * Special flag we generate to indicate that the screen layout requires * us to use a compatibility mode for apps that are not modern layout @@ -143,11 +164,85 @@ public final class Configuration implements Parcelable, Comparable<Configuration * is wider/taller than normal. They may be one of * {@link #SCREENLAYOUT_LONG_NO} or {@link #SCREENLAYOUT_LONG_YES}. * + * <p>The {@link #SCREENLAYOUT_LAYOUTDIR_MASK} defines whether the screen layout + * is either LTR or RTL. They may be one of + * {@link #SCREENLAYOUT_LAYOUTDIR_LTR} or {@link #SCREENLAYOUT_LAYOUTDIR_RTL}. + * * <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting * Multiple Screens</a> for more information. */ public int screenLayout; - + + /** @hide */ + static public int resetScreenLayout(int curLayout) { + return (curLayout&~(SCREENLAYOUT_LONG_MASK | SCREENLAYOUT_SIZE_MASK + | SCREENLAYOUT_COMPAT_NEEDED)) + | (SCREENLAYOUT_LONG_YES | SCREENLAYOUT_SIZE_XLARGE); + } + + /** @hide */ + static public int reduceScreenLayout(int curLayout, int longSizeDp, int shortSizeDp) { + int screenLayoutSize; + boolean screenLayoutLong; + boolean screenLayoutCompatNeeded; + + // These semi-magic numbers define our compatibility modes for + // applications with different screens. These are guarantees to + // app developers about the space they can expect for a particular + // configuration. DO NOT CHANGE! + if (longSizeDp < 470) { + // This is shorter than an HVGA normal density screen (which + // is 480 pixels on its long side). + screenLayoutSize = SCREENLAYOUT_SIZE_SMALL; + screenLayoutLong = false; + screenLayoutCompatNeeded = false; + } else { + // What size is this screen screen? + if (longSizeDp >= 960 && shortSizeDp >= 720) { + // 1.5xVGA or larger screens at medium density are the point + // at which we consider it to be an extra large screen. + screenLayoutSize = SCREENLAYOUT_SIZE_XLARGE; + } else if (longSizeDp >= 640 && shortSizeDp >= 480) { + // VGA or larger screens at medium density are the point + // at which we consider it to be a large screen. + screenLayoutSize = SCREENLAYOUT_SIZE_LARGE; + } else { + screenLayoutSize = SCREENLAYOUT_SIZE_NORMAL; + } + + // If this screen is wider than normal HVGA, or taller + // than FWVGA, then for old apps we want to run in size + // compatibility mode. + if (shortSizeDp > 321 || longSizeDp > 570) { + screenLayoutCompatNeeded = true; + } else { + screenLayoutCompatNeeded = false; + } + + // Is this a long screen? + if (((longSizeDp*3)/5) >= (shortSizeDp-1)) { + // Anything wider than WVGA (5:3) is considering to be long. + screenLayoutLong = true; + } else { + screenLayoutLong = false; + } + } + + // Now reduce the last screenLayout to not be better than what we + // have found. + if (!screenLayoutLong) { + curLayout = (curLayout&~SCREENLAYOUT_LONG_MASK) | SCREENLAYOUT_LONG_NO; + } + if (screenLayoutCompatNeeded) { + curLayout |= Configuration.SCREENLAYOUT_COMPAT_NEEDED; + } + int curSize = curLayout&SCREENLAYOUT_SIZE_MASK; + if (screenLayoutSize < curSize) { + curLayout = (curLayout&~SCREENLAYOUT_SIZE_MASK) | screenLayoutSize; + } + return curLayout; + } + /** * Check if the Configuration's current {@link #screenLayout} is at * least the given size. @@ -369,26 +464,40 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public int uiMode; + /** + * Default value for {@link #screenWidthDp} indicating that no width + * has been specified. + */ public static final int SCREEN_WIDTH_DP_UNDEFINED = 0; /** * The current width of the available screen space, in dp units, * corresponding to * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenWidthQualifier">screen - * width</a> resource qualifier. + * width</a> resource qualifier. Set to + * {@link #SCREEN_WIDTH_DP_UNDEFINED} if no width is specified. */ public int screenWidthDp; + /** + * Default value for {@link #screenHeightDp} indicating that no width + * has been specified. + */ public static final int SCREEN_HEIGHT_DP_UNDEFINED = 0; /** * The current height of the available screen space, in dp units, * corresponding to * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenHeightQualifier">screen - * height</a> resource qualifier. + * height</a> resource qualifier. Set to + * {@link #SCREEN_HEIGHT_DP_UNDEFINED} if no height is specified. */ public int screenHeightDp; + /** + * Default value for {@link #smallestScreenWidthDp} indicating that no width + * has been specified. + */ public static final int SMALLEST_SCREEN_WIDTH_DP_UNDEFINED = 0; /** @@ -397,10 +506,26 @@ public final class Configuration implements Parcelable, Comparable<Configuration * <a href="{@docRoot}guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier">smallest * screen width</a> resource qualifier. * This is the smallest value of both screenWidthDp and screenHeightDp - * in both portrait and landscape. + * in both portrait and landscape. Set to + * {@link #SMALLEST_SCREEN_WIDTH_DP_UNDEFINED} if no width is specified. */ public int smallestScreenWidthDp; + /** + * Default value for {@link #densityDpi} indicating that no width + * has been specified. + */ + public static final int DENSITY_DPI_UNDEFINED = 0; + + /** + * The target screen density being rendered to, + * corresponding to + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#DensityQualifier">density</a> + * resource qualifier. Set to + * {@link #DENSITY_DPI_UNDEFINED} if no density is specified. + */ + public int densityDpi; + /** @hide Hack to get this information from WM to app running in compat mode. */ public int compatScreenWidthDp; /** @hide Hack to get this information from WM to app running in compat mode. */ @@ -409,11 +534,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration public int compatSmallestScreenWidthDp; /** - * @hide The layout direction associated to the current Locale - */ - public int layoutDirection; - - /** * @hide Internal book-keeping. */ public int seq; @@ -439,7 +559,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration mnc = o.mnc; if (o.locale != null) { locale = (Locale) o.locale.clone(); - layoutDirection = o.layoutDirection; } userSetLocale = o.userSetLocale; touchscreen = o.touchscreen; @@ -454,6 +573,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration screenWidthDp = o.screenWidthDp; screenHeightDp = o.screenHeightDp; smallestScreenWidthDp = o.smallestScreenWidthDp; + densityDpi = o.densityDpi; compatScreenWidthDp = o.compatScreenWidthDp; compatScreenHeightDp = o.compatScreenHeightDp; compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp; @@ -465,20 +585,31 @@ public final class Configuration implements Parcelable, Comparable<Configuration sb.append("{"); sb.append(fontScale); sb.append(" "); - sb.append(mcc); - sb.append("mcc"); - sb.append(mnc); - sb.append("mnc"); + if (mcc != 0) { + sb.append(mcc); + sb.append("mcc"); + } else { + sb.append("?mcc"); + } + if (mnc != 0) { + sb.append(mnc); + sb.append("mnc"); + } else { + sb.append("?mnc"); + } if (locale != null) { sb.append(" "); sb.append(locale); } else { - sb.append(" (no locale)"); + sb.append(" ?locale"); } - switch (layoutDirection) { - case View.LAYOUT_DIRECTION_LTR: /* ltr not interesting */ break; - case View.LAYOUT_DIRECTION_RTL: sb.append(" rtl"); break; - default: sb.append(" layoutDir="); sb.append(layoutDirection); break; + int layoutDir = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK); + switch (layoutDir) { + case SCREENLAYOUT_LAYOUTDIR_UNDEFINED: sb.append(" ?layoutDir"); break; + case SCREENLAYOUT_LAYOUTDIR_LTR: sb.append(" ldltr"); break; + case SCREENLAYOUT_LAYOUTDIR_RTL: sb.append(" ldrtl"); break; + default: sb.append(" layoutDir="); + sb.append(layoutDir >> SCREENLAYOUT_LAYOUTDIR_SHIFT); break; } if (smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { sb.append(" sw"); sb.append(smallestScreenWidthDp); sb.append("dp"); @@ -495,6 +626,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration } else { sb.append(" ?hdp"); } + if (densityDpi != DENSITY_DPI_UNDEFINED) { + sb.append(" "); sb.append(densityDpi); sb.append("dpi"); + } else { + sb.append(" ?density"); + } switch ((screenLayout&SCREENLAYOUT_SIZE_MASK)) { case SCREENLAYOUT_SIZE_UNDEFINED: sb.append(" ?lsize"); break; case SCREENLAYOUT_SIZE_SMALL: sb.append(" smll"); break; @@ -596,12 +732,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration navigation = NAVIGATION_UNDEFINED; navigationHidden = NAVIGATIONHIDDEN_UNDEFINED; orientation = ORIENTATION_UNDEFINED; - screenLayout = SCREENLAYOUT_SIZE_UNDEFINED; + screenLayout = SCREENLAYOUT_UNDEFINED; uiMode = UI_MODE_TYPE_UNDEFINED; screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; - layoutDirection = View.LAYOUT_DIRECTION_LTR; + densityDpi = DENSITY_DPI_UNDEFINED; seq = 0; } @@ -637,7 +773,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration changed |= ActivityInfo.CONFIG_LOCALE; locale = delta.locale != null ? (Locale) delta.locale.clone() : null; - layoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale); + // If locale has changed, then layout direction is also changed ... + changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION; + // ... and we need to update the layout direction (represented by the first + // 2 most significant bits in screenLayout). + setLayoutDirection(locale); } if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0))) { @@ -679,10 +819,17 @@ public final class Configuration implements Parcelable, Comparable<Configuration changed |= ActivityInfo.CONFIG_ORIENTATION; orientation = delta.orientation; } - if (delta.screenLayout != SCREENLAYOUT_SIZE_UNDEFINED - && screenLayout != delta.screenLayout) { + if (getScreenLayoutNoDirection(delta.screenLayout) != + (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED) + && (getScreenLayoutNoDirection(screenLayout) != + getScreenLayoutNoDirection(delta.screenLayout))) { changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; - screenLayout = delta.screenLayout; + // We need to preserve the previous layout dir bits if they were defined + if ((delta.screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK) == 0) { + screenLayout = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK)|delta.screenLayout; + } else { + screenLayout = delta.screenLayout; + } } if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED) && uiMode != delta.uiMode) { @@ -707,8 +854,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration screenHeightDp = delta.screenHeightDp; } if (delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { + changed |= ActivityInfo.CONFIG_SCREEN_SIZE; smallestScreenWidthDp = delta.smallestScreenWidthDp; } + if (delta.densityDpi != DENSITY_DPI_UNDEFINED) { + changed |= ActivityInfo.CONFIG_DENSITY; + densityDpi = delta.densityDpi; + } if (delta.compatScreenWidthDp != SCREEN_WIDTH_DP_UNDEFINED) { compatScreenWidthDp = delta.compatScreenWidthDp; } @@ -718,7 +870,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration if (delta.compatSmallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { compatSmallestScreenWidthDp = delta.compatSmallestScreenWidthDp; } - if (delta.seq != 0) { seq = delta.seq; } @@ -754,6 +905,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration * PackageManager.ActivityInfo.CONFIG_SCREEN_SIZE}, or * {@link android.content.pm.ActivityInfo#CONFIG_SMALLEST_SCREEN_SIZE * PackageManager.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE}. + * {@link android.content.pm.ActivityInfo#CONFIG_LAYOUT_DIRECTION + * PackageManager.ActivityInfo.CONFIG_LAYOUT_DIRECTION}. */ public int diff(Configuration delta) { int changed = 0; @@ -769,6 +922,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration if (delta.locale != null && (locale == null || !locale.equals(delta.locale))) { changed |= ActivityInfo.CONFIG_LOCALE; + changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION; } if (delta.touchscreen != TOUCHSCREEN_UNDEFINED && touchscreen != delta.touchscreen) { @@ -798,8 +952,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration && orientation != delta.orientation) { changed |= ActivityInfo.CONFIG_ORIENTATION; } - if (delta.screenLayout != SCREENLAYOUT_SIZE_UNDEFINED - && screenLayout != delta.screenLayout) { + if (getScreenLayoutNoDirection(delta.screenLayout) != + (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED) + && getScreenLayoutNoDirection(screenLayout) != + getScreenLayoutNoDirection(delta.screenLayout)) { changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; } if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED) @@ -818,7 +974,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration && smallestScreenWidthDp != delta.smallestScreenWidthDp) { changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } - + if (delta.densityDpi != DENSITY_DPI_UNDEFINED + && densityDpi != delta.densityDpi) { + changed |= ActivityInfo.CONFIG_DENSITY; + } + return changed; } @@ -902,10 +1062,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(screenWidthDp); dest.writeInt(screenHeightDp); dest.writeInt(smallestScreenWidthDp); + dest.writeInt(densityDpi); dest.writeInt(compatScreenWidthDp); dest.writeInt(compatScreenHeightDp); dest.writeInt(compatSmallestScreenWidthDp); - dest.writeInt(layoutDirection); dest.writeInt(seq); } @@ -930,10 +1090,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration screenWidthDp = source.readInt(); screenHeightDp = source.readInt(); smallestScreenWidthDp = source.readInt(); + densityDpi = source.readInt(); compatScreenWidthDp = source.readInt(); compatScreenHeightDp = source.readInt(); compatSmallestScreenWidthDp = source.readInt(); - layoutDirection = source.readInt(); seq = source.readInt(); } @@ -1000,6 +1160,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration n = this.screenHeightDp - that.screenHeightDp; if (n != 0) return n; n = this.smallestScreenWidthDp - that.smallestScreenWidthDp; + if (n != 0) return n; + n = this.densityDpi - that.densityDpi; //if (n != 0) return n; return n; } @@ -1036,6 +1198,53 @@ public final class Configuration implements Parcelable, Comparable<Configuration result = 31 * result + screenWidthDp; result = 31 * result + screenHeightDp; result = 31 * result + smallestScreenWidthDp; + result = 31 * result + densityDpi; return result; } + + /** + * Set the locale. This is the preferred way for setting up the locale (instead of using the + * direct accessor). This will also set the userLocale and layout direction according to + * the locale. + * + * @param loc The locale. Can be null. + */ + public void setLocale(Locale loc) { + locale = loc; + userSetLocale = true; + setLayoutDirection(locale); + } + + /** + * Return the layout direction. Will be either {@link View#LAYOUT_DIRECTION_LTR} or + * {@link View#LAYOUT_DIRECTION_RTL}. + * + * @return the layout direction + */ + public int getLayoutDirection() { + // We need to substract one here as the configuration values are using "0" as undefined thus + // having LRT set to "1" and RTL set to "2" + return ((screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK) >> SCREENLAYOUT_LAYOUTDIR_SHIFT) - 1; + } + + /** + * Set the layout direction from the Locale. + * + * @param locale The Locale. If null will set the layout direction to + * {@link View#LAYOUT_DIRECTION_LTR}. If not null will set it to the layout direction + * corresponding to the Locale. + * + * @see {@link View#LAYOUT_DIRECTION_LTR} and {@link View#LAYOUT_DIRECTION_RTL} + */ + public void setLayoutDirection(Locale locale) { + // There is a "1" difference between the configuration values for + // layout direction and View constants for layout direction, just add "1". + final int layoutDirection = 1 + TextUtils.getLayoutDirectionFromLocale(locale); + screenLayout = (screenLayout&~SCREENLAYOUT_LAYOUTDIR_MASK)| + (layoutDirection << SCREENLAYOUT_LAYOUTDIR_SHIFT); + } + + private static int getScreenLayoutNoDirection(int screenLayout) { + return screenLayout&~SCREENLAYOUT_LAYOUTDIR_MASK; + } } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index ab2fe1c..b316f23 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -89,7 +89,8 @@ public class Resources { = new LongSparseArray<ColorStateList>(); private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables = new LongSparseArray<Drawable.ConstantState>(); - private static boolean mPreloaded; + private static boolean sPreloaded; + private static int sPreloadedDensity; /*package*/ final TypedValue mTmpValue = new TypedValue(); /*package*/ final Configuration mTmpConfig = new Configuration(); @@ -693,9 +694,9 @@ public class Resources { */ if (value.density > 0 && value.density != TypedValue.DENSITY_NONE) { if (value.density == density) { - value.density = DisplayMetrics.DENSITY_DEVICE; + value.density = mMetrics.densityDpi; } else { - value.density = (value.density * DisplayMetrics.DENSITY_DEVICE) / density; + value.density = (value.density * mMetrics.densityDpi) / density; } } @@ -1434,17 +1435,27 @@ public class Resources { int configChanges = 0xfffffff; if (config != null) { mTmpConfig.setTo(config); + int density = config.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = mMetrics.noncompatDensityDpi; + } if (mCompatibilityInfo != null) { - mCompatibilityInfo.applyToConfiguration(mTmpConfig); + mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); } if (mTmpConfig.locale == null) { mTmpConfig.locale = Locale.getDefault(); + mTmpConfig.setLayoutDirection(mTmpConfig.locale); } configChanges = mConfiguration.updateFrom(mTmpConfig); configChanges = ActivityInfo.activityInfoConfigToNative(configChanges); } if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); + mConfiguration.setLayoutDirection(mConfiguration.locale); + } + if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { + mMetrics.densityDpi = mConfiguration.densityDpi; + mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; } mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; @@ -1474,7 +1485,7 @@ public class Resources { mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, locale, mConfiguration.orientation, mConfiguration.touchscreen, - (int)(mMetrics.density*160), mConfiguration.keyboard, + mConfiguration.densityDpi, mConfiguration.keyboard, keyboardHidden, mConfiguration.navigation, width, height, mConfiguration.smallestScreenWidthDp, mConfiguration.screenWidthDp, mConfiguration.screenHeightDp, @@ -1836,11 +1847,14 @@ public class Resources { */ public final void startPreloading() { synchronized (mSync) { - if (mPreloaded) { + if (sPreloaded) { throw new IllegalStateException("Resources already preloaded"); } - mPreloaded = true; + sPreloaded = true; mPreloading = true; + sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE; + mConfiguration.densityDpi = sPreloadedDensity; + updateConfiguration(null, null); } } @@ -1854,7 +1868,24 @@ public class Resources { flushLayoutCache(); } } - + + private boolean verifyPreloadConfig(TypedValue value, String name) { + if ((value.changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE + | ActivityInfo.CONFIG_DENSITY)) != 0) { + String resName; + try { + resName = getResourceName(value.resourceId); + } catch (NotFoundException e) { + resName = "?"; + } + Log.w(TAG, "Preloaded " + name + " resource #0x" + + Integer.toHexString(value.resourceId) + + " (" + resName + ") that varies with configuration!!"); + return false; + } + return true; + } + /*package*/ Drawable loadDrawable(TypedValue value, int id) throws NotFoundException { @@ -1866,20 +1897,24 @@ public class Resources { } } - final long key = (((long) value.assetCookie) << 32) | value.data; boolean isColorDrawable = false; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { isColorDrawable = true; } + final long key = isColorDrawable ? value.data : + (((long) value.assetCookie) << 32) | value.data; + Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key); if (dr != null) { return dr; } - Drawable.ConstantState cs = isColorDrawable ? - sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key); + Drawable.ConstantState cs = isColorDrawable + ? sPreloadedColorDrawables.get(key) + : (sPreloadedDensity == mConfiguration.densityDpi + ? sPreloadedDrawables.get(key) : null); if (cs != null) { dr = cs.newDrawable(this); } else { @@ -1947,10 +1982,12 @@ public class Resources { cs = dr.getConstantState(); if (cs != null) { if (mPreloading) { - if (isColorDrawable) { - sPreloadedColorDrawables.put(key, cs); - } else { - sPreloadedDrawables.put(key, cs); + if (verifyPreloadConfig(value, "drawable")) { + if (isColorDrawable) { + sPreloadedColorDrawables.put(key, cs); + } else { + sPreloadedDrawables.put(key, cs); + } } } else { synchronized (mTmpValue) { @@ -2015,7 +2052,9 @@ public class Resources { csl = ColorStateList.valueOf(value.data); if (mPreloading) { - sPreloadedColorStateLists.put(key, csl); + if (verifyPreloadConfig(value, "color")) { + sPreloadedColorStateLists.put(key, csl); + } } return csl; @@ -2059,7 +2098,9 @@ public class Resources { if (csl != null) { if (mPreloading) { - sPreloadedColorStateLists.put(key, csl); + if (verifyPreloadConfig(value, "color")) { + sPreloadedColorStateLists.put(key, csl); + } } else { synchronized (mTmpValue) { //Log.i(TAG, "Saving cached color state list @ #" + diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 2968fbb..7f3b6b9 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -469,13 +469,20 @@ public class TypedArray { * {@link android.view.ViewGroup}'s layout_width and layout_height * attributes. This is only here for performance reasons; applications * should use {@link #getDimensionPixelSize}. - * + * * @param index Index of the attribute to retrieve. * @param name Textual name of attribute for error reporting. * * @return Attribute dimension value multiplied by the appropriate * metric and truncated to integer pixels. + * + * @throws RuntimeException + * if this TypedArray does not contain an entry for <code>index</code> + * + * @deprecated Use {@link #getLayoutDimension(int, int)} instead. + * */ + @Deprecated public int getLayoutDimension(int index, String name) { index *= AssetManager.STYLE_NUM_ENTRIES; final int[] data = mData; diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index a6af5c2..1fc1226 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -50,9 +50,6 @@ public class DatabaseUtils { private static final String TAG = "DatabaseUtils"; private static final boolean DEBUG = false; - private static final boolean LOCAL_LOGV = false; - - private static final String[] countProjection = new String[]{"count(*)"}; /** One of the values returned by {@link #getSqlStatementType(String)}. */ public static final int STATEMENT_SELECT = 1; @@ -963,10 +960,15 @@ public class DatabaseUtils { } /** - * This class allows users to do multiple inserts into a table but - * compile the SQL insert statement only once, which may increase - * performance. + * This class allows users to do multiple inserts into a table using + * the same statement. + * <p> + * This class is not thread-safe. + * </p> + * + * @deprecated Use {@link SQLiteStatement} instead. */ + @Deprecated public static class InsertHelper { private final SQLiteDatabase mDb; private final String mTableName; @@ -983,6 +985,13 @@ public class DatabaseUtils { * table_info(...)" command that we depend on. */ public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1; + + /** + * This field was accidentally exposed in earlier versions of the platform + * so we can hide it but we can't remove it. + * + * @hide + */ public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4; /** @@ -1036,7 +1045,7 @@ public class DatabaseUtils { sb.append(sbv); mInsertSQL = sb.toString(); - if (LOCAL_LOGV) Log.v(TAG, "insert statement is " + mInsertSQL); + if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL); } private SQLiteStatement getStatement(boolean allowReplace) throws SQLException { @@ -1069,24 +1078,35 @@ public class DatabaseUtils { * @return the row ID of the newly inserted row, or -1 if an * error occurred */ - private synchronized long insertInternal(ContentValues values, boolean allowReplace) { + private long insertInternal(ContentValues values, boolean allowReplace) { + // Start a transaction even though we don't really need one. + // This is to help maintain compatibility with applications that + // access InsertHelper from multiple threads even though they never should have. + // The original code used to lock the InsertHelper itself which was prone + // to deadlocks. Starting a transaction achieves the same mutual exclusion + // effect as grabbing a lock but without the potential for deadlocks. + mDb.beginTransactionNonExclusive(); try { SQLiteStatement stmt = getStatement(allowReplace); stmt.clearBindings(); - if (LOCAL_LOGV) Log.v(TAG, "--- inserting in table " + mTableName); + if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName); for (Map.Entry<String, Object> e: values.valueSet()) { final String key = e.getKey(); int i = getColumnIndex(key); DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue()); - if (LOCAL_LOGV) { + if (DEBUG) { Log.v(TAG, "binding " + e.getValue() + " to column " + i + " (" + key + ")"); } } - return stmt.executeInsert(); + long result = stmt.executeInsert(); + mDb.setTransactionSuccessful(); + return result; } catch (SQLException e) { Log.e(TAG, "Error inserting " + values + " into table " + mTableName, e); return -1; + } finally { + mDb.endTransaction(); } } @@ -1223,7 +1243,7 @@ public class DatabaseUtils { + "execute"); } try { - if (LOCAL_LOGV) Log.v(TAG, "--- doing insert or replace in table " + mTableName); + if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName); return mPreparedStatement.executeInsert(); } catch (SQLException e) { Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e); diff --git a/core/java/android/ddm/DdmHandleAppName.java b/core/java/android/ddm/DdmHandleAppName.java index 78dd23e..7e39e47 100644 --- a/core/java/android/ddm/DdmHandleAppName.java +++ b/core/java/android/ddm/DdmHandleAppName.java @@ -69,14 +69,14 @@ public class DdmHandleAppName extends ChunkHandler { * before or after DDMS connects. For the latter we need to send up * an APNM message. */ - public static void setAppName(String name) { + public static void setAppName(String name, int userId) { if (name == null || name.length() == 0) return; mAppName = name; // if DDMS is already connected, send the app name up - sendAPNM(name); + sendAPNM(name, userId); } public static String getAppName() { @@ -86,14 +86,18 @@ public class DdmHandleAppName extends ChunkHandler { /* * Send an APNM (APplication NaMe) chunk. */ - private static void sendAPNM(String appName) { + private static void sendAPNM(String appName, int userId) { if (false) Log.v("ddm", "Sending app name"); - ByteBuffer out = ByteBuffer.allocate(4 + appName.length()*2); + ByteBuffer out = ByteBuffer.allocate( + 4 /* appName's length */ + + appName.length()*2 /* appName */ + + 4 /* userId */); out.order(ChunkHandler.CHUNK_ORDER); out.putInt(appName.length()); putString(out, appName); + out.putInt(userId); Chunk chunk = new Chunk(CHUNK_APNM, out); DdmServer.sendChunk(chunk); diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java index 5088d22..e99fa92 100644 --- a/core/java/android/ddm/DdmHandleHello.java +++ b/core/java/android/ddm/DdmHandleHello.java @@ -21,6 +21,7 @@ import org.apache.harmony.dalvik.ddmc.ChunkHandler; import org.apache.harmony.dalvik.ddmc.DdmServer; import android.util.Log; import android.os.Debug; +import android.os.UserHandle; import java.nio.ByteBuffer; @@ -119,7 +120,7 @@ public class DdmHandleHello extends ChunkHandler { // appName = "unknown"; String appName = DdmHandleAppName.getAppName(); - ByteBuffer out = ByteBuffer.allocate(16 + ByteBuffer out = ByteBuffer.allocate(20 + vmIdent.length()*2 + appName.length()*2); out.order(ChunkHandler.CHUNK_ORDER); out.putInt(DdmServer.CLIENT_PROTOCOL_VERSION); @@ -128,6 +129,7 @@ public class DdmHandleHello extends ChunkHandler { out.putInt(appName.length()); putString(out, vmIdent); putString(out, appName); + out.putInt(UserHandle.myUserId()); Chunk reply = new Chunk(CHUNK_HELO, out); diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 829620b..1e8671b 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -233,6 +233,21 @@ public class Camera { * @see Parameters#setJpegThumbnailSize(int, int) */ public int orientation; + + /** + * <p>Whether the shutter sound can be disabled.</p> + * + * <p>On some devices, the camera shutter sound cannot be turned off + * through {@link #enableShutterSound enableShutterSound}. This field + * can be used to determine whether a call to disable the shutter sound + * will succeed.</p> + * + * <p>If this field is set to true, then a call of + * {@code enableShutterSound(false)} will be successful. If set to + * false, then that call will fail, and the shutter sound will be played + * when {@link Camera#takePicture takePicture} is called.</p> + */ + public boolean canDisableShutterSound; }; /** @@ -1146,6 +1161,33 @@ public class Camera { public native final void setDisplayOrientation(int degrees); /** + * <p>Enable or disable the default shutter sound when taking a picture.</p> + * + * <p>By default, the camera plays the system-defined camera shutter sound + * when {@link #takePicture} is called. Using this method, the shutter sound + * can be disabled. It is strongly recommended that an alternative shutter + * sound is played in the {@link ShutterCallback} when the system shutter + * sound is disabled.</p> + * + * <p>Note that devices may not always allow disabling the camera shutter + * sound. If the shutter sound state cannot be set to the desired value, + * this method will return false. {@link CameraInfo#canDisableShutterSound} + * can be used to determine whether the device will allow the shutter sound + * to be disabled.</p> + * + * @param enabled whether the camera should play the system shutter sound + * when {@link #takePicture takePicture} is called. + * @return {@code true} if the shutter sound state was successfully + * changed. {@code false} if the shutter sound state could not be + * changed. {@code true} is also returned if shutter sound playback + * is already set to the requested state. + * @see #takePicture + * @see CameraInfo#canDisableShutterSound + * @see ShutterCallback + */ + public native final boolean enableShutterSound(boolean enabled); + + /** * Callback interface for zoom changes during a smooth zoom operation. * * @see #setZoomChangeListener(OnZoomChangeListener) @@ -1308,8 +1350,14 @@ public class Camera { public Rect rect; /** - * The confidence level for the detection of the face. The range is 1 to 100. 100 is the - * highest confidence. + * <p>The confidence level for the detection of the face. The range is 1 to + * 100. 100 is the highest confidence.</p> + * + * <p>Depending on the device, even very low-confidence faces may be + * listed, so applications should filter out faces with low confidence, + * depending on the use case. For a typical point-and-shoot camera + * application that wishes to display rectangles around detected faces, + * filtering out faces with confidence less than 50 is recommended.</p> * * @see #startFaceDetection() */ @@ -1782,6 +1830,14 @@ public class Camera { public static final String SCENE_MODE_BARCODE = "barcode"; /** + * Capture a scene using high dynamic range imaging techniques. The + * camera will return an image that has an extended dynamic range + * compared to a regular capture. Capturing such an image may take + * longer than a regular capture. + */ + public static final String SCENE_MODE_HDR = "hdr"; + + /** * Auto-focus mode. Applications should call {@link * #autoFocus(AutoFocusCallback)} to start the focus in this mode. */ @@ -2784,6 +2840,7 @@ public class Camera { * @see #SCENE_MODE_SPORTS * @see #SCENE_MODE_PARTY * @see #SCENE_MODE_CANDLELIGHT + * @see #SCENE_MODE_BARCODE */ public String getSceneMode() { return get(KEY_SCENE_MODE); diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 3c70dc6..e0c9d2c 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -26,7 +26,7 @@ package android.hardware; * @see SensorEvent * */ -public class Sensor { +public final class Sensor { /** * A constant describing an accelerometer sensor type. See @@ -202,4 +202,11 @@ public class Sensor { mMaxRange = max; mResolution = res; } + + @Override + public String toString() { + return "{Sensor name=\"" + mName + "\", vendor=\"" + mVendor + "\", version=" + mVersion + + ", type=" + mType + ", maxRange=" + mMaxRange + ", resolution=" + mResolution + + ", power=" + mPower + ", minDelay=" + mMinDelay + "}"; + } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java new file mode 100644 index 0000000..28e320b --- /dev/null +++ b/core/java/android/hardware/display/DisplayManager.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.content.Context; +import android.os.Handler; +import android.util.SparseArray; +import android.view.Display; + +/** + * Manages the properties of attached displays. + * <p> + * Get an instance of this class by calling + * {@link android.content.Context#getSystemService(java.lang.String) + * Context.getSystemService()} with the argument + * {@link android.content.Context#DISPLAY_SERVICE}. + * </p> + */ +public final class DisplayManager { + private static final String TAG = "DisplayManager"; + private static final boolean DEBUG = false; + + private final Context mContext; + private final DisplayManagerGlobal mGlobal; + + private final Object mLock = new Object(); + private final SparseArray<Display> mDisplays = new SparseArray<Display>(); + + /** + * Broadcast receiver that indicates when the Wifi display status changes. + * <p> + * The status is provided as a {@link WifiDisplayStatus} object in the + * {@link #EXTRA_WIFI_DISPLAY_STATUS} extra. + * </p><p> + * This broadcast is only sent to registered receivers and can only be sent by the system. + * </p> + * @hide + */ + public static final String ACTION_WIFI_DISPLAY_STATUS_CHANGED = + "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED"; + + /** + * Contains a {@link WifiDisplayStatus} object. + * @hide + */ + public static final String EXTRA_WIFI_DISPLAY_STATUS = + "android.hardware.display.extra.WIFI_DISPLAY_STATUS"; + + /** @hide */ + public DisplayManager(Context context) { + mContext = context; + mGlobal = DisplayManagerGlobal.getInstance(); + } + + /** + * Gets information about a logical display. + * + * The display metrics may be adjusted to provide compatibility + * for legacy applications. + * + * @param displayId The logical display id. + * @return The display object, or null if there is no valid display with the given id. + */ + public Display getDisplay(int displayId) { + synchronized (mLock) { + return getOrCreateDisplayLocked(displayId, false /*assumeValid*/); + } + } + + /** + * Gets all currently valid logical displays. + * + * @return An array containing all displays. + */ + public Display[] getDisplays() { + int[] displayIds = mGlobal.getDisplayIds(); + int expectedCount = displayIds.length; + Display[] displays = new Display[expectedCount]; + synchronized (mLock) { + int actualCount = 0; + for (int i = 0; i < expectedCount; i++) { + Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/); + if (display != null) { + displays[actualCount++] = display; + } + } + if (actualCount != expectedCount) { + Display[] oldDisplays = displays; + displays = new Display[actualCount]; + System.arraycopy(oldDisplays, 0, displays, 0, actualCount); + } + } + return displays; + } + + private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) { + Display display = mDisplays.get(displayId); + if (display == null) { + display = mGlobal.getCompatibleDisplay(displayId, + mContext.getCompatibilityInfo(displayId)); + if (display != null) { + mDisplays.put(displayId, display); + } + } else if (!assumeValid && !display.isValid()) { + display = null; + } + return display; + } + + /** + * Registers an display listener to receive notifications about when + * displays are added, removed or changed. + * + * @param listener The listener to register. + * @param handler The handler on which the listener should be invoked, or null + * if the listener should be invoked on the calling thread's looper. + * + * @see #unregisterDisplayListener + */ + public void registerDisplayListener(DisplayListener listener, Handler handler) { + mGlobal.registerDisplayListener(listener, handler); + } + + /** + * Unregisters an input device listener. + * + * @param listener The listener to unregister. + * + * @see #registerDisplayListener + */ + public void unregisterDisplayListener(DisplayListener listener) { + mGlobal.unregisterDisplayListener(listener); + } + + /** + * Initiates a fresh scan of availble Wifi displays. + * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast. + * @hide + */ + public void scanWifiDisplays() { + mGlobal.scanWifiDisplays(); + } + + /** + * Connects to a Wifi display. + * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast. + * <p> + * Automatically remembers the display after a successful connection, if not + * already remembered. + * </p><p> + * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY} to connect + * to unknown displays. No permissions are required to connect to already known displays. + * </p> + * + * @param deviceAddress The MAC address of the device to which we should connect. + * @hide + */ + public void connectWifiDisplay(String deviceAddress) { + mGlobal.connectWifiDisplay(deviceAddress); + } + + /** + * Disconnects from the current Wifi display. + * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast. + * @hide + */ + public void disconnectWifiDisplay() { + mGlobal.disconnectWifiDisplay(); + } + + /** + * Renames a Wifi display. + * <p> + * The display must already be remembered for this call to succeed. In other words, + * we must already have successfully connected to the display at least once and then + * not forgotten it. + * </p><p> + * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}. + * </p> + * + * @param deviceAddress The MAC address of the device to rename. + * @param alias The alias name by which to remember the device, or null + * or empty if no alias should be used. + * @hide + */ + public void renameWifiDisplay(String deviceAddress, String alias) { + mGlobal.renameWifiDisplay(deviceAddress, alias); + } + + /** + * Forgets a previously remembered Wifi display. + * <p> + * Automatically disconnects from the display if currently connected to it. + * </p><p> + * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}. + * </p> + * + * @param deviceAddress The MAC address of the device to forget. + * @hide + */ + public void forgetWifiDisplay(String deviceAddress) { + mGlobal.forgetWifiDisplay(deviceAddress); + } + + /** + * Gets the current Wifi display status. + * Watch for changes in the status by registering a broadcast receiver for + * {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED}. + * + * @return The current Wifi display status. + * @hide + */ + public WifiDisplayStatus getWifiDisplayStatus() { + return mGlobal.getWifiDisplayStatus(); + } + + /** + * Listens for changes in available display devices. + */ + public interface DisplayListener { + /** + * Called whenever a logical display has been added to the system. + * Use {@link DisplayManager#getDisplay} to get more information about + * the display. + * + * @param displayId The id of the logical display that was added. + */ + void onDisplayAdded(int displayId); + + /** + * Called whenever a logical display has been removed from the system. + * + * @param displayId The id of the logical display that was removed. + */ + void onDisplayRemoved(int displayId); + + /** + * Called whenever the properties of a logical display have changed. + * + * @param displayId The id of the logical display that changed. + */ + void onDisplayChanged(int displayId); + } +} diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java new file mode 100644 index 0000000..a858681 --- /dev/null +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.content.Context; +import android.hardware.display.DisplayManager.DisplayListener; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.SparseArray; +import android.view.CompatibilityInfoHolder; +import android.view.Display; +import android.view.DisplayInfo; + +import java.util.ArrayList; + +/** + * Manager communication with the display manager service on behalf of + * an application process. You're probably looking for {@link DisplayManager}. + * + * @hide + */ +public final class DisplayManagerGlobal { + private static final String TAG = "DisplayManager"; + private static final boolean DEBUG = false; + + // True if display info and display ids should be cached. + // + // FIXME: The cache is currently disabled because it's unclear whether we have the + // necessary guarantees that the caches will always be flushed before clients + // attempt to observe their new state. For example, depending on the order + // in which the binder transactions take place, we might have a problem where + // an application could start processing a configuration change due to a display + // orientation change before the display info cache has actually been invalidated. + private static final boolean USE_CACHE = false; + + public static final int EVENT_DISPLAY_ADDED = 1; + public static final int EVENT_DISPLAY_CHANGED = 2; + public static final int EVENT_DISPLAY_REMOVED = 3; + + private static DisplayManagerGlobal sInstance; + + private final Object mLock = new Object(); + + private final IDisplayManager mDm; + + private DisplayManagerCallback mCallback; + private final ArrayList<DisplayListenerDelegate> mDisplayListeners = + new ArrayList<DisplayListenerDelegate>(); + + private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>(); + private int[] mDisplayIdCache; + + private DisplayManagerGlobal(IDisplayManager dm) { + mDm = dm; + } + + /** + * Gets an instance of the display manager global singleton. + * + * @return The display manager instance, may be null early in system startup + * before the display manager has been fully initialized. + */ + public static DisplayManagerGlobal getInstance() { + synchronized (DisplayManagerGlobal.class) { + if (sInstance == null) { + IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); + if (b != null) { + sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b)); + } + } + return sInstance; + } + } + + /** + * Get information about a particular logical display. + * + * @param displayId The logical display id. + * @return Information about the specified display, or null if it does not exist. + * This object belongs to an internal cache and should be treated as if it were immutable. + */ + public DisplayInfo getDisplayInfo(int displayId) { + try { + synchronized (mLock) { + DisplayInfo info; + if (USE_CACHE) { + info = mDisplayInfoCache.get(displayId); + if (info != null) { + return info; + } + } + + info = mDm.getDisplayInfo(displayId); + if (info == null) { + return null; + } + + if (USE_CACHE) { + mDisplayInfoCache.put(displayId, info); + } + registerCallbackIfNeededLocked(); + + if (DEBUG) { + Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info); + } + return info; + } + } catch (RemoteException ex) { + Log.e(TAG, "Could not get display information from display manager.", ex); + return null; + } + } + + /** + * Gets all currently valid logical display ids. + * + * @return An array containing all display ids. + */ + public int[] getDisplayIds() { + try { + synchronized (mLock) { + if (USE_CACHE) { + if (mDisplayIdCache != null) { + return mDisplayIdCache; + } + } + + int[] displayIds = mDm.getDisplayIds(); + if (USE_CACHE) { + mDisplayIdCache = displayIds; + } + registerCallbackIfNeededLocked(); + return displayIds; + } + } catch (RemoteException ex) { + Log.e(TAG, "Could not get display ids from display manager.", ex); + return new int[] { Display.DEFAULT_DISPLAY }; + } + } + + /** + * Gets information about a logical display. + * + * The display metrics may be adjusted to provide compatibility + * for legacy applications. + * + * @param displayId The logical display id. + * @param cih The compatibility info, or null if none is required. + * @return The display object, or null if there is no display with the given id. + */ + public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) { + DisplayInfo displayInfo = getDisplayInfo(displayId); + if (displayInfo == null) { + return null; + } + return new Display(this, displayId, displayInfo, cih); + } + + /** + * Gets information about a logical display without applying any compatibility metrics. + * + * @param displayId The logical display id. + * @return The display object, or null if there is no display with the given id. + */ + public Display getRealDisplay(int displayId) { + return getCompatibleDisplay(displayId, null); + } + + public void registerDisplayListener(DisplayListener listener, Handler handler) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mLock) { + int index = findDisplayListenerLocked(listener); + if (index < 0) { + mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); + registerCallbackIfNeededLocked(); + } + } + } + + public void unregisterDisplayListener(DisplayListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mLock) { + int index = findDisplayListenerLocked(listener); + if (index >= 0) { + DisplayListenerDelegate d = mDisplayListeners.get(index); + d.clearEvents(); + mDisplayListeners.remove(index); + } + } + } + + private int findDisplayListenerLocked(DisplayListener listener) { + final int numListeners = mDisplayListeners.size(); + for (int i = 0; i < numListeners; i++) { + if (mDisplayListeners.get(i).mListener == listener) { + return i; + } + } + return -1; + } + + private void registerCallbackIfNeededLocked() { + if (mCallback == null) { + mCallback = new DisplayManagerCallback(); + try { + mDm.registerCallback(mCallback); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to register callback with display manager service.", ex); + mCallback = null; + } + } + } + + private void handleDisplayEvent(int displayId, int event) { + synchronized (mLock) { + if (USE_CACHE) { + mDisplayInfoCache.remove(displayId); + + if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) { + mDisplayIdCache = null; + } + } + + final int numListeners = mDisplayListeners.size(); + for (int i = 0; i < numListeners; i++) { + mDisplayListeners.get(i).sendDisplayEvent(displayId, event); + } + } + } + + public void scanWifiDisplays() { + try { + mDm.scanWifiDisplays(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to scan for Wifi displays.", ex); + } + } + + public void connectWifiDisplay(String deviceAddress) { + if (deviceAddress == null) { + throw new IllegalArgumentException("deviceAddress must not be null"); + } + + try { + mDm.connectWifiDisplay(deviceAddress); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to connect to Wifi display " + deviceAddress + ".", ex); + } + } + + public void disconnectWifiDisplay() { + try { + mDm.disconnectWifiDisplay(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to disconnect from Wifi display.", ex); + } + } + + public void renameWifiDisplay(String deviceAddress, String alias) { + if (deviceAddress == null) { + throw new IllegalArgumentException("deviceAddress must not be null"); + } + + try { + mDm.renameWifiDisplay(deviceAddress, alias); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to rename Wifi display " + deviceAddress + + " with alias " + alias + ".", ex); + } + } + + public void forgetWifiDisplay(String deviceAddress) { + if (deviceAddress == null) { + throw new IllegalArgumentException("deviceAddress must not be null"); + } + + try { + mDm.forgetWifiDisplay(deviceAddress); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to forget Wifi display.", ex); + } + } + + public WifiDisplayStatus getWifiDisplayStatus() { + try { + return mDm.getWifiDisplayStatus(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to get Wifi display status.", ex); + return new WifiDisplayStatus(); + } + } + + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { + @Override + public void onDisplayEvent(int displayId, int event) { + if (DEBUG) { + Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); + } + handleDisplayEvent(displayId, event); + } + } + + private static final class DisplayListenerDelegate extends Handler { + public final DisplayListener mListener; + + public DisplayListenerDelegate(DisplayListener listener, Handler handler) { + super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); + mListener = listener; + } + + public void sendDisplayEvent(int displayId, int event) { + Message msg = obtainMessage(event, displayId, 0); + sendMessage(msg); + } + + public void clearEvents() { + removeCallbacksAndMessages(null); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_DISPLAY_ADDED: + mListener.onDisplayAdded(msg.arg1); + break; + case EVENT_DISPLAY_CHANGED: + mListener.onDisplayChanged(msg.arg1); + break; + case EVENT_DISPLAY_REMOVED: + mListener.onDisplayRemoved(msg.arg1); + break; + } + } + } +} diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl new file mode 100644 index 0000000..79aad78 --- /dev/null +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.hardware.display.IDisplayManagerCallback; +import android.hardware.display.WifiDisplay; +import android.hardware.display.WifiDisplayStatus; +import android.view.DisplayInfo; + +/** @hide */ +interface IDisplayManager { + DisplayInfo getDisplayInfo(int displayId); + int[] getDisplayIds(); + + void registerCallback(in IDisplayManagerCallback callback); + + // No permissions required. + void scanWifiDisplays(); + + // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device. + // No permissions required to connect to a known device. + void connectWifiDisplay(String address); + + // No permissions required. + void disconnectWifiDisplay(); + + // Requires CONFIGURE_WIFI_DISPLAY permission. + void renameWifiDisplay(String address, String alias); + + // Requires CONFIGURE_WIFI_DISPLAY permission. + void forgetWifiDisplay(String address); + + // No permissions required. + WifiDisplayStatus getWifiDisplayStatus(); +} diff --git a/core/java/android/hardware/display/IDisplayManagerCallback.aidl b/core/java/android/hardware/display/IDisplayManagerCallback.aidl new file mode 100644 index 0000000..c50e3fb --- /dev/null +++ b/core/java/android/hardware/display/IDisplayManagerCallback.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +/** @hide */ +interface IDisplayManagerCallback { + oneway void onDisplayEvent(int displayId, int event); +} diff --git a/core/java/android/hardware/display/WifiDisplay.aidl b/core/java/android/hardware/display/WifiDisplay.aidl new file mode 100644 index 0000000..7733075 --- /dev/null +++ b/core/java/android/hardware/display/WifiDisplay.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +parcelable WifiDisplay; diff --git a/core/java/android/hardware/display/WifiDisplay.java b/core/java/android/hardware/display/WifiDisplay.java new file mode 100644 index 0000000..0138b1c --- /dev/null +++ b/core/java/android/hardware/display/WifiDisplay.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.os.Parcel; +import android.os.Parcelable; + +import libcore.util.Objects; + +/** + * Describes the properties of a Wifi display. + * <p> + * This object is immutable. + * </p> + * + * @hide + */ +public final class WifiDisplay implements Parcelable { + private final String mDeviceAddress; + private final String mDeviceName; + private final String mDeviceAlias; + + public static final WifiDisplay[] EMPTY_ARRAY = new WifiDisplay[0]; + + public static final Creator<WifiDisplay> CREATOR = new Creator<WifiDisplay>() { + public WifiDisplay createFromParcel(Parcel in) { + String deviceAddress = in.readString(); + String deviceName = in.readString(); + String deviceAlias = in.readString(); + return new WifiDisplay(deviceAddress, deviceName, deviceAlias); + } + + public WifiDisplay[] newArray(int size) { + return size == 0 ? EMPTY_ARRAY : new WifiDisplay[size]; + } + }; + + public WifiDisplay(String deviceAddress, String deviceName, String deviceAlias) { + if (deviceAddress == null) { + throw new IllegalArgumentException("deviceAddress must not be null"); + } + if (deviceName == null) { + throw new IllegalArgumentException("deviceName must not be null"); + } + + mDeviceAddress = deviceAddress; + mDeviceName = deviceName; + mDeviceAlias = deviceAlias; + } + + /** + * Gets the MAC address of the Wifi display device. + */ + public String getDeviceAddress() { + return mDeviceAddress; + } + + /** + * Gets the name of the Wifi display device. + */ + public String getDeviceName() { + return mDeviceName; + } + + /** + * Gets the user-specified alias of the Wifi display device, or null if none. + * <p> + * The alias should be used in the UI whenever available. It is the value + * provided by the user when renaming the device. + * </p> + */ + public String getDeviceAlias() { + return mDeviceAlias; + } + + /** + * Gets the name to show in the UI. + * Uses the device alias if available, otherwise uses the device name. + */ + public String getFriendlyDisplayName() { + return mDeviceAlias != null ? mDeviceAlias : mDeviceName; + } + + @Override + public boolean equals(Object o) { + return o instanceof WifiDisplay && equals((WifiDisplay)o); + } + + public boolean equals(WifiDisplay other) { + return other != null + && mDeviceAddress.equals(other.mDeviceAddress) + && mDeviceName.equals(other.mDeviceName) + && Objects.equal(mDeviceAlias, other.mDeviceAlias); + } + + @Override + public int hashCode() { + // The address on its own should be sufficiently unique for hashing purposes. + return mDeviceAddress.hashCode(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mDeviceAddress); + dest.writeString(mDeviceName); + dest.writeString(mDeviceAlias); + } + + @Override + public int describeContents() { + return 0; + } + + // For debugging purposes only. + @Override + public String toString() { + String result = mDeviceName + " (" + mDeviceAddress + ")"; + if (mDeviceAlias != null) { + result += ", alias " + mDeviceAlias; + } + return result; + } +} diff --git a/core/java/android/hardware/display/WifiDisplayStatus.aidl b/core/java/android/hardware/display/WifiDisplayStatus.aidl new file mode 100644 index 0000000..35c633e --- /dev/null +++ b/core/java/android/hardware/display/WifiDisplayStatus.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +parcelable WifiDisplayStatus; diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java new file mode 100644 index 0000000..f7e72c4 --- /dev/null +++ b/core/java/android/hardware/display/WifiDisplayStatus.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * Describes the current global state of Wifi display connectivity, including the + * currently connected display and all available or remembered displays. + * <p> + * This object is immutable. + * </p> + * + * @hide + */ +public final class WifiDisplayStatus implements Parcelable { + private final int mFeatureState; + private final int mScanState; + private final int mActiveDisplayState; + private final WifiDisplay mActiveDisplay; + private final WifiDisplay[] mAvailableDisplays; + private final WifiDisplay[] mRememberedDisplays; + + /** Feature state: Wifi display is not available on this device. */ + public static final int FEATURE_STATE_UNAVAILABLE = 0; + /** Feature state: Wifi display is disabled, probably because Wifi is disabled. */ + public static final int FEATURE_STATE_DISABLED = 1; + /** Feature state: Wifi display is turned off in settings. */ + public static final int FEATURE_STATE_OFF = 2; + /** Feature state: Wifi display is turned on in settings. */ + public static final int FEATURE_STATE_ON = 3; + + /** Scan state: Not currently scanning. */ + public static final int SCAN_STATE_NOT_SCANNING = 0; + /** Scan state: Currently scanning. */ + public static final int SCAN_STATE_SCANNING = 1; + + /** Display state: Not connected. */ + public static final int DISPLAY_STATE_NOT_CONNECTED = 0; + /** Display state: Connecting to active display. */ + public static final int DISPLAY_STATE_CONNECTING = 1; + /** Display state: Connected to active display. */ + public static final int DISPLAY_STATE_CONNECTED = 2; + + public static final Creator<WifiDisplayStatus> CREATOR = new Creator<WifiDisplayStatus>() { + public WifiDisplayStatus createFromParcel(Parcel in) { + int featureState = in.readInt(); + int scanState = in.readInt(); + int activeDisplayState= in.readInt(); + + WifiDisplay activeDisplay = null; + if (in.readInt() != 0) { + activeDisplay = WifiDisplay.CREATOR.createFromParcel(in); + } + + WifiDisplay[] availableDisplays = WifiDisplay.CREATOR.newArray(in.readInt()); + for (int i = 0; i < availableDisplays.length; i++) { + availableDisplays[i] = WifiDisplay.CREATOR.createFromParcel(in); + } + + WifiDisplay[] rememberedDisplays = WifiDisplay.CREATOR.newArray(in.readInt()); + for (int i = 0; i < rememberedDisplays.length; i++) { + rememberedDisplays[i] = WifiDisplay.CREATOR.createFromParcel(in); + } + + return new WifiDisplayStatus(featureState, scanState, activeDisplayState, + activeDisplay, availableDisplays, rememberedDisplays); + } + + public WifiDisplayStatus[] newArray(int size) { + return new WifiDisplayStatus[size]; + } + }; + + public WifiDisplayStatus() { + this(FEATURE_STATE_UNAVAILABLE, SCAN_STATE_NOT_SCANNING, DISPLAY_STATE_NOT_CONNECTED, + null, WifiDisplay.EMPTY_ARRAY, WifiDisplay.EMPTY_ARRAY); + } + + public WifiDisplayStatus(int featureState, int scanState, + int activeDisplayState, WifiDisplay activeDisplay, + WifiDisplay[] availableDisplays, WifiDisplay[] rememberedDisplays) { + if (availableDisplays == null) { + throw new IllegalArgumentException("availableDisplays must not be null"); + } + if (rememberedDisplays == null) { + throw new IllegalArgumentException("rememberedDisplays must not be null"); + } + + mFeatureState = featureState; + mScanState = scanState; + mActiveDisplayState = activeDisplayState; + mActiveDisplay = activeDisplay; + mAvailableDisplays = availableDisplays; + mRememberedDisplays = rememberedDisplays; + } + + /** + * Returns the state of the Wifi display feature on this device. + * <p> + * The value of this property reflects whether the device supports the Wifi display, + * whether it has been enabled by the user and whether the prerequisites for + * connecting to displays have been met. + * </p> + */ + public int getFeatureState() { + return mFeatureState; + } + + /** + * Returns the current state of the Wifi display scan. + * + * @return One of: {@link #SCAN_STATE_NOT_SCANNING} or {@link #SCAN_STATE_SCANNING}. + */ + public int getScanState() { + return mScanState; + } + + /** + * Get the state of the currently active display. + * + * @return One of: {@link #DISPLAY_STATE_NOT_CONNECTED}, {@link #DISPLAY_STATE_CONNECTING}, + * or {@link #DISPLAY_STATE_CONNECTED}. + */ + public int getActiveDisplayState() { + return mActiveDisplayState; + } + + /** + * Gets the Wifi display that is currently active. It may be connecting or + * connected. + */ + public WifiDisplay getActiveDisplay() { + return mActiveDisplay; + } + + /** + * Gets the list of all available Wifi displays as reported by the most recent + * scan, never null. + * <p> + * Some of these displays may already be remembered, others may be unknown. + * </p> + */ + public WifiDisplay[] getAvailableDisplays() { + return mAvailableDisplays; + } + + /** + * Gets the list of all remembered Wifi displays, never null. + * <p> + * Not all remembered displays will necessarily be available. + * </p> + */ + public WifiDisplay[] getRememberedDisplays() { + return mRememberedDisplays; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mFeatureState); + dest.writeInt(mScanState); + dest.writeInt(mActiveDisplayState); + + if (mActiveDisplay != null) { + dest.writeInt(1); + mActiveDisplay.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + + dest.writeInt(mAvailableDisplays.length); + for (WifiDisplay display : mAvailableDisplays) { + display.writeToParcel(dest, flags); + } + + dest.writeInt(mRememberedDisplays.length); + for (WifiDisplay display : mRememberedDisplays) { + display.writeToParcel(dest, flags); + } + } + + @Override + public int describeContents() { + return 0; + } + + // For debugging purposes only. + @Override + public String toString() { + return "WifiDisplayStatus{featureState=" + mFeatureState + + ", scanState=" + mScanState + + ", activeDisplayState=" + mActiveDisplayState + + ", activeDisplay=" + mActiveDisplay + + ", availableDisplays=" + Arrays.toString(mAvailableDisplays) + + ", rememberedDisplays=" + Arrays.toString(mRememberedDisplays) + + "}"; + } +} diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 9bab797..98bd4f5 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -87,4 +87,12 @@ interface IUsbManager /* Sets the file path for USB mass storage backing file. */ void setMassStorageBackingFile(String path); + + /* Allow USB debugging from the attached host. If alwaysAllow is true, add the + * the public key to list of host keys that the user has approved. + */ + void allowUsbDebugging(boolean alwaysAllow, String publicKey); + + /* Deny USB debugging from the attached host */ + void denyUsbDebugging(); } diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java index 2252248..3646715 100644 --- a/core/java/android/hardware/usb/UsbRequest.java +++ b/core/java/android/hardware/usb/UsbRequest.java @@ -152,10 +152,14 @@ public class UsbRequest { /* package */ void dequeue() { boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); + int bytesRead; if (mBuffer.isDirect()) { - native_dequeue_direct(); + bytesRead = native_dequeue_direct(); } else { - native_dequeue_array(mBuffer.array(), mLength, out); + bytesRead = native_dequeue_array(mBuffer.array(), mLength, out); + } + if (bytesRead >= 0) { + mBuffer.position(Math.min(bytesRead, mLength)); } mBuffer = null; mLength = 0; @@ -174,8 +178,8 @@ public class UsbRequest { int ep_attributes, int ep_max_packet_size, int ep_interval); private native void native_close(); private native boolean native_queue_array(byte[] buffer, int length, boolean out); - private native void native_dequeue_array(byte[] buffer, int length, boolean out); + private native int native_dequeue_array(byte[] buffer, int length, boolean out); private native boolean native_queue_direct(ByteBuffer buffer, int length, boolean out); - private native void native_dequeue_direct(); + private native int native_dequeue_direct(); private native boolean native_cancel(); } diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index 27af013..3c3182a 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -149,6 +149,17 @@ public abstract class AbstractInputMethodService extends Service callback.finishedEvent(seq, handled); } } + + /** + * Take care of dispatching incoming generic motion events to the appropriate + * callbacks on the service, and tell the client when this is done. + */ + public void dispatchGenericMotionEvent(int seq, MotionEvent event, EventCallback callback) { + boolean handled = onGenericMotionEvent(event); + if (callback != null) { + callback.finishedEvent(seq, handled); + } + } } /** @@ -189,7 +200,25 @@ public abstract class AbstractInputMethodService extends Service return new IInputMethodWrapper(this, mInputMethod); } + /** + * Implement this to handle trackball events on your input method. + * + * @param event The motion event being received. + * @return True if the event was handled in this function, false otherwise. + * @see View#onTrackballEvent + */ public boolean onTrackballEvent(MotionEvent event) { return false; } + + /** + * Implement this to handle generic motion events on your input method. + * + * @param event The motion event being received. + * @return True if the event was handled in this function, false otherwise. + * @see View#onGenericMotionEvent + */ + public boolean onGenericMotionEvent(MotionEvent event) { + return false; + } } diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index e10f218..5324f81 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -17,6 +17,7 @@ package android.inputmethodservice; import com.android.internal.os.HandlerCaller; +import com.android.internal.os.SomeArgs; import com.android.internal.view.IInputMethodCallback; import com.android.internal.view.IInputMethodSession; @@ -42,6 +43,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub private static final int DO_UPDATE_EXTRACTED_TEXT = 67; private static final int DO_DISPATCH_KEY_EVENT = 70; private static final int DO_DISPATCH_TRACKBALL_EVENT = 80; + private static final int DO_DISPATCH_GENERIC_MOTION_EVENT = 85; private static final int DO_UPDATE_SELECTION = 90; private static final int DO_UPDATE_CURSOR = 95; private static final int DO_APP_PRIVATE_COMMAND = 100; @@ -91,28 +93,37 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub (ExtractedText)msg.obj); return; case DO_DISPATCH_KEY_EVENT: { - HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + SomeArgs args = (SomeArgs)msg.obj; mInputMethodSession.dispatchKeyEvent(msg.arg1, (KeyEvent)args.arg1, new InputMethodEventCallbackWrapper( (IInputMethodCallback)args.arg2)); - mCaller.recycleArgs(args); + args.recycle(); return; } case DO_DISPATCH_TRACKBALL_EVENT: { - HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + SomeArgs args = (SomeArgs)msg.obj; mInputMethodSession.dispatchTrackballEvent(msg.arg1, (MotionEvent)args.arg1, new InputMethodEventCallbackWrapper( (IInputMethodCallback)args.arg2)); - mCaller.recycleArgs(args); + args.recycle(); + return; + } + case DO_DISPATCH_GENERIC_MOTION_EVENT: { + SomeArgs args = (SomeArgs)msg.obj; + mInputMethodSession.dispatchGenericMotionEvent(msg.arg1, + (MotionEvent)args.arg1, + new InputMethodEventCallbackWrapper( + (IInputMethodCallback)args.arg2)); + args.recycle(); return; } case DO_UPDATE_SELECTION: { - HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + SomeArgs args = (SomeArgs)msg.obj; mInputMethodSession.updateSelection(args.argi1, args.argi2, args.argi3, args.argi4, args.argi5, args.argi6); - mCaller.recycleArgs(args); + args.recycle(); return; } case DO_UPDATE_CURSOR: { @@ -120,10 +131,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub return; } case DO_APP_PRIVATE_COMMAND: { - HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + SomeArgs args = (SomeArgs)msg.obj; mInputMethodSession.appPrivateCommand((String)args.arg1, (Bundle)args.arg2); - mCaller.recycleArgs(args); + args.recycle(); return; } case DO_TOGGLE_SOFT_INPUT: { @@ -166,6 +177,12 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub event, callback)); } + public void dispatchGenericMotionEvent(int seq, MotionEvent event, + IInputMethodCallback callback) { + mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_GENERIC_MOTION_EVENT, seq, + event, callback)); + } + public void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION, diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 17c9ee7..5275314 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -17,6 +17,7 @@ package android.inputmethodservice; import com.android.internal.os.HandlerCaller; +import com.android.internal.os.SomeArgs; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethod; import com.android.internal.view.IInputMethodCallback; @@ -124,7 +125,7 @@ class IInputMethodWrapper extends IInputMethod.Stub if (target == null) { return; } - HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + SomeArgs args = (SomeArgs)msg.obj; try { target.dump((FileDescriptor)args.arg1, (PrintWriter)args.arg2, (String[])args.arg3); @@ -134,6 +135,7 @@ class IInputMethodWrapper extends IInputMethod.Stub synchronized (args.arg4) { ((CountDownLatch)args.arg4).countDown(); } + args.recycle(); return; } @@ -149,23 +151,25 @@ class IInputMethodWrapper extends IInputMethod.Stub inputMethod.unbindInput(); return; case DO_START_INPUT: { - HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + SomeArgs args = (SomeArgs)msg.obj; IInputContext inputContext = (IInputContext)args.arg1; InputConnection ic = inputContext != null ? new InputConnectionWrapper(inputContext) : null; EditorInfo info = (EditorInfo)args.arg2; info.makeCompatible(mTargetSdkVersion); inputMethod.startInput(ic, info); + args.recycle(); return; } case DO_RESTART_INPUT: { - HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + SomeArgs args = (SomeArgs)msg.obj; IInputContext inputContext = (IInputContext)args.arg1; InputConnection ic = inputContext != null ? new InputConnectionWrapper(inputContext) : null; EditorInfo info = (EditorInfo)args.arg2; info.makeCompatible(mTargetSdkVersion); inputMethod.restartInput(ic, info); + args.recycle(); return; } case DO_CREATE_SESSION: { diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 46153e7..f07002e 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -19,6 +19,7 @@ package android.inputmethodservice; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import android.app.ActivityManager; import android.app.Dialog; import android.content.Context; import android.content.res.Configuration; @@ -250,6 +251,7 @@ public class InputMethodService extends AbstractInputMethodService { InputMethodManager mImm; int mTheme = 0; + boolean mHardwareAccelerated = false; LayoutInflater mInflater; TypedArray mThemeAttrs; @@ -614,6 +616,26 @@ public class InputMethodService extends AbstractInputMethodService { mTheme = theme; } + /** + * You can call this to try to enable hardware accelerated drawing for + * your IME. This must be set before {@link #onCreate}, so you + * will typically call it in your constructor. It is not always possible + * to use hardware acclerated drawing in an IME (for example on low-end + * devices that do not have the resources to support this), so the call + * returns true if it succeeds otherwise false if you will need to draw + * in software. You must be able to handle either case. + */ + public boolean enableHardwareAcceleration() { + if (mWindow != null) { + throw new IllegalStateException("Must be called before onCreate()"); + } + if (ActivityManager.isHighEndGfx()) { + mHardwareAccelerated = true; + return true; + } + return false; + } + @Override public void onCreate() { mTheme = Resources.selectSystemTheme(mTheme, getApplicationInfo().targetSdkVersion, @@ -626,6 +648,9 @@ public class InputMethodService extends AbstractInputMethodService { mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this, mTheme, mDispatcherState); + if (mHardwareAccelerated) { + mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + } initViews(); mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); } @@ -658,8 +683,8 @@ public class InputMethodService extends AbstractInputMethodService { com.android.internal.R.layout.input_method, null); mWindow.setContentView(mRootView); mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); - if (Settings.System.getInt(getContentResolver(), - Settings.System.FANCY_IME_ANIMATIONS, 0) != 0) { + if (Settings.Global.getInt(getContentResolver(), + Settings.Global.FANCY_IME_ANIMATIONS, 0) != 0) { mWindow.getWindow().setWindowAnimations( com.android.internal.R.style.Animation_InputMethodFancy); } @@ -1674,9 +1699,6 @@ public class InputMethodService extends AbstractInputMethodService { /** * Show the input method. This is a call back to the * IMF to handle showing the input method. - * Close this input method's soft input area, removing it from the display. - * The input method will continue running, but the user can no longer use - * it to generate input by touching the screen. * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link InputMethodManager#SHOW_FORCED * InputMethodManager.} bit set. @@ -1751,7 +1773,7 @@ public class InputMethodService extends AbstractInputMethodService { * Override this to intercept special key multiple events before they are * processed by the * application. If you return true, the application will not itself - * process the event. If you return true, the normal application processing + * process the event. If you return false, the normal application processing * will occur as if the IME had not seen the event at all. * * <p>The default implementation always returns false, except when @@ -1766,7 +1788,7 @@ public class InputMethodService extends AbstractInputMethodService { /** * Override this to intercept key up events before they are processed by the * application. If you return true, the application will not itself - * process the event. If you return true, the normal application processing + * process the event. If you return false, the normal application processing * will occur as if the IME had not seen the event at all. * * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK @@ -1784,8 +1806,29 @@ public class InputMethodService extends AbstractInputMethodService { return doMovementKey(keyCode, event, MOVEMENT_UP); } + /** + * Override this to intercept trackball motion events before they are + * processed by the application. + * If you return true, the application will not itself process the event. + * If you return false, the normal application processing will occur as if + * the IME had not seen the event at all. + */ @Override public boolean onTrackballEvent(MotionEvent event) { + if (DEBUG) Log.v(TAG, "onTrackballEvent: " + event); + return false; + } + + /** + * Override this to intercept generic motion events before they are + * processed by the application. + * If you return true, the application will not itself process the event. + * If you return false, the normal application processing will occur as if + * the IME had not seen the event at all. + */ + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if (DEBUG) Log.v(TAG, "onGenericMotionEvent(): event " + event); return false; } diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java new file mode 100644 index 0000000..4b60f07 --- /dev/null +++ b/core/java/android/net/BaseNetworkStateTracker.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.content.Context; +import android.os.Handler; + +import com.android.internal.util.Preconditions; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Interface to control and observe state of a specific network, hiding + * network-specific details from {@link ConnectivityManager}. Surfaces events + * through the registered {@link Handler} to enable {@link ConnectivityManager} + * to respond to state changes over time. + * + * @hide + */ +public abstract class BaseNetworkStateTracker implements NetworkStateTracker { + // TODO: better document threading expectations + // TODO: migrate to make NetworkStateTracker abstract class + + public static final String PROP_TCP_BUFFER_UNKNOWN = "net.tcp.buffersize.unknown"; + public static final String PROP_TCP_BUFFER_WIFI = "net.tcp.buffersize.wifi"; + + protected Context mContext; + private Handler mTarget; + + protected NetworkInfo mNetworkInfo; + protected LinkProperties mLinkProperties; + protected LinkCapabilities mLinkCapabilities; + + private AtomicBoolean mTeardownRequested = new AtomicBoolean(false); + private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false); + private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false); + + public BaseNetworkStateTracker(int networkType) { + mNetworkInfo = new NetworkInfo( + networkType, -1, ConnectivityManager.getNetworkTypeName(networkType), null); + mLinkProperties = new LinkProperties(); + mLinkCapabilities = new LinkCapabilities(); + } + + @Deprecated + protected Handler getTargetHandler() { + return mTarget; + } + + protected final void dispatchStateChanged() { + // TODO: include snapshot of other fields when sending + mTarget.obtainMessage(EVENT_STATE_CHANGED, getNetworkInfo()).sendToTarget(); + } + + protected final void dispatchConfigurationChanged() { + // TODO: include snapshot of other fields when sending + mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, getNetworkInfo()).sendToTarget(); + } + + @Override + public final void startMonitoring(Context context, Handler target) { + mContext = Preconditions.checkNotNull(context); + mTarget = Preconditions.checkNotNull(target); + startMonitoringInternal(); + } + + protected abstract void startMonitoringInternal(); + + @Override + public final NetworkInfo getNetworkInfo() { + return new NetworkInfo(mNetworkInfo); + } + + @Override + public final LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); + } + + @Override + public final LinkCapabilities getLinkCapabilities() { + return new LinkCapabilities(mLinkCapabilities); + } + + @Override + public void captivePortalCheckComplete() { + // not implemented + } + + @Override + public boolean setRadio(boolean turnOn) { + // Base tracker doesn't handle radios + return true; + } + + @Override + public boolean isAvailable() { + return mNetworkInfo.isAvailable(); + } + + @Override + public void setUserDataEnable(boolean enabled) { + // Base tracker doesn't handle enabled flags + } + + @Override + public void setPolicyDataEnable(boolean enabled) { + // Base tracker doesn't handle enabled flags + } + + @Override + public boolean isPrivateDnsRouteSet() { + return mPrivateDnsRouteSet.get(); + } + + @Override + public void privateDnsRouteSet(boolean enabled) { + mPrivateDnsRouteSet.set(enabled); + } + + @Override + public boolean isDefaultRouteSet() { + return mDefaultRouteSet.get(); + } + + @Override + public void defaultRouteSet(boolean enabled) { + mDefaultRouteSet.set(enabled); + } + + @Override + public boolean isTeardownRequested() { + return mTeardownRequested.get(); + } + + @Override + public void setTeardownRequested(boolean isRequested) { + mTeardownRequested.set(isRequested); + } + + @Override + public void setDependencyMet(boolean met) { + // Base tracker doesn't handle dependencies + } +} diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java new file mode 100644 index 0000000..19ed658 --- /dev/null +++ b/core/java/android/net/CaptivePortalTracker.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.os.UserHandle; +import android.os.Message; +import android.os.RemoteException; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.util.Log; + +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.URL; +import java.net.UnknownHostException; + +import com.android.internal.R; + +/** + * This class allows captive portal detection on a network. + * @hide + */ +public class CaptivePortalTracker extends StateMachine { + private static final boolean DBG = false; + private static final String TAG = "CaptivePortalTracker"; + + private static final String DEFAULT_SERVER = "clients3.google.com"; + private static final String NOTIFICATION_ID = "CaptivePortal.Notification"; + + private static final int SOCKET_TIMEOUT_MS = 10000; + + private String mServer; + private String mUrl; + private boolean mNotificationShown = false; + private boolean mIsCaptivePortalCheckEnabled = false; + private IConnectivityManager mConnService; + private TelephonyManager mTelephonyManager; + private Context mContext; + private NetworkInfo mNetworkInfo; + + private static final int CMD_DETECT_PORTAL = 0; + private static final int CMD_CONNECTIVITY_CHANGE = 1; + private static final int CMD_DELAYED_CAPTIVE_CHECK = 2; + + /* This delay happens every time before we do a captive check on a network */ + private static final int DELAYED_CHECK_INTERVAL_MS = 10000; + private int mDelayedCheckToken = 0; + + private State mDefaultState = new DefaultState(); + private State mNoActiveNetworkState = new NoActiveNetworkState(); + private State mActiveNetworkState = new ActiveNetworkState(); + private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState(); + + private CaptivePortalTracker(Context context, IConnectivityManager cs) { + super(TAG); + + mContext = context; + mConnService = cs; + mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + mContext.registerReceiver(mReceiver, filter); + + mServer = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.CAPTIVE_PORTAL_SERVER); + if (mServer == null) mServer = DEFAULT_SERVER; + + mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; + + addState(mDefaultState); + addState(mNoActiveNetworkState, mDefaultState); + addState(mActiveNetworkState, mDefaultState); + addState(mDelayedCaptiveCheckState, mActiveNetworkState); + setInitialState(mNoActiveNetworkState); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + NetworkInfo info = intent.getParcelableExtra( + ConnectivityManager.EXTRA_NETWORK_INFO); + sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info)); + } + } + }; + + public static CaptivePortalTracker makeCaptivePortalTracker(Context context, + IConnectivityManager cs) { + CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, cs); + captivePortal.start(); + return captivePortal; + } + + public void detectCaptivePortal(NetworkInfo info) { + sendMessage(obtainMessage(CMD_DETECT_PORTAL, info)); + } + + private class DefaultState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_DETECT_PORTAL: + NetworkInfo info = (NetworkInfo) message.obj; + // Checking on a secondary connection is not supported + // yet + notifyPortalCheckComplete(info); + break; + case CMD_CONNECTIVITY_CHANGE: + case CMD_DELAYED_CAPTIVE_CHECK: + break; + default: + loge("Ignoring " + message); + break; + } + return HANDLED; + } + } + + private class NoActiveNetworkState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + mNetworkInfo = null; + /* Clear any previous notification */ + setNotificationVisible(false); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString() + "\n"); + InetAddress server; + NetworkInfo info; + switch (message.what) { + case CMD_CONNECTIVITY_CHANGE: + info = (NetworkInfo) message.obj; + if (info.isConnected() && isActiveNetwork(info)) { + mNetworkInfo = info; + transitionTo(mDelayedCaptiveCheckState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class ActiveNetworkState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + } + + @Override + public boolean processMessage(Message message) { + NetworkInfo info; + switch (message.what) { + case CMD_CONNECTIVITY_CHANGE: + info = (NetworkInfo) message.obj; + if (!info.isConnected() + && info.getType() == mNetworkInfo.getType()) { + if (DBG) log("Disconnected from active network " + info); + transitionTo(mNoActiveNetworkState); + } else if (info.getType() != mNetworkInfo.getType() && + info.isConnected() && + isActiveNetwork(info)) { + if (DBG) log("Active network switched " + info); + deferMessage(message); + transitionTo(mNoActiveNetworkState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + + + private class DelayedCaptiveCheckState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + sendMessageDelayed(obtainMessage(CMD_DELAYED_CAPTIVE_CHECK, + ++mDelayedCheckToken, 0), DELAYED_CHECK_INTERVAL_MS); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_DELAYED_CAPTIVE_CHECK: + if (message.arg1 == mDelayedCheckToken) { + InetAddress server = lookupHost(mServer); + if (server != null) { + if (isCaptivePortal(server)) { + if (DBG) log("Captive network " + mNetworkInfo); + setNotificationVisible(true); + } + } + if (DBG) log("Not captive network " + mNetworkInfo); + transitionTo(mActiveNetworkState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private void notifyPortalCheckComplete(NetworkInfo info) { + if (info == null) { + loge("notifyPortalCheckComplete on null"); + return; + } + try { + mConnService.captivePortalCheckComplete(info); + } catch(RemoteException e) { + e.printStackTrace(); + } + } + + private boolean isActiveNetwork(NetworkInfo info) { + try { + NetworkInfo active = mConnService.getActiveNetworkInfo(); + if (active != null && active.getType() == info.getType()) { + return true; + } + } catch (RemoteException e) { + e.printStackTrace(); + } + return false; + } + + /** + * Do a URL fetch on a known server to see if we get the data we expect + */ + private boolean isCaptivePortal(InetAddress server) { + HttpURLConnection urlConnection = null; + if (!mIsCaptivePortalCheckEnabled) return false; + + mUrl = "http://" + server.getHostAddress() + "/generate_204"; + if (DBG) log("Checking " + mUrl); + try { + URL url = new URL(mUrl); + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + // we got a valid response, but not from the real google + return urlConnection.getResponseCode() != 204; + } catch (IOException e) { + if (DBG) log("Probably not a portal: exception " + e); + return false; + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + } + + private InetAddress lookupHost(String hostname) { + InetAddress inetAddress[]; + try { + inetAddress = InetAddress.getAllByName(hostname); + } catch (UnknownHostException e) { + return null; + } + + for (InetAddress a : inetAddress) { + if (a instanceof Inet4Address) return a; + } + return null; + } + + private void setNotificationVisible(boolean visible) { + // if it should be hidden and it is already hidden, then noop + if (!visible && !mNotificationShown) { + return; + } + + Resources r = Resources.getSystem(); + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + if (visible) { + CharSequence title; + CharSequence details; + switch (mNetworkInfo.getType()) { + case ConnectivityManager.TYPE_WIFI: + title = r.getString(R.string.wifi_available_sign_in, 0); + details = r.getString(R.string.network_available_sign_in_detailed, + mNetworkInfo.getExtraInfo()); + break; + case ConnectivityManager.TYPE_MOBILE: + title = r.getString(R.string.network_available_sign_in, 0); + // TODO: Change this to pull from NetworkInfo once a printable + // name has been added to it + details = mTelephonyManager.getNetworkOperatorName(); + break; + default: + title = r.getString(R.string.network_available_sign_in, 0); + details = r.getString(R.string.network_available_sign_in_detailed, + mNetworkInfo.getExtraInfo()); + break; + } + + Notification notification = new Notification(); + notification.when = 0; + notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range; + notification.flags = Notification.FLAG_AUTO_CANCEL; + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl)); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); + notification.tickerText = title; + notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); + + notificationManager.notify(NOTIFICATION_ID, 1, notification); + } else { + notificationManager.cancel(NOTIFICATION_ID, 1); + } + mNotificationShown = visible; + } + + private static void log(String s) { + Log.d(TAG, s); + } + + private static void loge(String s) { + Log.e(TAG, s); + } + +} diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 5f8793c..6ff1a33 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -89,11 +89,21 @@ public class ConnectivityManager { * should always obtain network information through * {@link #getActiveNetworkInfo()} or * {@link #getAllNetworkInfo()}. + * @see #EXTRA_NETWORK_TYPE */ @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; /** + * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast. + * Can be used with {@link #getNetworkInfo(int)} to get {@link NetworkInfo} + * state based on the calling application. + * + * @see android.content.Intent#getIntExtra(String, int) + */ + public static final String EXTRA_NETWORK_TYPE = "networkType"; + + /** * The lookup key for a boolean that indicates whether a connect event * is for a network to which the connectivity manager was failing over * following a disconnect on another network. @@ -137,6 +147,28 @@ public class ConnectivityManager { public static final String EXTRA_INET_CONDITION = "inetCondition"; /** + * Broadcast action to indicate the change of data activity status + * (idle or active) on a network in a recent period. + * The network becomes active when data transimission is started, or + * idle if there is no data transimition for a period of time. + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_ACTIVITY_CHANGE = "android.net.conn.DATA_ACTIVITY_CHANGE"; + /** + * The lookup key for an enum that indicates the network device type on which this data activity + * change happens. + * {@hide} + */ + public static final String EXTRA_DEVICE_TYPE = "deviceType"; + /** + * The lookup key for a boolean that indicates the device is active or not. {@code true} means + * it is actively sending or receiving data and {@code false} means it is idle. + * {@hide} + */ + public static final String EXTRA_IS_ACTIVE = "isActive"; + + /** * Broadcast Action: The setting for background data usage has changed * values. Use {@link #getBackgroundDataSetting()} to get the current value. * <p> @@ -298,6 +330,14 @@ public class ConnectivityManager { public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI; + /** + * Default value for {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY} in + * milliseconds. + * + * @hide + */ + public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000; + private final IConnectivityManager mService; public static boolean isNetworkTypeValid(int networkType) { @@ -880,4 +920,24 @@ public class ConnectivityManager { return false; } } + + /** {@hide} */ + public boolean updateLockdownVpn() { + try { + return mService.updateLockdownVpn(); + } catch (RemoteException e) { + return false; + } + } + + /** + * {@hide} + */ + public void captivePortalCheckComplete(NetworkInfo info) { + try { + mService.captivePortalCheckComplete(info); + } catch (RemoteException e) { + } + } + } diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index cc3e34f..874e80a 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -92,10 +92,12 @@ public class DhcpStateMachine extends StateMachine { /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates * success/failure */ public static final int CMD_POST_DHCP_ACTION = BASE + 5; + /* Notification from DHCP state machine before quitting */ + public static final int CMD_ON_QUIT = BASE + 6; /* Command from controller to indicate DHCP discovery/renewal can continue * after pre DHCP action is complete */ - public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 6; + public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 7; /* Message.arg1 arguments to CMD_POST_DHCP notification */ public static final int DHCP_SUCCESS = 1; @@ -172,6 +174,10 @@ public class DhcpStateMachine extends StateMachine { quit(); } + protected void onQuitting() { + mController.sendMessage(CMD_ON_QUIT); + } + class DefaultState extends State { @Override public void exit() { diff --git a/core/java/android/net/DnsPinger.java b/core/java/android/net/DnsPinger.java index 11acabe..66f0fd0 100644 --- a/core/java/android/net/DnsPinger.java +++ b/core/java/android/net/DnsPinger.java @@ -295,8 +295,8 @@ public final class DnsPinger extends Handler { } private InetAddress getDefaultDns() { - String dns = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_DNS_SERVER); + String dns = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.DEFAULT_DNS_SERVER); if (dns == null || dns.length() == 0) { dns = mContext.getResources().getString( com.android.internal.R.string.config_default_dns_server); diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java index ccd96ff..39440c2 100644 --- a/core/java/android/net/DummyDataStateTracker.java +++ b/core/java/android/net/DummyDataStateTracker.java @@ -119,6 +119,10 @@ public class DummyDataStateTracker implements NetworkStateTracker { return true; } + public void captivePortalCheckComplete() { + // not implemented + } + /** * Record the detailed state of a network, and if it is a * change from the previous state, send a notification to diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index 0cc78c9..3a06dc0 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -99,6 +99,10 @@ public class EthernetDataTracker implements NetworkStateTracker { public void limitReached(String limitName, String iface) { // Ignored. } + + public void interfaceClassDataActivityChanged(String label, boolean active) { + // Ignored. + } } private EthernetDataTracker() { @@ -230,6 +234,10 @@ public class EthernetDataTracker implements NetworkStateTracker { mNetworkInfo.setExtraInfo(mHwAddr); } } + + // if a DHCP client had previously been started for this interface, then stop it + NetworkUtils.stopDhcp(mIface); + reconnect(); break; } @@ -266,6 +274,11 @@ public class EthernetDataTracker implements NetworkStateTracker { return mLinkUp; } + @Override + public void captivePortalCheckComplete() { + // not implemented + } + /** * Turn the wireless radio off for a network. * @param turnOn {@code true} to turn the radio on, {@code false} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 92aeff2..056fa03 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -26,6 +26,7 @@ import android.os.ParcelFileDescriptor; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; /** * Interface that answers queries about, and allows changing, the @@ -118,7 +119,11 @@ interface IConnectivityManager ParcelFileDescriptor establishVpn(in VpnConfig config); - void startLegacyVpn(in VpnConfig config, in String[] racoon, in String[] mtpd); + void startLegacyVpn(in VpnProfile profile); LegacyVpnInfo getLegacyVpnInfo(); + + boolean updateLockdownVpn(); + + void captivePortalCheckComplete(in NetworkInfo info); } diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index a97f203..6f4dd5f 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -62,4 +62,11 @@ interface INetworkManagementEventObserver { */ void limitReached(String limitName, String iface); + /** + * Interface data activity status is changed. + * + * @param iface The interface. + * @param active True if the interface is actively transmitting data, false if it is idle. + */ + void interfaceClassDataActivityChanged(String label, boolean active); } diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 3250ae7..df6057e 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -30,9 +30,9 @@ import android.net.NetworkTemplate; interface INetworkPolicyManager { /** Control UID policies. */ - void setAppPolicy(int appId, int policy); - int getAppPolicy(int appId); - int[] getAppsWithPolicy(int policy); + void setUidPolicy(int uid, int policy); + int getUidPolicy(int uid); + int[] getUidsWithPolicy(int policy); boolean isUidForeground(int uid); diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java index 3ee8a80..14a8094 100644 --- a/core/java/android/net/LocalSocket.java +++ b/core/java/android/net/LocalSocket.java @@ -16,6 +16,7 @@ package android.net; +import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; @@ -26,7 +27,7 @@ import java.net.SocketOptions; * Creates a (non-server) socket in the UNIX-domain namespace. The interface * here is not entirely unlike that of java.net.Socket */ -public class LocalSocket { +public class LocalSocket implements Closeable { private LocalSocketImpl impl; private volatile boolean implCreated; @@ -42,6 +43,15 @@ public class LocalSocket { isBound = false; isConnected = false; } + /** + * Creates a AF_LOCAL/UNIX domain stream socket with FileDescriptor. + * @hide + */ + public LocalSocket(FileDescriptor fd) throws IOException { + this(new LocalSocketImpl(fd)); + isBound = true; + isConnected = true; + } /** * for use with AndroidServerSocket @@ -158,6 +168,7 @@ public class LocalSocket { * * @throws IOException */ + @Override public void close() throws IOException { implCreateIfNeeded(); impl.close(); diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index d59fa6a..b35d61c 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -381,6 +381,11 @@ public class MobileDataStateTracker implements NetworkStateTracker { return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED); } + @Override + public void captivePortalCheckComplete() { + // not implemented + } + /** * Record the detailed state of a network, and if it is a * change from the previous state, send a notification to diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 0bc6b58..0b23cb7 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -79,7 +79,9 @@ public class NetworkInfo implements Parcelable { /** Access to this network is blocked. */ BLOCKED, /** Link has poor connectivity. */ - VERIFYING_POOR_LINK + VERIFYING_POOR_LINK, + /** Checking if network is a captive portal */ + CAPTIVE_PORTAL_CHECK, } /** @@ -97,6 +99,7 @@ public class NetworkInfo implements Parcelable { stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING); stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING); stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING); + stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING); stateMap.put(DetailedState.CONNECTED, State.CONNECTED); stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED); stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING); diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 07bfd4b..2cd1f9b 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; import android.os.RemoteException; +import android.os.UserHandle; import android.text.format.Time; import com.google.android.collect.Sets; @@ -72,29 +73,29 @@ public class NetworkPolicyManager { } /** - * Set policy flags for specific application. + * Set policy flags for specific UID. * * @param policy {@link #POLICY_NONE} or combination of flags like * {@link #POLICY_REJECT_METERED_BACKGROUND}. */ - public void setAppPolicy(int appId, int policy) { + public void setUidPolicy(int uid, int policy) { try { - mService.setAppPolicy(appId, policy); + mService.setUidPolicy(uid, policy); } catch (RemoteException e) { } } - public int getAppPolicy(int appId) { + public int getUidPolicy(int uid) { try { - return mService.getAppPolicy(appId); + return mService.getUidPolicy(uid); } catch (RemoteException e) { return POLICY_NONE; } } - public int[] getAppsWithPolicy(int policy) { + public int[] getUidsWithPolicy(int policy) { try { - return mService.getAppsWithPolicy(policy); + return mService.getUidsWithPolicy(policy); } catch (RemoteException e) { return new int[0]; } @@ -236,8 +237,7 @@ public class NetworkPolicyManager { @Deprecated public static boolean isUidValidForPolicy(Context context, int uid) { // first, quick-reject non-applications - if (uid < android.os.Process.FIRST_APPLICATION_UID - || uid > android.os.Process.LAST_APPLICATION_UID) { + if (!UserHandle.isApp(uid)) { return false; } diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java index 0d6dcd6..0a0c1e0 100644 --- a/core/java/android/net/NetworkStateTracker.java +++ b/core/java/android/net/NetworkStateTracker.java @@ -41,12 +41,6 @@ public interface NetworkStateTracker { * ------------------------------------------------------------- */ - // Share the event space with ConnectivityService (which we can't see, but - // must send events to). If you change these, change ConnectivityService - // too. - static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1; - static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100; - /** * The network state has changed and the NetworkInfo object * contains the new state. @@ -129,6 +123,11 @@ public interface NetworkStateTracker { public boolean reconnect(); /** + * Ready to switch on to the network after captive portal check + */ + public void captivePortalCheckComplete(); + + /** * Turn the wireless radio off for a network. * @param turnOn {@code true} to turn the radio on, {@code false} */ diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index fb7a4f8..446bbf0 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -21,6 +21,7 @@ import android.os.Parcelable; import android.os.SystemClock; import android.util.SparseBooleanArray; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Objects; import java.io.CharArrayWriter; @@ -608,13 +609,13 @@ public class NetworkStats implements Parcelable { * Return all rows except those attributed to the requested UID; doesn't * mutate the original structure. */ - public NetworkStats withoutUid(int uid) { + public NetworkStats withoutUids(int[] uids) { final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); Entry entry = new Entry(); for (int i = 0; i < size; i++) { entry = getValues(i, entry); - if (entry.uid != uid) { + if (!ArrayUtils.contains(uids, entry.uid)) { stats.addValues(entry); } } diff --git a/core/java/android/net/ParseException.java b/core/java/android/net/ParseException.java index 000fa68..68b209b 100644 --- a/core/java/android/net/ParseException.java +++ b/core/java/android/net/ParseException.java @@ -17,10 +17,9 @@ package android.net; /** - * - * - * When WebAddress Parser Fails, this exception is thrown + * Thrown when parsing a URL fails. */ +// See non-public class {@link WebAddress}. public class ParseException extends RuntimeException { public String response; diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index 2703f1d..846443d 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -21,6 +21,7 @@ import android.util.Log; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; +import java.net.SocketException; import java.security.KeyManagementException; import java.security.cert.X509Certificate; import javax.net.SocketFactory; @@ -300,9 +301,10 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * null if no protocol was negotiated. * * @param socket a socket created by this factory. + * @throws IllegalArgumentException if the socket was not created by this factory. */ public byte[] getNpnSelectedProtocol(Socket socket) { - return ((OpenSSLSocketImpl) socket).getNpnSelectedProtocol(); + return castToOpenSSLSocket(socket).getNpnSelectedProtocol(); } /** @@ -316,6 +318,54 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { mInsecureFactory = null; } + /** + * Enables <a href="http://tools.ietf.org/html/rfc5077#section-3.2">session ticket</a> + * support on the given socket. + * + * @param socket a socket created by this factory + * @param useSessionTickets {@code true} to enable session ticket support on this socket. + * @throws IllegalArgumentException if the socket was not created by this factory. + */ + public void setUseSessionTickets(Socket socket, boolean useSessionTickets) { + castToOpenSSLSocket(socket).setUseSessionTickets(useSessionTickets); + } + + /** + * Turns on <a href="http://tools.ietf.org/html/rfc6066#section-3">Server + * Name Indication (SNI)</a> on a given socket. + * + * @param socket a socket created by this factory. + * @param hostName the desired SNI hostname, null to disable. + * @throws IllegalArgumentException if the socket was not created by this factory. + */ + public void setHostname(Socket socket, String hostName) { + castToOpenSSLSocket(socket).setHostname(hostName); + } + + /** + * Sets this socket's SO_SNDTIMEO write timeout in milliseconds. + * Use 0 for no timeout. + * To take effect, this option must be set before the blocking method was called. + * + * @param socket a socket created by this factory. + * @param timeout the desired write timeout in milliseconds. + * @throws IllegalArgumentException if the socket was not created by this factory. + * + * @hide + */ + public void setSoWriteTimeout(Socket socket, int writeTimeoutMilliseconds) + throws SocketException { + castToOpenSSLSocket(socket).setSoWriteTimeout(writeTimeoutMilliseconds); + } + + private static OpenSSLSocketImpl castToOpenSSLSocket(Socket socket) { + if (!(socket instanceof OpenSSLSocketImpl)) { + throw new IllegalArgumentException("Socket not created by this factory: " + + socket); + } + + return (OpenSSLSocketImpl) socket; + } /** * {@inheritDoc} diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 3b990e3..cc6903d 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -16,10 +16,13 @@ package android.net; +import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; +import android.os.Environment.UserEnvironment; import android.util.Log; import java.io.File; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charsets; @@ -2288,4 +2291,39 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { builder = builder.appendEncodedPath(pathSegment); return builder.build(); } + + /** + * If this {@link Uri} is {@code file://}, then resolve and return its + * canonical path. Also fixes legacy emulated storage paths so they are + * usable across user boundaries. Should always be called from the app + * process before sending elsewhere. + * + * @hide + */ + public Uri getCanonicalUri() { + if ("file".equals(getScheme())) { + final String canonicalPath; + try { + canonicalPath = new File(getPath()).getCanonicalPath(); + } catch (IOException e) { + return this; + } + + if (Environment.isExternalStorageEmulated()) { + final String legacyPath = Environment.getLegacyExternalStorageDirectory() + .toString(); + + // Splice in user-specific path when legacy path is found + if (canonicalPath.startsWith(legacyPath)) { + return Uri.fromFile(new File( + Environment.getExternalStorageDirectory().toString(), + canonicalPath.substring(legacyPath.length() + 1))); + } + } + + return Uri.fromFile(new File(canonicalPath)); + } else { + return this; + } + } } diff --git a/core/java/android/net/arp/ArpPeer.java b/core/java/android/net/arp/ArpPeer.java index 6ba1e7c..2013b11 100644 --- a/core/java/android/net/arp/ArpPeer.java +++ b/core/java/android/net/arp/ArpPeer.java @@ -16,8 +16,12 @@ package android.net.arp; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.RouteInfo; import android.os.SystemClock; import android.util.Log; + import java.io.IOException; import java.net.InetAddress; import java.net.Inet6Address; @@ -35,6 +39,8 @@ import libcore.net.RawSocket; * @hide */ public class ArpPeer { + private static final boolean DBG = false; + private static final String TAG = "ArpPeer"; private String mInterfaceName; private final InetAddress mMyAddr; private final byte[] mMyMac = new byte[6]; @@ -46,7 +52,6 @@ public class ArpPeer { private static final int ARP_LENGTH = 28; private static final int MAC_ADDR_LENGTH = 6; private static final int IPV4_LENGTH = 4; - private static final String TAG = "ArpPeer"; public ArpPeer(String interfaceName, InetAddress myAddr, String mac, InetAddress peer) throws SocketException { @@ -125,6 +130,41 @@ public class ArpPeer { return null; } + public static boolean doArp(String myMacAddress, LinkProperties linkProperties, + int timeoutMillis, int numArpPings, int minArpResponses) { + String interfaceName = linkProperties.getInterfaceName(); + InetAddress inetAddress = null; + InetAddress gateway = null; + boolean success; + + for (LinkAddress la : linkProperties.getLinkAddresses()) { + inetAddress = la.getAddress(); + break; + } + + for (RouteInfo route : linkProperties.getRoutes()) { + gateway = route.getGateway(); + break; + } + + try { + ArpPeer peer = new ArpPeer(interfaceName, inetAddress, myMacAddress, gateway); + int responses = 0; + for (int i=0; i < numArpPings; i++) { + if(peer.doArp(timeoutMillis) != null) responses++; + } + if (DBG) Log.d(TAG, "ARP test result: " + responses + "/" + numArpPings); + success = (responses >= minArpResponses); + peer.close(); + } catch (SocketException se) { + //Consider an Arp socket creation issue as a successful Arp + //test to avoid any wifi connectivity issues + Log.e(TAG, "ARP test initiation failure: " + se); + success = true; + } + return success; + } + public void close() { try { mSocket.close(); diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java index 6ad8fe3..f66075d 100644 --- a/core/java/android/net/http/CertificateChainValidator.java +++ b/core/java/android/net/http/CertificateChainValidator.java @@ -168,7 +168,13 @@ public class CertificateChainValidator { } try { - SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(chain, authType); + X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultTrustManager(); + if (x509TrustManager instanceof TrustManagerImpl) { + TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager; + trustManager.checkServerTrusted(chain, authType, domain); + } else { + x509TrustManager.checkServerTrusted(chain, authType); + } return null; // No errors. } catch (GeneralSecurityException e) { if (HttpLog.LOGV) { diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java new file mode 100644 index 0000000..64eacbc --- /dev/null +++ b/core/java/android/net/http/X509TrustManagerExtensions.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.http; + +import org.apache.harmony.xnet.provider.jsse.TrustManagerImpl; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; + +import javax.net.ssl.X509TrustManager; + +/** + * X509TrustManager wrapper exposing Android-added features. + * + * <p> The checkServerTrusted method allows callers to perform additional + * verification of certificate chains after they have been successfully + * verified by the platform.</p> + */ +public class X509TrustManagerExtensions { + + TrustManagerImpl mDelegate; + + /** + * Constructs a new X509TrustManagerExtensions wrapper. + * + * @param tm A {@link X509TrustManager} as returned by TrustManagerFactory.getInstance(); + * @throws IllegalArgumentException If tm is an unsupported TrustManager type. + */ + public X509TrustManagerExtensions(X509TrustManager tm) throws IllegalArgumentException { + if (tm instanceof TrustManagerImpl) { + mDelegate = (TrustManagerImpl) tm; + } else { + throw new IllegalArgumentException("tm is not a supported type of X509TrustManager"); + } + } + + /** + * Verifies the given certificate chain. + * + * <p>See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a + * description of the chain and authType parameters. The final parameter, host, should be the + * hostname of the server.</p> + * + * @throws CertificateException if the chain does not verify correctly. + * @return the properly ordered chain used for verification as a list of X509Certificates. + */ + public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType, + String host) throws CertificateException { + return mDelegate.checkServerTrusted(chain, authType, host); + } +} diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java index ed1c5b3..2d9dae9 100644 --- a/core/java/android/nfc/NdefRecord.java +++ b/core/java/android/nfc/NdefRecord.java @@ -688,7 +688,8 @@ public final class NdefRecord implements Parcelable { } } catch (FormatException e) { } } else if (Arrays.equals(mType, RTD_URI)) { - return parseWktUri().normalizeScheme(); + Uri wktUri = parseWktUri(); + return (wktUri != null ? wktUri.normalizeScheme() : null); } break; diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java index f9b765c..f2cd232 100644 --- a/core/java/android/nfc/Tag.java +++ b/core/java/android/nfc/Tag.java @@ -24,6 +24,7 @@ import android.nfc.tech.Ndef; import android.nfc.tech.NdefFormatable; import android.nfc.tech.NfcA; import android.nfc.tech.NfcB; +import android.nfc.tech.NfcBarcode; import android.nfc.tech.NfcF; import android.nfc.tech.NfcV; import android.nfc.tech.TagTechnology; @@ -184,6 +185,9 @@ public final class Tag implements Parcelable { case TagTechnology.NFC_V: strings[i] = NfcV.class.getName(); break; + case TagTechnology.NFC_BARCODE: + strings[i] = NfcBarcode.class.getName(); + break; default: throw new IllegalArgumentException("Unknown tech type " + techList[i]); } diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java index a31cb9c..64aa299 100644 --- a/core/java/android/nfc/tech/Ndef.java +++ b/core/java/android/nfc/tech/Ndef.java @@ -140,8 +140,8 @@ public final class Ndef extends BasicTagTechnology { * * <p>Does not cause any RF activity and does not block. * - * @param tag an MIFARE Classic compatible tag - * @return MIFARE Classic object + * @param tag an NDEF compatible tag + * @return Ndef object */ public static Ndef get(Tag tag) { if (!tag.hasTech(TagTechnology.NDEF)) return null; diff --git a/core/java/android/nfc/tech/NfcBarcode.java b/core/java/android/nfc/tech/NfcBarcode.java new file mode 100644 index 0000000..3149857 --- /dev/null +++ b/core/java/android/nfc/tech/NfcBarcode.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc.tech; + +import android.nfc.Tag; +import android.os.Bundle; +import android.os.RemoteException; + +/** + * Provides access to tags containing just a barcode. + * + * <p>Acquire an {@link NfcBarcode} object using {@link #get}. + * + */ +public final class NfcBarcode extends BasicTagTechnology { + + /** Kovio Tags */ + public static final int TYPE_KOVIO = 1; + public static final int TYPE_UNKNOWN = -1; + + /** @hide */ + public static final String EXTRA_BARCODE_TYPE = "barcodetype"; + + private int mType; + + /** + * Get an instance of {@link NfcBarcode} for the given tag. + * + * <p>Returns null if {@link NfcBarcode} was not enumerated in {@link Tag#getTechList}. + * + * <p>Does not cause any RF activity and does not block. + * + * @param tag an NfcBarcode compatible tag + * @return NfcBarcode object + */ + public static NfcBarcode get(Tag tag) { + if (!tag.hasTech(TagTechnology.NFC_BARCODE)) return null; + try { + return new NfcBarcode(tag); + } catch (RemoteException e) { + return null; + } + } + + /** + * Internal constructor, to be used by NfcAdapter + * @hide + */ + public NfcBarcode(Tag tag) throws RemoteException { + super(tag, TagTechnology.NFC_BARCODE); + Bundle extras = tag.getTechExtras(TagTechnology.NFC_BARCODE); + if (extras != null) { + mType = extras.getInt(EXTRA_BARCODE_TYPE); + } else { + throw new NullPointerException("NfcBarcode tech extras are null."); + } + } + + /** + * Returns the NFC Barcode tag type. + * + * <p>Currently only one of {@link #TYPE_KOVIO} or {@link #TYPE_UNKNOWN}. + * + * <p>Does not cause any RF activity and does not block. + * + * @return the NFC Barcode tag type + */ + public int getType() { + return mType; + } + + /** + * Returns the barcode of an NfcBarcode tag. + * + * <p>Does not cause any RF activity and does not block. + * + * @return a byte array containing the barcode + */ + public byte[] getBarcode() { + switch (mType) { + case TYPE_KOVIO: + // For Kovio tags the barcode matches the ID + return mTag.getId(); + default: + return null; + } + } +} diff --git a/core/java/android/nfc/tech/TagTechnology.java b/core/java/android/nfc/tech/TagTechnology.java index be5cbd2..3493ea7 100644 --- a/core/java/android/nfc/tech/TagTechnology.java +++ b/core/java/android/nfc/tech/TagTechnology.java @@ -148,6 +148,15 @@ public interface TagTechnology extends Closeable { public static final int MIFARE_ULTRALIGHT = 9; /** + * This technology is an instance of {@link NfcBarcode}. + * <p>Support for this technology type is optional. If a stack doesn't support this technology + * type tags using it must still be discovered and present the lower level radio interface + * technologies in use. + * @hide + */ + public static final int NFC_BARCODE = 10; + + /** * Get the {@link Tag} object backing this {@link TagTechnology} object. * @return the {@link Tag} backing this {@link TagTechnology} object. */ diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index c62715b..7b16f4d 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -115,4 +115,6 @@ public class BatteryManager { public static final int BATTERY_PLUGGED_AC = 1; /** Power source is a USB port. */ public static final int BATTERY_PLUGGED_USB = 2; + /** Power source is wireless. */ + public static final int BATTERY_PLUGGED_WIRELESS = 4; } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 438c536..54f2fe3 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -71,9 +71,9 @@ public abstract class BatteryStats implements Parcelable { public static final int FULL_WIFI_LOCK = 5; /** - * A constant indicating a scan wifi lock timer + * A constant indicating a wifi scan */ - public static final int SCAN_WIFI_LOCK = 6; + public static final int WIFI_SCAN = 6; /** * A constant indicating a wifi multicast timer @@ -136,7 +136,7 @@ public abstract class BatteryStats implements Parcelable { private static final String BATTERY_DATA = "bt"; private static final String BATTERY_DISCHARGE_DATA = "dc"; private static final String BATTERY_LEVEL_DATA = "lv"; - private static final String WIFI_LOCK_DATA = "wfl"; + private static final String WIFI_DATA = "wfl"; private static final String MISC_DATA = "m"; private static final String SCREEN_BRIGHTNESS_DATA = "br"; private static final String SIGNAL_STRENGTH_TIME_DATA = "sgt"; @@ -260,8 +260,8 @@ public abstract class BatteryStats implements Parcelable { public abstract void noteWifiStoppedLocked(); public abstract void noteFullWifiLockAcquiredLocked(); public abstract void noteFullWifiLockReleasedLocked(); - public abstract void noteScanWifiLockAcquiredLocked(); - public abstract void noteScanWifiLockReleasedLocked(); + public abstract void noteWifiScanStartedLocked(); + public abstract void noteWifiScanStoppedLocked(); public abstract void noteWifiMulticastEnabledLocked(); public abstract void noteWifiMulticastDisabledLocked(); public abstract void noteAudioTurnedOnLocked(); @@ -270,20 +270,22 @@ public abstract class BatteryStats implements Parcelable { public abstract void noteVideoTurnedOffLocked(); public abstract long getWifiRunningTime(long batteryRealtime, int which); public abstract long getFullWifiLockTime(long batteryRealtime, int which); - public abstract long getScanWifiLockTime(long batteryRealtime, int which); + public abstract long getWifiScanTime(long batteryRealtime, int which); public abstract long getWifiMulticastTime(long batteryRealtime, int which); public abstract long getAudioTurnedOnTime(long batteryRealtime, int which); public abstract long getVideoTurnedOnTime(long batteryRealtime, int which); /** - * Note that these must match the constants in android.os.LocalPowerManager. + * Note that these must match the constants in android.os.PowerManager. + * Also, if the user activity types change, the BatteryStatsImpl.VERSION must + * also be bumped. */ static final String[] USER_ACTIVITY_TYPES = { - "other", "cheek", "touch", "long_touch", "touch_up", "button", "unknown" + "other", "button", "touch" }; - public static final int NUM_USER_ACTIVITY_TYPES = 7; + public static final int NUM_USER_ACTIVITY_TYPES = 3; public abstract void noteUserActivityLocked(int type); public abstract boolean hasUserActivity(); @@ -453,7 +455,7 @@ public abstract class BatteryStats implements Parcelable { public static final int STATE_PHONE_SCANNING_FLAG = 1<<27; public static final int STATE_WIFI_RUNNING_FLAG = 1<<26; public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<25; - public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<24; + public static final int STATE_WIFI_SCAN_FLAG = 1<<24; public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<23; // These are on the lower bits used for the command; if they change // we need to write another int of data. @@ -859,7 +861,7 @@ public abstract class BatteryStats implements Parcelable { new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi"), new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running"), new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock"), - new BitDescription(HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG, "wifi_scan_lock"), + new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan"), new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast"), new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth"), new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio"), @@ -1326,15 +1328,15 @@ public abstract class BatteryStats implements Parcelable { long rx = u.getTcpBytesReceived(which); long tx = u.getTcpBytesSent(which); long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which); - long scanWifiLockOnTime = u.getScanWifiLockTime(batteryRealtime, which); + long wifiScanTime = u.getWifiScanTime(batteryRealtime, which); long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which); if (rx > 0 || tx > 0) dumpLine(pw, uid, category, NETWORK_DATA, rx, tx); - if (fullWifiLockOnTime != 0 || scanWifiLockOnTime != 0 + if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || uidWifiRunningTime != 0) { - dumpLine(pw, uid, category, WIFI_LOCK_DATA, - fullWifiLockOnTime, scanWifiLockOnTime, uidWifiRunningTime); + dumpLine(pw, uid, category, WIFI_DATA, + fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime); } if (u.hasUserActivity()) { @@ -1692,7 +1694,7 @@ public abstract class BatteryStats implements Parcelable { long tcpReceived = u.getTcpBytesReceived(which); long tcpSent = u.getTcpBytesSent(which); long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which); - long scanWifiLockOnTime = u.getScanWifiLockTime(batteryRealtime, which); + long wifiScanTime = u.getWifiScanTime(batteryRealtime, which); long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which); if (tcpReceived != 0 || tcpSent != 0) { @@ -1703,7 +1705,7 @@ public abstract class BatteryStats implements Parcelable { if (u.hasUserActivity()) { boolean hasData = false; - for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { + for (int i=0; i<Uid.NUM_USER_ACTIVITY_TYPES; i++) { int val = u.getUserActivityCount(i, which); if (val != 0) { if (!hasData) { @@ -1723,7 +1725,7 @@ public abstract class BatteryStats implements Parcelable { } } - if (fullWifiLockOnTime != 0 || scanWifiLockOnTime != 0 + if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || uidWifiRunningTime != 0) { sb.setLength(0); sb.append(prefix); sb.append(" Wifi Running: "); @@ -1731,12 +1733,12 @@ public abstract class BatteryStats implements Parcelable { sb.append("("); sb.append(formatRatioLocked(uidWifiRunningTime, whichBatteryRealtime)); sb.append(")\n"); sb.append(prefix); sb.append(" Full Wifi Lock: "); - formatTimeMs(sb, fullWifiLockOnTime / 1000); - sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime, + formatTimeMs(sb, fullWifiLockOnTime / 1000); + sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime, whichBatteryRealtime)); sb.append(")\n"); - sb.append(prefix); sb.append(" Scan Wifi Lock: "); - formatTimeMs(sb, scanWifiLockOnTime / 1000); - sb.append("("); sb.append(formatRatioLocked(scanWifiLockOnTime, + sb.append(prefix); sb.append(" Wifi Scan: "); + formatTimeMs(sb, wifiScanTime / 1000); + sb.append("("); sb.append(formatRatioLocked(wifiScanTime, whichBatteryRealtime)); sb.append(")"); pw.println(sb.toString()); } @@ -2069,6 +2071,9 @@ public abstract class BatteryStats implements Parcelable { case BatteryManager.BATTERY_PLUGGED_USB: pw.print("usb"); break; + case BatteryManager.BATTERY_PLUGGED_WIRELESS: + pw.print("wireless"); + break; default: pw.print(oldPlug); break; diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 7b51119..16b4835 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -64,7 +64,7 @@ public class Binder implements IBinder { public static final native int getCallingPid(); /** - * Return the ID of the user assigned to the process that sent you the + * Return the Linux uid assigned to the process that sent you the * current transaction that is being processed. This uid can be used with * higher-level system services to determine its identity and check * permissions. If the current thread is not currently executing an @@ -73,31 +73,15 @@ public class Binder implements IBinder { public static final native int getCallingUid(); /** - * Return the original ID of the user assigned to the process that sent you the current - * transaction that is being processed. This uid can be used with higher-level system services - * to determine its identity and check permissions. If the current thread is not currently - * executing an incoming transaction, then its own uid is returned. - * <p/> - * This value cannot be reset by calls to {@link #clearCallingIdentity()}. - * @hide - */ - public static final int getOrigCallingUid() { - if (UserId.MU_ENABLED) { - return getOrigCallingUidNative(); - } else { - return getCallingUid(); - } - } - - private static final native int getOrigCallingUidNative(); - - /** - * Utility function to return the user id of the calling process. - * @return userId of the calling process, extracted from the callingUid - * @hide + * Return the UserHandle assigned to the process that sent you the + * current transaction that is being processed. This is the user + * of the caller. It is distinct from {@link #getCallingUid()} in that a + * particular user will have multiple distinct apps running under it each + * with their own uid. If the current thread is not currently executing an + * incoming transaction, then its own UserHandle is returned. */ - public static final int getOrigCallingUser() { - return UserId.getUserId(getOrigCallingUid()); + public static final UserHandle getCallingUserHandle() { + return new UserHandle(UserHandle.getUserId(getCallingUid())); } /** diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 8ec0c69..a7f39d5 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -370,7 +370,7 @@ public class Build { public static final int ICE_CREAM_SANDWICH_MR1 = 15; /** - * Android 4.1. + * June 2012: Android 4.1. * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> @@ -411,6 +411,26 @@ public class Build { * </ul> */ public static final int JELLY_BEAN = 16; + + /** + * Android 4.2: Moar jelly beans! + * + * <p>Applications targeting this or a later release will get these + * new changes in behavior:</p> + * <ul> + * <li>Content Providers: The default value of {@code android:exported} is now + * {@code false}. See + * <a href="{@docRoot}guide/topics/manifest/provider-element.html#exported"> + * the android:exported section</a> in the provider documentation for more details.</li> + * <li>{@link android.view.View#getLayoutDirection() View.getLayoutDirection()} + * can return different values than {@link android.view.View#LAYOUT_DIRECTION_LTR} + * based on the locale etc. + * <li> {@link android.webkit.WebView#addJavascriptInterface(Object, String) + * WebView.addJavascriptInterface} requires explicit annotations on methods + * for them to be accessible from Javascript. + * </ul> + */ + public static final int JELLY_BEAN_MR1 = 17; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 591cd0e..c08bfeb 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -95,7 +95,7 @@ public final class Debug * Default trace file path and file */ private static final String DEFAULT_TRACE_PATH_PREFIX = - Environment.getExternalStorageDirectory().getPath() + "/"; + Environment.getLegacyExternalStorageDirectory().getPath() + "/"; private static final String DEFAULT_TRACE_BODY = "dmtrace"; private static final String DEFAULT_TRACE_EXTENSION = ".trace"; private static final String DEFAULT_TRACE_FILE_PATH = diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 679cf1a..3315566 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -16,9 +16,10 @@ package android.os; -import android.content.res.Resources; import android.os.storage.IMountService; +import android.os.storage.StorageManager; import android.os.storage.StorageVolume; +import android.text.TextUtils; import android.util.Log; import java.io.File; @@ -29,31 +30,146 @@ import java.io.File; public class Environment { private static final String TAG = "Environment"; + private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE"; + private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE"; + private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET"; + private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE"; + + /** {@hide} */ + public static String DIRECTORY_ANDROID = "Android"; + private static final File ROOT_DIRECTORY = getDirectory("ANDROID_ROOT", "/system"); private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; - private static final Object mLock = new Object(); + private static UserEnvironment sCurrentUser; + + private static final Object sLock = new Object(); - private volatile static StorageVolume mPrimaryVolume = null; + // @GuardedBy("sLock") + private static volatile StorageVolume sPrimaryVolume; private static StorageVolume getPrimaryVolume() { - if (mPrimaryVolume == null) { - synchronized (mLock) { - if (mPrimaryVolume == null) { + if (sPrimaryVolume == null) { + synchronized (sLock) { + if (sPrimaryVolume == null) { try { IMountService mountService = IMountService.Stub.asInterface(ServiceManager .getService("mount")); - Parcelable[] volumes = mountService.getVolumeList(); - mPrimaryVolume = (StorageVolume)volumes[0]; + final StorageVolume[] volumes = mountService.getVolumeList(); + sPrimaryVolume = StorageManager.getPrimaryVolume(volumes); } catch (Exception e) { Log.e(TAG, "couldn't talk to MountService", e); } } } } - return mPrimaryVolume; + return sPrimaryVolume; + } + + static { + initForCurrentUser(); + } + + /** {@hide} */ + public static void initForCurrentUser() { + final int userId = UserHandle.myUserId(); + sCurrentUser = new UserEnvironment(userId); + + synchronized (sLock) { + sPrimaryVolume = null; + } + } + + /** {@hide} */ + public static class UserEnvironment { + // TODO: generalize further to create package-specific environment + + private final File mExternalStorage; + private final File mExternalStorageAndroidData; + private final File mExternalStorageAndroidMedia; + private final File mExternalStorageAndroidObb; + private final File mMediaStorage; + + public UserEnvironment(int userId) { + // See storage config details at http://source.android.com/tech/storage/ + String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE); + String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET); + String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE); + if (TextUtils.isEmpty(rawMediaStorage)) { + rawMediaStorage = "/data/media"; + } + + if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) { + // Device has emulated storage; external storage paths should have + // userId burned into them. + final String rawUserId = Integer.toString(userId); + final File emulatedBase = new File(rawEmulatedStorageTarget); + final File mediaBase = new File(rawMediaStorage); + + // /storage/emulated/0 + mExternalStorage = buildPath(emulatedBase, rawUserId); + // /data/media/0 + mMediaStorage = buildPath(mediaBase, rawUserId); + + } else { + // Device has physical external storage; use plain paths. + if (TextUtils.isEmpty(rawExternalStorage)) { + Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default"); + rawExternalStorage = "/storage/sdcard0"; + } + + // /storage/sdcard0 + mExternalStorage = new File(rawExternalStorage); + // /data/media + mMediaStorage = new File(rawMediaStorage); + } + + mExternalStorageAndroidObb = buildPath(mExternalStorage, DIRECTORY_ANDROID, "obb"); + mExternalStorageAndroidData = buildPath(mExternalStorage, DIRECTORY_ANDROID, "data"); + mExternalStorageAndroidMedia = buildPath(mExternalStorage, DIRECTORY_ANDROID, "media"); + } + + public File getExternalStorageDirectory() { + return mExternalStorage; + } + + public File getExternalStorageObbDirectory() { + return mExternalStorageAndroidObb; + } + + public File getExternalStoragePublicDirectory(String type) { + return new File(mExternalStorage, type); + } + + public File getExternalStorageAndroidDataDir() { + return mExternalStorageAndroidData; + } + + public File getExternalStorageAppDataDirectory(String packageName) { + return new File(mExternalStorageAndroidData, packageName); + } + + public File getExternalStorageAppMediaDirectory(String packageName) { + return new File(mExternalStorageAndroidMedia, packageName); + } + + public File getExternalStorageAppObbDirectory(String packageName) { + return new File(mExternalStorageAndroidObb, packageName); + } + + public File getExternalStorageAppFilesDirectory(String packageName) { + return new File(new File(mExternalStorageAndroidData, packageName), "files"); + } + + public File getExternalStorageAppCacheDirectory(String packageName) { + return new File(new File(mExternalStorageAndroidData, packageName), "cache"); + } + + public File getMediaStorageDirectory() { + return mMediaStorage; + } } /** @@ -100,7 +216,19 @@ public class Environment { * @hide */ public static File getMediaStorageDirectory() { - return MEDIA_STORAGE_DIRECTORY; + throwIfSystem(); + return sCurrentUser.getMediaStorageDirectory(); + } + + /** + * Return the system directory for a user. This is for use by system services to store + * files relating to the user. This directory will be automatically deleted when the user + * is removed. + * + * @hide + */ + public static File getUserSystemDirectory(int userId) { + return new File(new File(getSystemSecureDirectory(), "users"), Integer.toString(userId)); } /** @@ -122,24 +250,7 @@ public class Environment { private static final File SECURE_DATA_DIRECTORY = getDirectory("ANDROID_SECURE_DATA", "/data/secure"); - /** @hide */ - private static final File MEDIA_STORAGE_DIRECTORY - = getDirectory("MEDIA_STORAGE", "/data/media"); - - private static final File EXTERNAL_STORAGE_DIRECTORY - = getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"); - - private static final File EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY = new File(new File( - getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "data"); - - private static final File EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY = new File(new File( - getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "media"); - - private static final File EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY = new File(new File( - getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "obb"); - - private static final File DOWNLOAD_CACHE_DIRECTORY - = getDirectory("DOWNLOAD_CACHE", "/cache"); + private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache"); /** * Gets the Android data directory. @@ -187,7 +298,30 @@ public class Environment { * @see #isExternalStorageRemovable() */ public static File getExternalStorageDirectory() { - return EXTERNAL_STORAGE_DIRECTORY; + throwIfSystem(); + return sCurrentUser.getExternalStorageDirectory(); + } + + /** {@hide} */ + public static File getLegacyExternalStorageDirectory() { + return new File(System.getenv(ENV_EXTERNAL_STORAGE)); + } + + /** {@hide} */ + public static File getLegacyExternalStorageObbDirectory() { + return buildPath(getLegacyExternalStorageDirectory(), DIRECTORY_ANDROID, "obb"); + } + + /** {@hide} */ + public static File getEmulatedStorageSource(int userId) { + // /mnt/shell/emulated/0 + return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId)); + } + + /** {@hide} */ + public static File getEmulatedStorageObbSource() { + // /mnt/shell/emulated/obb + return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), "obb"); } /** @@ -307,7 +441,8 @@ public class Environment { * using it such as with {@link File#mkdirs File.mkdirs()}. */ public static File getExternalStoragePublicDirectory(String type) { - return new File(getExternalStorageDirectory(), type); + throwIfSystem(); + return sCurrentUser.getExternalStoragePublicDirectory(type); } /** @@ -315,7 +450,8 @@ public class Environment { * @hide */ public static File getExternalStorageAndroidDataDir() { - return EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY; + throwIfSystem(); + return sCurrentUser.getExternalStorageAndroidDataDir(); } /** @@ -323,7 +459,8 @@ public class Environment { * @hide */ public static File getExternalStorageAppDataDirectory(String packageName) { - return new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, packageName); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppDataDirectory(packageName); } /** @@ -331,7 +468,8 @@ public class Environment { * @hide */ public static File getExternalStorageAppMediaDirectory(String packageName) { - return new File(EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY, packageName); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppMediaDirectory(packageName); } /** @@ -339,7 +477,8 @@ public class Environment { * @hide */ public static File getExternalStorageAppObbDirectory(String packageName) { - return new File(EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY, packageName); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppObbDirectory(packageName); } /** @@ -347,17 +486,17 @@ public class Environment { * @hide */ public static File getExternalStorageAppFilesDirectory(String packageName) { - return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, - packageName), "files"); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppFilesDirectory(packageName); } - + /** * Generates the path to an application's cache. * @hide */ public static File getExternalStorageAppCacheDirectory(String packageName) { - return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, - packageName), "cache"); + throwIfSystem(); + return sCurrentUser.getExternalStorageAppCacheDirectory(packageName); } /** @@ -430,9 +569,10 @@ public class Environment { try { IMountService mountService = IMountService.Stub.asInterface(ServiceManager .getService("mount")); - return mountService.getVolumeState(getExternalStorageDirectory() - .toString()); - } catch (Exception rex) { + final StorageVolume primary = getPrimaryVolume(); + return mountService.getVolumeState(primary.getPath()); + } catch (RemoteException rex) { + Log.w(TAG, "Failed to read external storage state; assuming REMOVED: " + rex); return Environment.MEDIA_REMOVED; } } @@ -446,8 +586,8 @@ public class Environment { * <p>See {@link #getExternalStorageDirectory()} for more information. */ public static boolean isExternalStorageRemovable() { - StorageVolume volume = getPrimaryVolume(); - return (volume != null && volume.isRemovable()); + final StorageVolume primary = getPrimaryVolume(); + return (primary != null && primary.isRemovable()); } /** @@ -464,12 +604,30 @@ public class Environment { * android.content.ComponentName, boolean)} for additional details. */ public static boolean isExternalStorageEmulated() { - StorageVolume volume = getPrimaryVolume(); - return (volume != null && volume.isEmulated()); + final StorageVolume primary = getPrimaryVolume(); + return (primary != null && primary.isEmulated()); } static File getDirectory(String variableName, String defaultPath) { String path = System.getenv(variableName); return path == null ? new File(defaultPath) : new File(path); } + + private static void throwIfSystem() { + if (Process.myUid() == Process.SYSTEM_UID) { + Log.wtf(TAG, "Static storage paths aren't available from AID_SYSTEM", new Throwable()); + } + } + + private static File buildPath(File base, String... segments) { + File cur = base; + for (String segment : segments) { + if (cur == null) { + cur = new File(segment); + } else { + cur = new File(cur, segment); + } + } + return cur; + } } diff --git a/core/java/android/os/FactoryTest.java b/core/java/android/os/FactoryTest.java new file mode 100644 index 0000000..ec99697 --- /dev/null +++ b/core/java/android/os/FactoryTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * Provides support for in-place factory test functions. + * + * This class provides a few properties that alter the normal operation of the system + * during factory testing. + * + * {@hide} + */ +public final class FactoryTest { + /** + * When true, long-press on power should immediately cause the device to + * shut down, without prompting the user. + */ + public static boolean isLongPressOnPowerOffEnabled() { + return SystemProperties.getInt("factory.long_press_power_off", 0) != 0; + } +} diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index b892c81..94de448 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -101,36 +101,88 @@ public class Handler { } /** - * Default constructor associates this handler with the queue for the + * Default constructor associates this handler with the {@link Looper} for the * current thread. * - * If there isn't one, this handler won't be able to receive messages. + * If this thread does not have a looper, this handler won't be able to receive messages + * so an exception is thrown. */ public Handler() { - if (FIND_POTENTIAL_LEAKS) { - final Class<? extends Handler> klass = getClass(); - if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && - (klass.getModifiers() & Modifier.STATIC) == 0) { - Log.w(TAG, "The following Handler class should be static or leaks might occur: " + - klass.getCanonicalName()); - } - } - - mLooper = Looper.myLooper(); - if (mLooper == null) { - throw new RuntimeException( - "Can't create handler inside thread that has not called Looper.prepare()"); - } - mQueue = mLooper.mQueue; - mCallback = null; + this(null, false); } /** - * Constructor associates this handler with the queue for the + * Constructor associates this handler with the {@link Looper} for the * current thread and takes a callback interface in which you can handle * messages. + * + * If this thread does not have a looper, this handler won't be able to receive messages + * so an exception is thrown. + * + * @param callback The callback interface in which to handle messages, or null. */ public Handler(Callback callback) { + this(callback, false); + } + + /** + * Use the provided {@link Looper} instead of the default one. + * + * @param looper The looper, must not be null. + */ + public Handler(Looper looper) { + this(looper, null, false); + } + + /** + * Use the provided {@link Looper} instead of the default one and take a callback + * interface in which to handle messages. + * + * @param looper The looper, must not be null. + * @param callback The callback interface in which to handle messages, or null. + */ + public Handler(Looper looper, Callback callback) { + this(looper, callback, false); + } + + /** + * Use the {@link Looper} for the current thread + * and set whether the handler should be asynchronous. + * + * Handlers are synchronous by default unless this constructor is used to make + * one that is strictly asynchronous. + * + * Asynchronous messages represent interrupts or events that do not require global ordering + * with represent to synchronous messages. Asynchronous messages are not subject to + * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. + * + * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for + * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. + * + * @hide + */ + public Handler(boolean async) { + this(null, async); + } + + /** + * Use the {@link Looper} for the current thread with the specified callback interface + * and set whether the handler should be asynchronous. + * + * Handlers are synchronous by default unless this constructor is used to make + * one that is strictly asynchronous. + * + * Asynchronous messages represent interrupts or events that do not require global ordering + * with represent to synchronous messages. Asynchronous messages are not subject to + * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. + * + * @param callback The callback interface in which to handle messages, or null. + * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for + * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. + * + * @hide + */ + public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && @@ -147,25 +199,33 @@ public class Handler { } mQueue = mLooper.mQueue; mCallback = callback; + mAsynchronous = async; } /** - * Use the provided queue instead of the default one. - */ - public Handler(Looper looper) { - mLooper = looper; - mQueue = looper.mQueue; - mCallback = null; - } - - /** - * Use the provided queue instead of the default one and take a callback - * interface in which to handle messages. + * Use the provided {@link Looper} instead of the default one and take a callback + * interface in which to handle messages. Also set whether the handler + * should be asynchronous. + * + * Handlers are synchronous by default unless this constructor is used to make + * one that is strictly asynchronous. + * + * Asynchronous messages represent interrupts or events that do not require global ordering + * with represent to synchronous messages. Asynchronous messages are not subject to + * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. + * + * @param looper The looper, must not be null. + * @param callback The callback interface in which to handle messages, or null. + * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for + * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. + * + * @hide */ - public Handler(Looper looper, Callback callback) { + public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; + mAsynchronous = async; } /** @@ -352,6 +412,58 @@ public class Handler { } /** + * Runs the specified task synchronously. + * + * If the current thread is the same as the handler thread, then the runnable + * runs immediately without being enqueued. Otherwise, posts the runnable + * to the handler and waits for it to complete before returning. + * + * This method is dangerous! Improper use can result in deadlocks. + * Never call this method while any locks are held or use it in a + * possibly re-entrant manner. + * + * This method is occasionally useful in situations where a background thread + * must synchronously await completion of a task that must run on the + * handler's thread. However, this problem is often a symptom of bad design. + * Consider improving the design (if possible) before resorting to this method. + * + * One example of where you might want to use this method is when you just + * set up a Handler thread and need to perform some initialization steps on + * it before continuing execution. + * + * If timeout occurs then this method returns <code>false</code> but the runnable + * will remain posted on the handler and may already be in progress or + * complete at a later time. + * + * @param r The Runnable that will be executed synchronously. + * @param timeout The timeout in milliseconds, or 0 to wait indefinitely. + * + * @return Returns true if the Runnable was successfully executed. + * Returns false on failure, usually because the + * looper processing the message queue is exiting. + * + * @hide This method is prone to abuse and should probably not be in the API. + * If we ever do make it part of the API, we might want to rename it to something + * less funny like runUnsafe(). + */ + public final boolean runWithScissors(final Runnable r, long timeout) { + if (r == null) { + throw new IllegalArgumentException("runnable must not be null"); + } + if (timeout < 0) { + throw new IllegalArgumentException("timeout must be non-negative"); + } + + if (Looper.myLooper() == mLooper) { + r.run(); + return true; + } + + BlockingRunnable br = new BlockingRunnable(r); + return br.postAndWait(this, timeout); + } + + /** * Remove any pending posts of Runnable r that are in the message queue. */ public final void removeCallbacks(Runnable r) @@ -464,20 +576,15 @@ public class Handler { * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ - public boolean sendMessageAtTime(Message msg, long uptimeMillis) - { - boolean sent = false; + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; - if (queue != null) { - msg.target = this; - sent = queue.enqueueMessage(msg, uptimeMillis); - } - else { + if (queue == null) { RuntimeException e = new RuntimeException( - this + " sendMessageAtTime() called with no mQueue"); + this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); + return false; } - return sent; + return enqueueMessage(queue, msg, uptimeMillis); } /** @@ -492,20 +599,23 @@ public class Handler { * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ - public final boolean sendMessageAtFrontOfQueue(Message msg) - { - boolean sent = false; + public final boolean sendMessageAtFrontOfQueue(Message msg) { MessageQueue queue = mQueue; - if (queue != null) { - msg.target = this; - sent = queue.enqueueMessage(msg, 0); - } - else { + if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); + return false; + } + return enqueueMessage(queue, msg, 0); + } + + private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { + msg.target = this; + if (mAsynchronous) { + msg.setAsynchronous(true); } - return sent; + return queue.enqueueMessage(msg, uptimeMillis); } /** @@ -618,5 +728,57 @@ public class Handler { final MessageQueue mQueue; final Looper mLooper; final Callback mCallback; + final boolean mAsynchronous; IMessenger mMessenger; + + private static final class BlockingRunnable implements Runnable { + private final Runnable mTask; + private boolean mDone; + + public BlockingRunnable(Runnable task) { + mTask = task; + } + + @Override + public void run() { + try { + mTask.run(); + } finally { + synchronized (this) { + mDone = true; + notifyAll(); + } + } + } + + public boolean postAndWait(Handler handler, long timeout) { + if (!handler.post(this)) { + return false; + } + + synchronized (this) { + if (timeout > 0) { + final long expirationTime = SystemClock.uptimeMillis() + timeout; + while (!mDone) { + long delay = expirationTime - SystemClock.uptimeMillis(); + if (delay <= 0) { + return false; // timeout + } + try { + wait(delay); + } catch (InterruptedException ex) { + } + } + } else { + while (!mDone) { + try { + wait(); + } catch (InterruptedException ex) { + } + } + } + } + return true; + } + } } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index e7ea355..2179fa1 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -152,6 +152,16 @@ interface INetworkManagementService boolean isTetheringStarted(); /** + * Start bluetooth reverse tethering services + */ + void startReverseTethering(in String iface); + + /** + * Stop currently running bluetooth reserse tethering services + */ + void stopReverseTethering(); + + /** * Tethers the specified interface */ void tetherInterface(String iface); @@ -218,17 +228,17 @@ interface INetworkManagementService /** * Start Wifi Access Point */ - void startAccessPoint(in WifiConfiguration wifiConfig, String wlanIface, String softapIface); + void startAccessPoint(in WifiConfiguration wifiConfig, String iface); /** * Stop Wifi Access Point */ - void stopAccessPoint(String wlanIface); + void stopAccessPoint(String iface); /** * Set Access Point config */ - void setAccessPoint(in WifiConfiguration wifiConfig, String wlanIface, String softapIface); + void setAccessPoint(in WifiConfiguration wifiConfig, String iface); /** ** DATA USAGE RELATED @@ -313,6 +323,27 @@ interface INetworkManagementService int getInterfaceTxThrottle(String iface); /** + * Sets idletimer for an interface. + * + * This either initializes a new idletimer or increases its + * reference-counting if an idletimer already exists for given + * {@code iface}. + * + * {@code label} usually represents the network type of {@code iface}. + * Caller should ensure that {@code label} for an {@code iface} remains the + * same for all calls to addIdleTimer. + * + * Every {@code addIdleTimer} should be paired with a + * {@link removeIdleTimer} to cleanup when the network disconnects. + */ + void addIdleTimer(String iface, int timeout, String label); + + /** + * Removes idletimer for an interface. + */ + void removeIdleTimer(String iface); + + /** * Sets the name of the default interface in the DNS resolver. */ void setDefaultInterfaceForDns(String iface); @@ -331,4 +362,11 @@ interface INetworkManagementService * Flush the DNS cache associated with the specified interface. */ void flushInterfaceDnsCache(String iface); + + void setFirewallEnabled(boolean enabled); + boolean isFirewallEnabled(); + void setFirewallInterfaceRule(String iface, boolean allow); + void setFirewallEgressSourceRule(String addr, boolean allow); + void setFirewallEgressDestRule(String addr, int port, boolean allow); + void setFirewallUidRule(int uid, boolean allow); } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 270e9be..557d3f3 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -23,27 +23,31 @@ import android.os.WorkSource; interface IPowerManager { - // WARNING: changes in acquireWakeLock() signature must be reflected in IPowerManager.cpp/h - void acquireWakeLock(int flags, IBinder lock, String tag, in WorkSource ws); - void updateWakeLockWorkSource(IBinder lock, in WorkSource ws); - void goToSleep(long time); - void goToSleepWithReason(long time, int reason); - // WARNING: changes in releaseWakeLock() signature must be reflected in IPowerManager.cpp/h + // WARNING: The first two methods must remain the first two methods because their + // transaction numbers must not change unless IPowerManager.cpp is also updated. + void acquireWakeLock(IBinder lock, int flags, String tag, in WorkSource ws); void releaseWakeLock(IBinder lock, int flags); - void userActivity(long when, boolean noChangeLights); - void userActivityWithForce(long when, boolean noChangeLights, boolean force); - void clearUserActivityTimeout(long now, long timeout); - void setPokeLock(int pokey, IBinder lock, String tag); - int getSupportedWakeLockFlags(); - void setStayOnSetting(int val); - void setMaximumScreenOffTimeount(int timeMs); - void preventScreenOn(boolean prevent); + + void updateWakeLockWorkSource(IBinder lock, in WorkSource ws); + boolean isWakeLockLevelSupported(int level); + + void userActivity(long time, int event, int flags); + void wakeUp(long time); + void goToSleep(long time, int reason); + void nap(long time); + boolean isScreenOn(); void reboot(String reason); void crash(String message); - // sets the brightness of the backlights (screen, keyboard, button) 0-255 - void setBacklightBrightness(int brightness); + void setStayOnSetting(int val); + void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs); + + // temporarily overrides the screen brightness settings to allow the user to + // see the effect of a settings change without applying it immediately + void setTemporaryScreenBrightnessSettingOverride(int brightness); + void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj); + + // sets the attention light (used by phone app only) void setAttentionLight(boolean on, int color); - void setAutoBrightnessAdjustment(float adj); } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl new file mode 100644 index 0000000..ec02ae0 --- /dev/null +++ b/core/java/android/os/IUserManager.aidl @@ -0,0 +1,40 @@ +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.os; + +import android.os.ParcelFileDescriptor; +import android.content.pm.UserInfo; +import android.graphics.Bitmap; + +/** + * {@hide} + */ +interface IUserManager { + UserInfo createUser(in String name, int flags); + boolean removeUser(int userHandle); + void setUserName(int userHandle, String name); + void setUserIcon(int userHandle, in Bitmap icon); + Bitmap getUserIcon(int userHandle); + List<UserInfo> getUsers(boolean excludeDying); + UserInfo getUserInfo(int userHandle); + void setGuestEnabled(boolean enable); + boolean isGuestEnabled(); + void wipeUser(int userHandle); + int getUserSerialNumber(int userHandle); + int getUserHandle(int userSerialNumber); +} diff --git a/core/java/android/os/LocalPowerManager.java b/core/java/android/os/LocalPowerManager.java deleted file mode 100644 index 52df1f8..0000000 --- a/core/java/android/os/LocalPowerManager.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os; - -/** @hide */ -public interface LocalPowerManager { - // Note: be sure to update BatteryStats if adding or modifying event constants. - - public static final int OTHER_EVENT = 0; - public static final int BUTTON_EVENT = 1; - public static final int TOUCH_EVENT = 2; - - public static final int POKE_LOCK_IGNORE_TOUCH_EVENTS = 0x1; - - public static final int POKE_LOCK_SHORT_TIMEOUT = 0x2; - public static final int POKE_LOCK_MEDIUM_TIMEOUT = 0x4; - public static final int POKE_LOCK_TIMEOUT_MASK = 0x6; - - void goToSleep(long time); - - // notify power manager when keyboard is opened/closed - void setKeyboardVisibility(boolean visible); - - // when the keyguard is up, it manages the power state, and userActivity doesn't do anything. - void enableUserActivity(boolean enabled); - - // the same as the method on PowerManager - void userActivity(long time, boolean noChangeLights, int eventType); - - boolean isScreenOn(); - - void setScreenBrightnessOverride(int brightness); - void setButtonBrightnessOverride(int brightness); -} diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 903c8b3..ae50ddb 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -16,22 +16,25 @@ package android.os; +import android.content.Context; import android.util.Log; /** - * This class gives you control of the power state of the device. - * - * <p><b>Device battery life will be significantly affected by the use of this API.</b> Do not - * acquire WakeLocks unless you really need them, use the minimum levels possible, and be sure - * to release it as soon as you can. - * - * <p>You can obtain an instance of this class by calling + * This class gives you control of the power state of the device. + * + * <p> + * <b>Device battery life will be significantly affected by the use of this API.</b> + * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels + * possible, and be sure to release them as soon as possible. + * </p><p> + * You can obtain an instance of this class by calling * {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. - * - * <p>The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}. This will - * create a {@link PowerManager.WakeLock} object. You can then use methods on this object to - * control the power state of the device. In practice it's quite simple: - * + * </p><p> + * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}. + * This will create a {@link PowerManager.WakeLock} object. You can then use methods + * on the wake lock object to control the power state of the device. + * </p><p> + * In practice it's quite simple: * {@samplecode * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag"); @@ -39,11 +42,11 @@ import android.util.Log; * ..screen will stay on during this section.. * wl.release(); * } - * - * <p>The following flags are defined, with varying effects on system power. <i>These flags are - * mutually exclusive - you may only specify one of them.</i> - * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> + * </p><p> + * The following wake lock levels are defined, with varying effects on system power. + * <i>These levels are mutually exclusive - you may only specify one of them.</i> * + * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> * <thead> * <tr><th>Flag Value</th> * <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr> @@ -67,15 +70,16 @@ import android.util.Log; * </tr> * </tbody> * </table> - * - * <p>*<i>If you hold a partial wakelock, the CPU will continue to run, irrespective of any timers - * and even after the user presses the power button. In all other wakelocks, the CPU will run, but - * the user can still put the device to sleep using the power button.</i> - * - * <p>In addition, you can add two more flags, which affect behavior of the screen only. <i>These - * flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i> - * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> + * </p><p> + * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any + * display timeouts or the state of the screen and even after the user presses the power button. + * In all other wake locks, the CPU will run, but the user can still put the device to sleep + * using the power button.</i> + * </p><p> + * In addition, you can add two more flags, which affect behavior of the screen only. + * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i> * + * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> * <thead> * <tr><th>Flag Value</th> <th>Description</th></tr> * </thead> @@ -96,113 +100,132 @@ import android.util.Log; * </tr> * </tbody> * </table> - * + * </p><p> * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK} * permission in an {@code <uses-permission>} element of the application's manifest. + * </p> */ -public class PowerManager -{ +public final class PowerManager { private static final String TAG = "PowerManager"; - - /** - * These internal values define the underlying power elements that we might - * want to control individually. Eventually we'd like to expose them. + + /* NOTE: Wake lock levels were previously defined as a bit field, except that only a few + * combinations were actually supported so the bit field was removed. This explains + * why the numbering scheme is so odd. If adding a new wake lock level, any unused + * value can be used. */ - private static final int WAKE_BIT_CPU_STRONG = 1; - private static final int WAKE_BIT_CPU_WEAK = 2; - private static final int WAKE_BIT_SCREEN_DIM = 4; - private static final int WAKE_BIT_SCREEN_BRIGHT = 8; - private static final int WAKE_BIT_KEYBOARD_BRIGHT = 16; - private static final int WAKE_BIT_PROXIMITY_SCREEN_OFF = 32; - - private static final int LOCK_MASK = WAKE_BIT_CPU_STRONG - | WAKE_BIT_CPU_WEAK - | WAKE_BIT_SCREEN_DIM - | WAKE_BIT_SCREEN_BRIGHT - | WAKE_BIT_KEYBOARD_BRIGHT - | WAKE_BIT_PROXIMITY_SCREEN_OFF; /** - * Wake lock that ensures that the CPU is running. The screen might - * not be on. + * Wake lock level: Ensures that the CPU is running; the screen and keyboard + * backlight will be allowed to go off. + * <p> + * If the user presses the power button, then the screen will be turned off + * but the CPU will be kept on until all partial wake locks have been released. + * </p> */ - public static final int PARTIAL_WAKE_LOCK = WAKE_BIT_CPU_STRONG; + public static final int PARTIAL_WAKE_LOCK = 0x00000001; /** - * Wake lock that ensures that the screen and keyboard are on at - * full brightness. + * Wake lock level: Ensures that the screen is on (but may be dimmed); + * the keyboard backlight will be allowed to go off. + * <p> + * If the user presses the power button, then the {@link #SCREEN_DIM_WAKE_LOCK} will be + * implicitly released by the system, causing both the screen and the CPU to be turned off. + * Contrast with {@link #PARTIAL_WAKE_LOCK}. + * </p> * - * <p class="note">Most applications should strongly consider using - * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}. - * This window flag will be correctly managed by the platform - * as the user moves between applications and doesn't require a special permission.</p> + * @deprecated Most applications should use + * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead + * of this type of wake lock, as it will be correctly managed by the platform + * as the user moves between applications and doesn't require a special permission. */ - public static final int FULL_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT - | WAKE_BIT_KEYBOARD_BRIGHT; + @Deprecated + public static final int SCREEN_DIM_WAKE_LOCK = 0x00000006; /** + * Wake lock level: Ensures that the screen is on at full brightness; + * the keyboard backlight will be allowed to go off. + * <p> + * If the user presses the power button, then the {@link #SCREEN_BRIGHT_WAKE_LOCK} will be + * implicitly released by the system, causing both the screen and the CPU to be turned off. + * Contrast with {@link #PARTIAL_WAKE_LOCK}. + * </p> + * * @deprecated Most applications should use * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead * of this type of wake lock, as it will be correctly managed by the platform * as the user moves between applications and doesn't require a special permission. - * - * Wake lock that ensures that the screen is on at full brightness; - * the keyboard backlight will be allowed to go off. */ @Deprecated - public static final int SCREEN_BRIGHT_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT; + public static final int SCREEN_BRIGHT_WAKE_LOCK = 0x0000000a; /** - * Wake lock that ensures that the screen is on (but may be dimmed); - * the keyboard backlight will be allowed to go off. + * Wake lock level: Ensures that the screen and keyboard backlight are on at + * full brightness. + * <p> + * If the user presses the power button, then the {@link #FULL_WAKE_LOCK} will be + * implicitly released by the system, causing both the screen and the CPU to be turned off. + * Contrast with {@link #PARTIAL_WAKE_LOCK}. + * </p> + * + * @deprecated Most applications should use + * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead + * of this type of wake lock, as it will be correctly managed by the platform + * as the user moves between applications and doesn't require a special permission. */ - public static final int SCREEN_DIM_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_DIM; + @Deprecated + public static final int FULL_WAKE_LOCK = 0x0000001a; /** - * Wake lock that turns the screen off when the proximity sensor activates. - * Since not all devices have proximity sensors, use - * {@link #getSupportedWakeLockFlags() getSupportedWakeLockFlags()} to determine if - * this wake lock mode is supported. + * Wake lock level: Turns the screen off when the proximity sensor activates. + * <p> + * Since not all devices have proximity sensors, use {@link #isWakeLockLevelSupported} + * to determine whether this wake lock level is supported. + * </p> * * {@hide} */ - public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = WAKE_BIT_PROXIMITY_SCREEN_OFF; + public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 0x00000020; /** - * Flag for {@link WakeLock#release release(int)} to defer releasing a - * {@link #WAKE_BIT_PROXIMITY_SCREEN_OFF} wakelock until the proximity sensor returns - * a negative value. + * Mask for the wake lock level component of a combined wake lock level and flags integer. * - * {@hide} + * @hide */ - public static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1; + public static final int WAKE_LOCK_LEVEL_MASK = 0x0000ffff; /** + * Wake lock flag: Turn the screen on when the wake lock is acquired. + * <p> * Normally wake locks don't actually wake the device, they just cause - * it to remain on once it's already on. Think of the video player - * app as the normal behavior. Notifications that pop up and want + * the screen to remain on once it's already on. Think of the video player + * application as the normal behavior. Notifications that pop up and want * the device to be on are the exception; use this flag to be like them. - * <p> - * Does not work with PARTIAL_WAKE_LOCKs. + * </p><p> + * Cannot be used with {@link #PARTIAL_WAKE_LOCK}. + * </p> */ public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000; /** - * When this wake lock is released, poke the user activity timer + * Wake lock flag: When this wake lock is released, poke the user activity timer * so the screen stays on for a little longer. * <p> - * Will not turn the screen on if it is not already on. See {@link #ACQUIRE_CAUSES_WAKEUP} - * if you want that. - * <p> - * Does not work with PARTIAL_WAKE_LOCKs. + * Will not turn the screen on if it is not already on. + * See {@link #ACQUIRE_CAUSES_WAKEUP} if you want that. + * </p><p> + * Cannot be used with {@link #PARTIAL_WAKE_LOCK}. + * </p> */ public static final int ON_AFTER_RELEASE = 0x20000000; /** - * Brightness value to use when battery is low. - * @hide + * Flag for {@link WakeLock#release release(int)} to defer releasing a + * {@link #WAKE_BIT_PROXIMITY_SCREEN_OFF} wake lock until the proximity sensor returns + * a negative value. + * + * {@hide} */ - public static final int BRIGHTNESS_LOW_BATTERY = 10; + public static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1; /** * Brightness value for fully on. @@ -211,46 +234,181 @@ public class PowerManager public static final int BRIGHTNESS_ON = 255; /** - * Brightness value for dim backlight. + * Brightness value for fully off. + * @hide + */ + public static final int BRIGHTNESS_OFF = 0; + + // Note: Be sure to update android.os.BatteryStats and PowerManager.h + // if adding or modifying user activity event constants. + + /** + * User activity event type: Unspecified event type. * @hide */ - public static final int BRIGHTNESS_DIM = 20; + public static final int USER_ACTIVITY_EVENT_OTHER = 0; /** - * Brightness value for fully off. + * User activity event type: Button or key pressed or released. * @hide */ - public static final int BRIGHTNESS_OFF = 0; + public static final int USER_ACTIVITY_EVENT_BUTTON = 1; /** - * Class lets you say that you need to have the device on. - * <p> - * Call release when you are done and don't need the lock anymore. + * User activity event type: Touch down, move or up. + * @hide + */ + public static final int USER_ACTIVITY_EVENT_TOUCH = 2; + + /** + * User activity flag: Do not restart the user activity timeout or brighten + * the display in response to user activity if it is already dimmed. + * @hide + */ + public static final int USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS = 1 << 0; + + /** + * Go to sleep reason code: Going to sleep due by user request. + * @hide + */ + public static final int GO_TO_SLEEP_REASON_USER = 0; + + /** + * Go to sleep reason code: Going to sleep due by request of the + * device administration policy. + * @hide + */ + public static final int GO_TO_SLEEP_REASON_DEVICE_ADMIN = 1; + + /** + * Go to sleep reason code: Going to sleep due to a screen timeout. + * @hide + */ + public static final int GO_TO_SLEEP_REASON_TIMEOUT = 2; + + final Context mContext; + final IPowerManager mService; + final Handler mHandler; + + /** + * {@hide} + */ + public PowerManager(Context context, IPowerManager service, Handler handler) { + mContext = context; + mService = service; + mHandler = handler; + } + + /** + * Gets the minimum supported screen brightness setting. + * The screen may be allowed to become dimmer than this value but + * this is the minimum value that can be set by the user. + * @hide + */ + public int getMinimumScreenBrightnessSetting() { + return mContext.getResources().getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingMinimum); + } + + /** + * Gets the maximum supported screen brightness setting. + * The screen may be allowed to become dimmer than this value but + * this is the maximum value that can be set by the user. + * @hide + */ + public int getMaximumScreenBrightnessSetting() { + return mContext.getResources().getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingMaximum); + } + + /** + * Gets the default screen brightness setting. + * @hide + */ + public int getDefaultScreenBrightnessSetting() { + return mContext.getResources().getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingDefault); + } + + /** + * Returns true if the screen auto-brightness adjustment setting should + * be available in the UI. This setting is experimental and disabled by default. + * @hide + */ + public static boolean useScreenAutoBrightnessAdjustmentFeature() { + return SystemProperties.getBoolean("persist.power.useautobrightadj", false); + } + + /** + * Returns true if the twilight service should be used to adjust screen brightness + * policy. This setting is experimental and disabled by default. + * @hide + */ + public static boolean useTwilightAdjustmentFeature() { + return SystemProperties.getBoolean("persist.power.usetwilightadj", false); + } + + /** + * Creates a new wake lock with the specified level and flags. * <p> - * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK} - * permission in an {@code <uses-permission>} element of the application's manifest. + * The {@code levelAndFlags} parameter specifies a wake lock level and optional flags + * combined using the logical OR operator. + * </p><p> + * The wake lock levels are: {@link #PARTIAL_WAKE_LOCK}, + * {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK} + * and {@link #SCREEN_BRIGHT_WAKE_LOCK}. Exactly one wake lock level must be + * specified as part of the {@code levelAndFlags} parameter. + * </p><p> + * The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP} + * and {@link #ON_AFTER_RELEASE}. Multiple flags can be combined as part of the + * {@code levelAndFlags} parameters. + * </p><p> + * Call {@link WakeLock#acquire() acquire()} on the object to acquire the + * wake lock, and {@link WakeLock#release release()} when you are done. + * </p><p> + * {@samplecode + * PowerManager pm = (PowerManager)mContext.getSystemService( + * Context.POWER_SERVICE); + * PowerManager.WakeLock wl = pm.newWakeLock( + * PowerManager.SCREEN_DIM_WAKE_LOCK + * | PowerManager.ON_AFTER_RELEASE, + * TAG); + * wl.acquire(); + * // ... do work... + * wl.release(); + * } + * </p><p> + * Although a wake lock can be created without special permissions, + * the {@link android.Manifest.permission#WAKE_LOCK} permission is + * required to actually acquire or release the wake lock that is returned. + * </p><p class="note"> + * If using this to keep the screen on, you should strongly consider using + * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead. + * This window flag will be correctly managed by the platform + * as the user moves between applications and doesn't require a special permission. + * </p> + * + * @param levelAndFlags Combination of wake lock level and flag values defining + * the requested behavior of the WakeLock. + * @param tag Your class name (or other tag) for debugging purposes. + * + * @see WakeLock#acquire() + * @see WakeLock#release() + * @see #PARTIAL_WAKE_LOCK + * @see #FULL_WAKE_LOCK + * @see #SCREEN_DIM_WAKE_LOCK + * @see #SCREEN_BRIGHT_WAKE_LOCK + * @see #ACQUIRE_CAUSES_WAKEUP + * @see #ON_AFTER_RELEASE */ - public class WakeLock - { - static final int RELEASE_WAKE_LOCK = 1; + public WakeLock newWakeLock(int levelAndFlags, String tag) { + validateWakeLockParameters(levelAndFlags, tag); + return new WakeLock(levelAndFlags, tag); + } - Runnable mReleaser = new Runnable() { - public void run() { - release(); - } - }; - - int mFlags; - String mTag; - IBinder mToken; - int mCount = 0; - boolean mRefCounted = true; - boolean mHeld = false; - WorkSource mWorkSource; - - WakeLock(int flags, String tag) - { - switch (flags & LOCK_MASK) { + /** @hide */ + public static void validateWakeLockParameters(int levelAndFlags, String tag) { + switch (levelAndFlags & WAKE_LOCK_LEVEL_MASK) { case PARTIAL_WAKE_LOCK: case SCREEN_DIM_WAKE_LOCK: case SCREEN_BRIGHT_WAKE_LOCK: @@ -258,42 +416,282 @@ public class PowerManager case PROXIMITY_SCREEN_OFF_WAKE_LOCK: break; default: - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Must specify a valid wake lock level."); + } + if (tag == null) { + throw new IllegalArgumentException("The tag must not be null."); + } + } + + /** + * Notifies the power manager that user activity happened. + * <p> + * Resets the auto-off timer and brightens the screen if the device + * is not asleep. This is what happens normally when a key or the touch + * screen is pressed or when some other user activity occurs. + * This method does not wake up the device if it has been put to sleep. + * </p><p> + * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. + * </p> + * + * @param when The time of the user activity, in the {@link SystemClock#uptimeMillis()} + * time base. This timestamp is used to correctly order the user activity request with + * other power management functions. It should be set + * to the timestamp of the input event that caused the user activity. + * @param noChangeLights If true, does not cause the keyboard backlight to turn on + * because of this event. This is set when the power key is pressed. + * We want the device to stay on while the button is down, but we're about + * to turn off the screen so we don't want the keyboard backlight to turn on again. + * Otherwise the lights flash on and then off and it looks weird. + * + * @see #wakeUp + * @see #goToSleep + */ + public void userActivity(long when, boolean noChangeLights) { + try { + mService.userActivity(when, USER_ACTIVITY_EVENT_OTHER, + noChangeLights ? USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS : 0); + } catch (RemoteException e) { + } + } + + /** + * Forces the device to go to sleep. + * <p> + * Overrides all the wake locks that are held. + * This is what happens when the power key is pressed to turn off the screen. + * </p><p> + * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. + * </p> + * + * @param time The time when the request to go to sleep was issued, in the + * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly + * order the go to sleep request with other power management functions. It should be set + * to the timestamp of the input event that caused the request to go to sleep. + * + * @see #userActivity + * @see #wakeUp + */ + public void goToSleep(long time) { + try { + mService.goToSleep(time, GO_TO_SLEEP_REASON_USER); + } catch (RemoteException e) { + } + } + + /** + * Forces the device to wake up from sleep. + * <p> + * If the device is currently asleep, wakes it up, otherwise does nothing. + * This is what happens when the power key is pressed to turn on the screen. + * </p><p> + * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. + * </p> + * + * @param time The time when the request to wake up was issued, in the + * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly + * order the wake up request with other power management functions. It should be set + * to the timestamp of the input event that caused the request to wake up. + * + * @see #userActivity + * @see #goToSleep + */ + public void wakeUp(long time) { + try { + mService.wakeUp(time); + } catch (RemoteException e) { + } + } + + /** + * Forces the device to start napping. + * <p> + * If the device is currently awake, starts dreaming, otherwise does nothing. + * When the dream ends or if the dream cannot be started, the device will + * either wake up or go to sleep depending on whether there has been recent + * user activity. + * </p><p> + * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. + * </p> + * + * @param time The time when the request to nap was issued, in the + * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly + * order the nap request with other power management functions. It should be set + * to the timestamp of the input event that caused the request to nap. + * + * @see #wakeUp + * @see #goToSleep + * + * @hide + */ + public void nap(long time) { + try { + mService.nap(time); + } catch (RemoteException e) { + } + } + + /** + * Sets the brightness of the backlights (screen, keyboard, button). + * <p> + * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. + * </p> + * + * @param brightness The brightness value from 0 to 255. + * + * {@hide} + */ + public void setBacklightBrightness(int brightness) { + try { + mService.setTemporaryScreenBrightnessSettingOverride(brightness); + } catch (RemoteException e) { + } + } + + /** + * Returns true if the specified wake lock level is supported. + * + * @param level The wake lock level to check. + * @return True if the specified wake lock level is supported. + * + * {@hide} + */ + public boolean isWakeLockLevelSupported(int level) { + try { + return mService.isWakeLockLevelSupported(level); + } catch (RemoteException e) { + return false; + } + } + + /** + * Returns whether the screen is currently on. + * <p> + * Only indicates whether the screen is on. The screen could be either bright or dim. + * </p><p> + * {@samplecode + * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + * boolean isScreenOn = pm.isScreenOn(); + * } + * </p> + * + * @return whether the screen is on (bright or dim). + */ + public boolean isScreenOn() { + try { + return mService.isScreenOn(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Reboot the device. Will not return if the reboot is successful. + * <p> + * Requires the {@link android.Manifest.permission#REBOOT} permission. + * </p> + * + * @param reason code to pass to the kernel (e.g., "recovery") to + * request special boot modes, or null. + */ + public void reboot(String reason) { + try { + mService.reboot(reason); + } catch (RemoteException e) { + } + } + + /** + * A wake lock is a mechanism to indicate that your application needs + * to have the device stay on. + * <p> + * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK} + * permission in an {@code <uses-permission>} element of the application's manifest. + * Obtain a wake lock by calling {@link PowerManager#newWakeLock(int, String)}. + * </p><p> + * Call {@link #acquire()} to acquire the wake lock and force the device to stay + * on at the level that was requested when the wake lock was created. + * </p><p> + * Call {@link #release()} when you are done and don't need the lock anymore. + * It is very important to do this as soon as possible to avoid running down the + * device's battery excessively. + * </p> + */ + public final class WakeLock { + private final int mFlags; + private final String mTag; + private final IBinder mToken; + private int mCount; + private boolean mRefCounted = true; + private boolean mHeld; + private WorkSource mWorkSource; + + private final Runnable mReleaser = new Runnable() { + public void run() { + release(); } + }; + WakeLock(int flags, String tag) { mFlags = flags; mTag = tag; mToken = new Binder(); } + @Override + protected void finalize() throws Throwable { + synchronized (mToken) { + if (mHeld) { + Log.wtf(TAG, "WakeLock finalized while still held: " + mTag); + try { + mService.releaseWakeLock(mToken, 0); + } catch (RemoteException e) { + } + } + } + } + /** - * Sets whether this WakeLock is ref counted. - * - * <p>Wake locks are reference counted by default. + * Sets whether this WakeLock is reference counted. + * <p> + * Wake locks are reference counted by default. If a wake lock is + * reference counted, then each call to {@link #acquire()} must be + * balanced by an equal number of calls to {@link #release()}. If a wake + * lock is not reference counted, then one call to {@link #release()} is + * sufficient to undo the effect of all previous calls to {@link #acquire()}. + * </p> * - * @param value true for ref counted, false for not ref counted. + * @param value True to make the wake lock reference counted, false to + * make the wake lock non-reference counted. */ - public void setReferenceCounted(boolean value) - { - mRefCounted = value; + public void setReferenceCounted(boolean value) { + synchronized (mToken) { + mRefCounted = value; + } } /** - * Makes sure the device is on at the level you asked when you created - * the wake lock. + * Acquires the wake lock. + * <p> + * Ensures that the device is on at the level requested when + * the wake lock was created. + * </p> */ - public void acquire() - { + public void acquire() { synchronized (mToken) { acquireLocked(); } } /** - * Makes sure the device is on at the level you asked when you created - * the wake lock. The lock will be released after the given timeout. - * - * @param timeout Release the lock after the give timeout in milliseconds. + * Acquires the wake lock with a timeout. + * <p> + * Ensures that the device is on at the level requested when + * the wake lock was created. The lock will be released after the given timeout + * expires. + * </p> + * + * @param timeout The timeout after which to release the wake lock, in milliseconds. */ public void acquire(long timeout) { synchronized (mToken) { @@ -301,12 +699,18 @@ public class PowerManager mHandler.postDelayed(mReleaser, timeout); } } - + private void acquireLocked() { if (!mRefCounted || mCount++ == 0) { + // Do this even if the wake lock is already thought to be held (mHeld == true) + // because non-reference counted wake locks are not always properly released. + // For example, the keyguard's wake lock might be forcibly released by the + // power manager without the keyguard knowing. A subsequent call to acquire + // should immediately acquire the wake lock once again despite never having + // been explicitly released by the keyguard. mHandler.removeCallbacks(mReleaser); try { - mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource); + mService.acquireWakeLock(mToken, mFlags, mTag, mWorkSource); } catch (RemoteException e) { } mHeld = true; @@ -314,24 +718,27 @@ public class PowerManager } /** - * Release your claim to the CPU or screen being on. - * + * Releases the wake lock. * <p> - * It may turn off shortly after you release it, or it may not if there - * are other wake locks held. + * This method releases your claim to the CPU or screen being on. + * The screen may turn off shortly after you release the wake lock, or it may + * not if there are other wake locks still held. + * </p> */ public void release() { release(0); } /** - * Release your claim to the CPU or screen being on. - * @param flags Combination of flag values to modify the release behavior. - * Currently only {@link #WAIT_FOR_PROXIMITY_NEGATIVE} is supported. - * + * Releases the wake lock with flags to modify the release behavior. * <p> - * It may turn off shortly after you release it, or it may not if there - * are other wake locks held. + * This method releases your claim to the CPU or screen being on. + * The screen may turn off shortly after you release the wake lock, or it may + * not if there are other wake locks still held. + * </p> + * + * @param flags Combination of flag values to modify the release behavior. + * Currently only {@link #WAIT_FOR_PROXIMITY_NEGATIVE} is supported. * * {@hide} */ @@ -339,11 +746,13 @@ public class PowerManager synchronized (mToken) { if (!mRefCounted || --mCount == 0) { mHandler.removeCallbacks(mReleaser); - try { - mService.releaseWakeLock(mToken, flags); - } catch (RemoteException e) { + if (mHeld) { + try { + mService.releaseWakeLock(mToken, flags); + } catch (RemoteException e) { + } + mHeld = false; } - mHeld = false; } if (mCount < 0) { throw new RuntimeException("WakeLock under-locked " + mTag); @@ -351,23 +760,40 @@ public class PowerManager } } - public boolean isHeld() - { + /** + * Returns true if the wake lock has been acquired but not yet released. + * + * @return True if the wake lock is held. + */ + public boolean isHeld() { synchronized (mToken) { return mHeld; } } + /** + * Sets the work source associated with the wake lock. + * <p> + * The work source is used to determine on behalf of which application + * the wake lock is being held. This is useful in the case where a + * service is performing work on behalf of an application so that the + * cost of that work can be accounted to the application. + * </p> + * + * @param ws The work source, or null if none. + */ public void setWorkSource(WorkSource ws) { synchronized (mToken) { if (ws != null && ws.size() == 0) { ws = null; } - boolean changed = true; + + final boolean changed; if (ws == null) { + changed = mWorkSource != null; mWorkSource = null; } else if (mWorkSource == null) { - changed = mWorkSource != null; + changed = true; mWorkSource = new WorkSource(ws); } else { changed = mWorkSource.diff(ws); @@ -375,6 +801,7 @@ public class PowerManager mWorkSource.set(ws); } } + if (changed && mHeld) { try { mService.updateWakeLockWorkSource(mToken, mWorkSource); @@ -384,6 +811,7 @@ public class PowerManager } } + @Override public String toString() { synchronized (mToken) { return "WakeLock{" @@ -391,194 +819,5 @@ public class PowerManager + " held=" + mHeld + ", refCount=" + mCount + "}"; } } - - @Override - protected void finalize() throws Throwable - { - synchronized (mToken) { - if (mHeld) { - Log.wtf(TAG, "WakeLock finalized while still held: " + mTag); - try { - mService.releaseWakeLock(mToken, 0); - } catch (RemoteException e) { - } - } - } - } } - - /** - * Get a wake lock at the level of the flags parameter. Call - * {@link WakeLock#acquire() acquire()} on the object to acquire the - * wake lock, and {@link WakeLock#release release()} when you are done. - * - * {@samplecode - *PowerManager pm = (PowerManager)mContext.getSystemService( - * Context.POWER_SERVICE); - *PowerManager.WakeLock wl = pm.newWakeLock( - * PowerManager.SCREEN_DIM_WAKE_LOCK - * | PowerManager.ON_AFTER_RELEASE, - * TAG); - *wl.acquire(); - * // ... - *wl.release(); - * } - * - * <p class="note">If using this to keep the screen on, you should strongly consider using - * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead. - * This window flag will be correctly managed by the platform - * as the user moves between applications and doesn't require a special permission.</p> - * - * @param flags Combination of flag values defining the requested behavior of the WakeLock. - * @param tag Your class name (or other tag) for debugging purposes. - * - * @see WakeLock#acquire() - * @see WakeLock#release() - */ - public WakeLock newWakeLock(int flags, String tag) - { - if (tag == null) { - throw new NullPointerException("tag is null in PowerManager.newWakeLock"); - } - return new WakeLock(flags, tag); - } - - /** - * User activity happened. - * <p> - * Turns the device from whatever state it's in to full on, and resets - * the auto-off timer. - * - * @param when is used to order this correctly with the wake lock calls. - * This time should be in the {@link SystemClock#uptimeMillis - * SystemClock.uptimeMillis()} time base. - * @param noChangeLights should be true if you don't want the lights to - * turn on because of this event. This is set when the power - * key goes down. We want the device to stay on while the button - * is down, but we're about to turn off. Otherwise the lights - * flash on and then off and it looks weird. - */ - public void userActivity(long when, boolean noChangeLights) - { - try { - mService.userActivity(when, noChangeLights); - } catch (RemoteException e) { - } - } - - /** - * Force the device to go to sleep. Overrides all the wake locks that are - * held. - * - * @param time is used to order this correctly with the wake lock calls. - * The time should be in the {@link SystemClock#uptimeMillis - * SystemClock.uptimeMillis()} time base. - */ - public void goToSleep(long time) - { - try { - mService.goToSleep(time); - } catch (RemoteException e) { - } - } - - /** - * sets the brightness of the backlights (screen, keyboard, button). - * - * @param brightness value from 0 to 255 - * - * {@hide} - */ - public void setBacklightBrightness(int brightness) - { - try { - mService.setBacklightBrightness(brightness); - } catch (RemoteException e) { - } - } - - /** - * Returns the set of flags for {@link #newWakeLock(int, String) newWakeLock()} - * that are supported on the device. - * For example, to test to see if the {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} - * is supported: - * - * {@samplecode - * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - * int supportedFlags = pm.getSupportedWakeLockFlags(); - * boolean proximitySupported = ((supportedFlags & PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) - * == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK); - * } - * - * @return the set of supported WakeLock flags. - * - * {@hide} - */ - public int getSupportedWakeLockFlags() - { - try { - return mService.getSupportedWakeLockFlags(); - } catch (RemoteException e) { - return 0; - } - } - - /** - * Returns whether the screen is currently on. The screen could be bright - * or dim. - * - * {@samplecode - * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - * boolean isScreenOn = pm.isScreenOn(); - * } - * - * @return whether the screen is on (bright or dim). - */ - public boolean isScreenOn() - { - try { - return mService.isScreenOn(); - } catch (RemoteException e) { - return false; - } - } - - /** - * Reboot the device. Will not return if the reboot is - * successful. Requires the {@link android.Manifest.permission#REBOOT} - * permission. - * - * @param reason code to pass to the kernel (e.g., "recovery") to - * request special boot modes, or null. - */ - public void reboot(String reason) - { - try { - mService.reboot(reason); - } catch (RemoteException e) { - } - } - - private PowerManager() - { - } - - /** - * {@hide} - */ - public PowerManager(IPowerManager service, Handler handler) - { - mService = service; - mHandler = handler; - } - - /** - * TODO: It would be nice to be able to set the poke lock here, - * but I'm not sure what would be acceptable as an interface - - * either a PokeLock object (like WakeLock) or, possibly just a - * method call to set the poke lock. - */ - - IPowerManager mService; - Handler mHandler; } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 6ab4dc1..05099fb 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -116,6 +116,12 @@ public class Process { public static final int NFC_UID = 1027; /** + * Defines the UID/GID for the Bluetooth service process. + * @hide + */ + public static final int BLUETOOTH_UID = 1002; + + /** * Defines the GID for the group that allows write access to the internal media storage. * @hide */ @@ -146,10 +152,24 @@ public class Process { public static final int LAST_ISOLATED_UID = 99999; /** + * First gid for applications to share resources. Used when forward-locking + * is enabled but all UserHandles need to be able to read the resources. + * @hide + */ + public static final int FIRST_SHARED_APPLICATION_GID = 50000; + + /** + * Last gid for applications to share resources. Used when forward-locking + * is enabled but all UserHandles need to be able to read the resources. + * @hide + */ + public static final int LAST_SHARED_APPLICATION_GID = 59999; + + /** * Defines a secondary group id for access to the bluetooth hardware. */ public static final int BLUETOOTH_GID = 2000; - + /** * Standard priority of application threads. * Use with {@link #setThreadPriority(int)} and @@ -370,12 +390,13 @@ public class Process { public static final ProcessStartResult start(final String processClass, final String niceName, int uid, int gid, int[] gids, - int debugFlags, int targetSdkVersion, + int debugFlags, int mountExternal, + int targetSdkVersion, String seInfo, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, - debugFlags, targetSdkVersion, seInfo, zygoteArgs); + debugFlags, mountExternal, targetSdkVersion, seInfo, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -547,7 +568,8 @@ public class Process { final String niceName, final int uid, final int gid, final int[] gids, - int debugFlags, int targetSdkVersion, + int debugFlags, int mountExternal, + int targetSdkVersion, String seInfo, String[] extraArgs) throws ZygoteStartFailedEx { @@ -574,6 +596,11 @@ public class Process { if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) { argsForZygote.add("--enable-assert"); } + if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) { + argsForZygote.add("--mount-external-multiuser"); + } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) { + argsForZygote.add("--mount-external-multiuser-all"); + } argsForZygote.add("--target-sdk-version=" + targetSdkVersion); //TODO optionally enable debuger @@ -634,16 +661,29 @@ public class Process { public static final native int myTid(); /** - * Returns the identifier of this process's user. + * Returns the identifier of this process's uid. This is the kernel uid + * that the process is running under, which is the identity of its + * app-specific sandbox. It is different from {@link #myUserHandle} in that + * a uid identifies a specific app sandbox in a specific user. */ public static final native int myUid(); /** + * Returns this process's user handle. This is the + * user the process is running under. It is distinct from + * {@link #myUid()} in that a particular user will have multiple + * distinct apps running under it each with their own uid. + */ + public static final UserHandle myUserHandle() { + return new UserHandle(UserHandle.getUserId(myUid())); + } + + /** * Returns whether the current process is in an isolated sandbox. * @hide */ public static final boolean isIsolated() { - int uid = UserId.getAppId(myUid()); + int uid = UserHandle.getAppId(myUid()); return uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID; } diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 43cf74e..480fe7d 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -40,6 +40,7 @@ import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -326,7 +327,8 @@ public class RecoverySystem { throws IOException { String filename = packageFile.getCanonicalPath(); Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); - String arg = "--update_package=" + filename; + String arg = "--update_package=" + filename + + "\n--locale=" + Locale.getDefault().toString(); bootCommand(context, arg); } @@ -346,7 +348,8 @@ public class RecoverySystem { final ConditionVariable condition = new ConditionVariable(); Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION"); - context.sendOrderedBroadcast(intent, android.Manifest.permission.MASTER_CLEAR, + context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER, + android.Manifest.permission.MASTER_CLEAR, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -357,7 +360,7 @@ public class RecoverySystem { // Block until the ordered broadcast has completed. condition.block(); - bootCommand(context, "--wipe_data"); + bootCommand(context, "--wipe_data\n--locale=" + Locale.getDefault().toString()); } /** @@ -365,7 +368,7 @@ public class RecoverySystem { * @throws IOException if something goes wrong. */ public static void rebootWipeCache(Context context) throws IOException { - bootCommand(context, "--wipe_cache"); + bootCommand(context, "--wipe_cache\n--locale=" + Locale.getDefault().toString()); } /** diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index b74af16..d02a320 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -304,4 +304,25 @@ public class RemoteCallbackList<E extends IInterface> { mBroadcastCount = -1; } + + /** + * Returns the number of registered callbacks. Note that the number of registered + * callbacks may differ from the value returned by {@link #beginBroadcast()} since + * the former returns the number of callbacks registered at the time of the call + * and the second the number of callback to which the broadcast will be delivered. + * <p> + * This function is useful to decide whether to schedule a broadcast if this + * requires doing some work which otherwise would not be performed. + * </p> + * + * @return The size. + */ + public int getRegisteredCallbackCount() { + synchronized (mCallbacks) { + if (mKilled) { + return 0; + } + return mCallbacks.size(); + } + } } diff --git a/core/java/android/os/SchedulingPolicyService.java b/core/java/android/os/SchedulingPolicyService.java index 94f907b..a3fede6 100644 --- a/core/java/android/os/SchedulingPolicyService.java +++ b/core/java/android/os/SchedulingPolicyService.java @@ -33,7 +33,7 @@ public class SchedulingPolicyService extends ISchedulingPolicyService.Stub { // Minimum and maximum values allowed for requestPriority parameter prio private static final int PRIORITY_MIN = 1; - private static final int PRIORITY_MAX = 2; + private static final int PRIORITY_MAX = 3; public SchedulingPolicyService() { } diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index 7291739..c9adf45 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -46,15 +46,16 @@ package android.os; * such as {@link Thread#sleep(long) Thread.sleep(millls)}, * {@link Object#wait(long) Object.wait(millis)}, and * {@link System#nanoTime System.nanoTime()}. This clock is guaranteed - * to be monotonic, and is the recommended basis for the general purpose - * interval timing of user interface events, performance measurements, - * and anything else that does not need to measure elapsed time during - * device sleep. Most methods that accept a timestamp value expect the - * {@link #uptimeMillis} clock. + * to be monotonic, and is suitable for interval timing when the + * interval does not span device sleep. Most methods that accept a + * timestamp value currently expect the {@link #uptimeMillis} clock. + * + * <li> <p> {@link #elapsedRealtime} and {@link #elapsedRealtimeNanos} + * return the time since the system was booted, and include deep sleep. + * This clock is guaranteed to be monotonic, and continues to tick even + * when the CPU is in power saving modes, so is the recommend basis + * for general purpose interval timing. * - * <li> <p> {@link #elapsedRealtime} is counted in milliseconds since the - * system was booted, including deep sleep. This clock should be used - * when measuring time intervals that may span periods of system sleep. * </ul> * * There are several mechanisms for controlling the timing of events: @@ -150,7 +151,14 @@ public final class SystemClock { * @return elapsed milliseconds since boot. */ native public static long elapsedRealtime(); - + + /** + * Returns nanoseconds since boot, including time spent in sleep. + * + * @return elapsed nanoseconds since boot. + */ + public static native long elapsedRealtimeNanos(); + /** * Returns milliseconds running in the current thread. * diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java index da27db5..f345271 100644 --- a/core/java/android/os/SystemService.java +++ b/core/java/android/os/SystemService.java @@ -16,15 +16,55 @@ package android.os; -/** @hide */ -public class SystemService -{ - /** Request that the init daemon start a named service. */ +import android.util.Slog; + +import com.google.android.collect.Maps; + +import java.util.HashMap; +import java.util.concurrent.TimeoutException; + +/** + * Controls and utilities for low-level {@code init} services. + * + * @hide + */ +public class SystemService { + + private static HashMap<String, State> sStates = Maps.newHashMap(); + + /** + * State of a known {@code init} service. + */ + public enum State { + RUNNING("running"), + STOPPING("stopping"), + STOPPED("stopped"), + RESTARTING("restarting"); + + State(String state) { + sStates.put(state, this); + } + } + + private static Object sPropertyLock = new Object(); + + static { + SystemProperties.addChangeCallback(new Runnable() { + @Override + public void run() { + synchronized (sPropertyLock) { + sPropertyLock.notifyAll(); + } + } + }); + } + + /** Request that the init daemon start a named service. */ public static void start(String name) { SystemProperties.set("ctl.start", name); } - - /** Request that the init daemon stop a named service. */ + + /** Request that the init daemon stop a named service. */ public static void stop(String name) { SystemProperties.set("ctl.stop", name); } @@ -33,4 +73,77 @@ public class SystemService public static void restart(String name) { SystemProperties.set("ctl.restart", name); } + + /** + * Return current state of given service. + */ + public static State getState(String service) { + final String rawState = SystemProperties.get("init.svc." + service); + final State state = sStates.get(rawState); + if (state != null) { + return state; + } else { + return State.STOPPED; + } + } + + /** + * Check if given service is {@link State#STOPPED}. + */ + public static boolean isStopped(String service) { + return State.STOPPED.equals(getState(service)); + } + + /** + * Check if given service is {@link State#RUNNING}. + */ + public static boolean isRunning(String service) { + return State.RUNNING.equals(getState(service)); + } + + /** + * Wait until given service has entered specific state. + */ + public static void waitForState(String service, State state, long timeoutMillis) + throws TimeoutException { + final long endMillis = SystemClock.elapsedRealtime() + timeoutMillis; + while (true) { + synchronized (sPropertyLock) { + final State currentState = getState(service); + if (state.equals(currentState)) { + return; + } + + if (SystemClock.elapsedRealtime() >= endMillis) { + throw new TimeoutException("Service " + service + " currently " + currentState + + "; waited " + timeoutMillis + "ms for " + state); + } + + try { + sPropertyLock.wait(timeoutMillis); + } catch (InterruptedException e) { + } + } + } + } + + /** + * Wait until any of given services enters {@link State#STOPPED}. + */ + public static void waitForAnyStopped(String... services) { + while (true) { + synchronized (sPropertyLock) { + for (String service : services) { + if (State.STOPPED.equals(getState(service))) { + return; + } + } + + try { + sPropertyLock.wait(); + } catch (InterruptedException e) { + } + } + } + } } diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index d2050b7..59d0f7a 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -39,11 +39,12 @@ public final class Trace { public static final long TRACE_TAG_SYNC_MANAGER = 1L << 7; public static final long TRACE_TAG_AUDIO = 1L << 8; public static final long TRACE_TAG_VIDEO = 1L << 9; + public static final long TRACE_TAG_CAMERA = 1L << 10; public static final int TRACE_FLAGS_START_BIT = 1; public static final String[] TRACE_TAGS = { "Graphics", "Input", "View", "WebView", "Window Manager", - "Activity Manager", "Sync Manager", "Audio", "Video", + "Activity Manager", "Sync Manager", "Audio", "Video", "Camera", }; public static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags"; diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java index b924e84..d33382b 100644 --- a/core/java/android/os/UEventObserver.java +++ b/core/java/android/os/UEventObserver.java @@ -37,14 +37,79 @@ import java.util.HashMap; * @hide */ public abstract class UEventObserver { - private static final String TAG = UEventObserver.class.getSimpleName(); + private static UEventThread sThread; + + private static native void native_setup(); + private static native int next_event(byte[] buffer); + + public UEventObserver() { + } + + protected void finalize() throws Throwable { + try { + stopObserving(); + } finally { + super.finalize(); + } + } + + private static UEventThread getThread() { + synchronized (UEventObserver.class) { + if (sThread == null) { + sThread = new UEventThread(); + sThread.start(); + } + return sThread; + } + } + + private static UEventThread peekThread() { + synchronized (UEventObserver.class) { + return sThread; + } + } + + /** + * Begin observation of UEvent's.<p> + * This method will cause the UEvent thread to start if this is the first + * invocation of startObserving in this process.<p> + * Once called, the UEvent thread will call onUEvent() when an incoming + * UEvent matches the specified string.<p> + * This method can be called multiple times to register multiple matches. + * Only one call to stopObserving is required even with multiple registered + * matches. + * @param match A substring of the UEvent to match. Use "" to match all + * UEvent's + */ + public final void startObserving(String match) { + final UEventThread t = getThread(); + t.addObserver(match, this); + } + + /** + * End observation of UEvent's.<p> + * This process's UEvent thread will never call onUEvent() on this + * UEventObserver after this call. Repeated calls have no effect. + */ + public final void stopObserving() { + final UEventThread t = getThread(); + if (t != null) { + t.removeObserver(this); + } + } + + /** + * Subclasses of UEventObserver should override this method to handle + * UEvents. + */ + public abstract void onUEvent(UEvent event); /** * Representation of a UEvent. */ - static public class UEvent { + public static final class UEvent { // collection of key=value pairs parsed from the uevent message - public HashMap<String,String> mMap = new HashMap<String,String>(); + private final HashMap<String,String> mMap = new HashMap<String,String>(); public UEvent(String message) { int offset = 0; @@ -79,20 +144,20 @@ public abstract class UEventObserver { } } - private static UEventThread sThread; - private static boolean sThreadStarted = false; - - private static class UEventThread extends Thread { + private static final class UEventThread extends Thread { /** Many to many mapping of string match to observer. * Multimap would be better, but not available in android, so use * an ArrayList where even elements are the String match and odd * elements the corresponding UEventObserver observer */ - private ArrayList<Object> mObservers = new ArrayList<Object>(); - - UEventThread() { + private final ArrayList<Object> mKeysAndObservers = new ArrayList<Object>(); + + private final ArrayList<UEventObserver> mTempObserversToSignal = + new ArrayList<UEventObserver>(); + + public UEventThread() { super("UEventObserver"); } - + public void run() { native_setup(); @@ -101,91 +166,54 @@ public abstract class UEventObserver { while (true) { len = next_event(buffer); if (len > 0) { - String bufferStr = new String(buffer, 0, len); // easier to search a String - synchronized (mObservers) { - for (int i = 0; i < mObservers.size(); i += 2) { - if (bufferStr.indexOf((String)mObservers.get(i)) != -1) { - ((UEventObserver)mObservers.get(i+1)) - .onUEvent(new UEvent(bufferStr)); - } - } + sendEvent(new String(buffer, 0, len)); + } + } + } + + private void sendEvent(String message) { + synchronized (mKeysAndObservers) { + final int N = mKeysAndObservers.size(); + for (int i = 0; i < N; i += 2) { + final String key = (String)mKeysAndObservers.get(i); + if (message.indexOf(key) != -1) { + final UEventObserver observer = + (UEventObserver)mKeysAndObservers.get(i + 1); + mTempObserversToSignal.add(observer); } } } + + if (!mTempObserversToSignal.isEmpty()) { + final UEvent event = new UEvent(message); + final int N = mTempObserversToSignal.size(); + for (int i = 0; i < N; i++) { + final UEventObserver observer = mTempObserversToSignal.get(i); + observer.onUEvent(event); + } + mTempObserversToSignal.clear(); + } } + public void addObserver(String match, UEventObserver observer) { - synchronized(mObservers) { - mObservers.add(match); - mObservers.add(observer); + synchronized (mKeysAndObservers) { + mKeysAndObservers.add(match); + mKeysAndObservers.add(observer); } } + /** Removes every key/value pair where value=observer from mObservers */ public void removeObserver(UEventObserver observer) { - synchronized(mObservers) { - boolean found = true; - while (found) { - found = false; - for (int i = 0; i < mObservers.size(); i += 2) { - if (mObservers.get(i+1) == observer) { - mObservers.remove(i+1); - mObservers.remove(i); - found = true; - break; - } + synchronized (mKeysAndObservers) { + for (int i = 0; i < mKeysAndObservers.size(); ) { + if (mKeysAndObservers.get(i + 1) == observer) { + mKeysAndObservers.remove(i + 1); + mKeysAndObservers.remove(i); + } else { + i += 2; } } } } } - - private static native void native_setup(); - private static native int next_event(byte[] buffer); - - private static final synchronized void ensureThreadStarted() { - if (sThreadStarted == false) { - sThread = new UEventThread(); - sThread.start(); - sThreadStarted = true; - } - } - - /** - * Begin observation of UEvent's.<p> - * This method will cause the UEvent thread to start if this is the first - * invocation of startObserving in this process.<p> - * Once called, the UEvent thread will call onUEvent() when an incoming - * UEvent matches the specified string.<p> - * This method can be called multiple times to register multiple matches. - * Only one call to stopObserving is required even with multiple registered - * matches. - * @param match A substring of the UEvent to match. Use "" to match all - * UEvent's - */ - public final synchronized void startObserving(String match) { - ensureThreadStarted(); - sThread.addObserver(match, this); - } - - /** - * End observation of UEvent's.<p> - * This process's UEvent thread will never call onUEvent() on this - * UEventObserver after this call. Repeated calls have no effect. - */ - public final synchronized void stopObserving() { - sThread.removeObserver(this); - } - - /** - * Subclasses of UEventObserver should override this method to handle - * UEvents. - */ - public abstract void onUEvent(UEvent event); - - protected void finalize() throws Throwable { - try { - stopObserving(); - } finally { - super.finalize(); - } - } } diff --git a/core/java/android/os/UserHandle.aidl b/core/java/android/os/UserHandle.aidl new file mode 100644 index 0000000..4892d32 --- /dev/null +++ b/core/java/android/os/UserHandle.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +parcelable UserHandle; diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java new file mode 100644 index 0000000..cc96152 --- /dev/null +++ b/core/java/android/os/UserHandle.java @@ -0,0 +1,260 @@ +/* + * 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.os; + +/** + * Representation of a user on the device. + */ +public final class UserHandle implements Parcelable { + /** + * @hide Range of uids allocated for a user. + */ + public static final int PER_USER_RANGE = 100000; + + /** @hide A user id to indicate all users on the device */ + public static final int USER_ALL = -1; + + /** @hide A user handle to indicate all users on the device */ + public static final UserHandle ALL = new UserHandle(USER_ALL); + + /** @hide A user id to indicate the currently active user */ + public static final int USER_CURRENT = -2; + + /** @hide A user handle to indicate the current user of the device */ + public static final UserHandle CURRENT = new UserHandle(USER_CURRENT); + + /** @hide A user id to indicate that we would like to send to the current + * user, but if this is calling from a user process then we will send it + * to the caller's user instead of failing wiht a security exception */ + public static final int USER_CURRENT_OR_SELF = -3; + + /** @hide A user handle to indicate that we would like to send to the current + * user, but if this is calling from a user process then we will send it + * to the caller's user instead of failing wiht a security exception */ + public static final UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF); + + /** @hide An undefined user id */ + public static final int USER_NULL = -10000; + + /** @hide A user id constant to indicate the "owner" user of the device */ + public static final int USER_OWNER = 0; + + /** @hide A user handle to indicate the primary/owner user of the device */ + public static final UserHandle OWNER = new UserHandle(USER_OWNER); + + /** + * @hide Enable multi-user related side effects. Set this to false if + * there are problems with single user use-cases. + */ + public static final boolean MU_ENABLED = true; + + final int mHandle; + + /** + * Checks to see if the user id is the same for the two uids, i.e., they belong to the same + * user. + * @hide + */ + public static final boolean isSameUser(int uid1, int uid2) { + return getUserId(uid1) == getUserId(uid2); + } + + /** + * Checks to see if both uids are referring to the same app id, ignoring the user id part of the + * uids. + * @param uid1 uid to compare + * @param uid2 other uid to compare + * @return whether the appId is the same for both uids + * @hide + */ + public static final boolean isSameApp(int uid1, int uid2) { + return getAppId(uid1) == getAppId(uid2); + } + + /** @hide */ + public static final boolean isIsolated(int uid) { + if (uid > 0) { + final int appId = getAppId(uid); + return appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID; + } else { + return false; + } + } + + /** @hide */ + public static boolean isApp(int uid) { + if (uid > 0) { + final int appId = getAppId(uid); + return appId >= Process.FIRST_APPLICATION_UID && appId <= Process.LAST_APPLICATION_UID; + } else { + return false; + } + } + + /** + * Returns the user id for a given uid. + * @hide + */ + public static final int getUserId(int uid) { + if (MU_ENABLED) { + return uid / PER_USER_RANGE; + } else { + return 0; + } + } + + /** @hide */ + public static final int getCallingUserId() { + return getUserId(Binder.getCallingUid()); + } + + /** + * Returns the uid that is composed from the userId and the appId. + * @hide + */ + public static final int getUid(int userId, int appId) { + if (MU_ENABLED) { + return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); + } else { + return appId; + } + } + + /** + * Returns the app id (or base uid) for a given uid, stripping out the user id from it. + * @hide + */ + public static final int getAppId(int uid) { + return uid % PER_USER_RANGE; + } + + /** + * Returns the shared app gid for a given uid or appId. + * @hide + */ + public static final int getSharedAppGid(int id) { + return Process.FIRST_SHARED_APPLICATION_GID + (id % PER_USER_RANGE) + - Process.FIRST_APPLICATION_UID; + } + + /** + * Returns the user id of the current process + * @return user id of the current process + * @hide + */ + public static final int myUserId() { + return getUserId(Process.myUid()); + } + + /** @hide */ + public UserHandle(int h) { + mHandle = h; + } + + /** @hide */ + public int getIdentifier() { + return mHandle; + } + + @Override + public String toString() { + return "UserHandle{" + mHandle + "}"; + } + + @Override + public boolean equals(Object obj) { + try { + if (obj != null) { + UserHandle other = (UserHandle)obj; + return mHandle == other.mHandle; + } + } catch (ClassCastException e) { + } + return false; + } + + @Override + public int hashCode() { + return mHandle; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mHandle); + } + + /** + * Write a UserHandle to a Parcel, handling null pointers. Must be + * read with {@link #readFromParcel(Parcel)}. + * + * @param h The UserHandle to be written. + * @param out The Parcel in which the UserHandle will be placed. + * + * @see #readFromParcel(Parcel) + */ + public static void writeToParcel(UserHandle h, Parcel out) { + if (h != null) { + h.writeToParcel(out, 0); + } else { + out.writeInt(USER_NULL); + } + } + + /** + * Read a UserHandle from a Parcel that was previously written + * with {@link #writeToParcel(UserHandle, Parcel)}, returning either + * a null or new object as appropriate. + * + * @param in The Parcel from which to read the UserHandle + * @return Returns a new UserHandle matching the previously written + * object, or null if a null had been written. + * + * @see #writeToParcel(UserHandle, Parcel) + */ + public static UserHandle readFromParcel(Parcel in) { + int h = in.readInt(); + return h != USER_NULL ? new UserHandle(h) : null; + } + + public static final Parcelable.Creator<UserHandle> CREATOR + = new Parcelable.Creator<UserHandle>() { + public UserHandle createFromParcel(Parcel in) { + return new UserHandle(in); + } + + public UserHandle[] newArray(int size) { + return new UserHandle[size]; + } + }; + + /** + * Instantiate a new UserHandle from the data in a Parcel that was + * previously written with {@link #writeToParcel(Parcel, int)}. Note that you + * must not use this with data written by + * {@link #writeToParcel(UserHandle, Parcel)} since it is not possible + * to handle a null UserHandle here. + * + * @param in The Parcel containing the previously written UserHandle, + * positioned at the location in the buffer where it was written. + */ + public UserHandle(Parcel in) { + mHandle = in.readInt(); + } +} diff --git a/core/java/android/os/UserId.java b/core/java/android/os/UserId.java deleted file mode 100644 index 8bf6c6e..0000000 --- a/core/java/android/os/UserId.java +++ /dev/null @@ -1,116 +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.os; - -/** - * @hide - */ -public final class UserId { - /** - * Range of IDs allocated for a user. - * - * @hide - */ - public static final int PER_USER_RANGE = 100000; - - public static final int USER_ALL = -1; - - /** - * Enable multi-user related side effects. Set this to false if there are problems with single - * user usecases. - * */ - public static final boolean MU_ENABLED = true; - - /** - * Checks to see if the user id is the same for the two uids, i.e., they belong to the same - * user. - * @hide - */ - public static final boolean isSameUser(int uid1, int uid2) { - return getUserId(uid1) == getUserId(uid2); - } - - /** - * Checks to see if both uids are referring to the same app id, ignoring the user id part of the - * uids. - * @param uid1 uid to compare - * @param uid2 other uid to compare - * @return whether the appId is the same for both uids - * @hide - */ - public static final boolean isSameApp(int uid1, int uid2) { - return getAppId(uid1) == getAppId(uid2); - } - - public static final boolean isIsolated(int uid) { - uid = getAppId(uid); - return uid >= Process.FIRST_ISOLATED_UID && uid <= Process.LAST_ISOLATED_UID; - } - - public static boolean isApp(int uid) { - if (uid > 0) { - uid = UserId.getAppId(uid); - return uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID; - } else { - return false; - } - } - - /** - * Returns the user id for a given uid. - * @hide - */ - public static final int getUserId(int uid) { - if (MU_ENABLED) { - return uid / PER_USER_RANGE; - } else { - return 0; - } - } - - public static final int getCallingUserId() { - return getUserId(Binder.getCallingUid()); - } - - /** - * Returns the uid that is composed from the userId and the appId. - * @hide - */ - public static final int getUid(int userId, int appId) { - if (MU_ENABLED) { - return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); - } else { - return appId; - } - } - - /** - * Returns the app id (or base uid) for a given uid, stripping out the user id from it. - * @hide - */ - public static final int getAppId(int uid) { - return uid % PER_USER_RANGE; - } - - /** - * Returns the user id of the current process - * @return user id of the current process - */ - public static final int myUserId() { - return getUserId(Process.myUid()); - } -} diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java new file mode 100644 index 0000000..96c96d7 --- /dev/null +++ b/core/java/android/os/UserManager.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os; + +import com.android.internal.R; +import android.content.Context; +import android.content.pm.UserInfo; +import android.graphics.Bitmap; +import android.content.res.Resources; +import android.util.Log; + +import java.util.List; + +/** + * Manages users and user details on a multi-user system. + */ +public class UserManager { + + private static String TAG = "UserManager"; + private final IUserManager mService; + private final Context mContext; + + /** @hide */ + public UserManager(Context context, IUserManager service) { + mService = service; + mContext = context; + } + + /** + * Returns whether the system supports multiple users. + * @return true if multiple users can be created, false if it is a single user device. + * @hide + */ + public static boolean supportsMultipleUsers() { + return getMaxSupportedUsers() > 1; + } + + /** + * Returns the user handle for the user that this application is running for. + * @return the user handle of the user making this call. + * @hide + * */ + public int getUserHandle() { + return UserHandle.myUserId(); + } + + /** + * Returns the user name of the user making this call. This call is only + * available to applications on the system image; it requires the + * MANAGE_USERS permission. + * @return the user name + */ + public String getUserName() { + try { + return mService.getUserInfo(getUserHandle()).name; + } catch (RemoteException re) { + Log.w(TAG, "Could not get user name", re); + return ""; + } + } + + /** + * Used to determine whether the user making this call is subject to + * teleportations. + * @return whether the user making this call is a goat + */ + public boolean isUserAGoat() { + return false; + } + + /** + * Returns the UserInfo object describing a specific user. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * @param userHandle the user handle of the user whose information is being requested. + * @return the UserInfo object for a specific user. + * @hide + * */ + public UserInfo getUserInfo(int userHandle) { + try { + return mService.getUserInfo(userHandle); + } catch (RemoteException re) { + Log.w(TAG, "Could not get user info", re); + return null; + } + } + + /** + * Creates a user with the specified name and options. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * + * @param name the user's name + * @param flags flags that identify the type of user and other properties. + * @see UserInfo + * + * @return the UserInfo object for the created user, or null if the user could not be created. + * @hide + */ + public UserInfo createUser(String name, int flags) { + try { + return mService.createUser(name, flags); + } catch (RemoteException re) { + Log.w(TAG, "Could not create a user", re); + return null; + } + } + + /** + * Returns information for all users on this device. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * @return the list of users that were created. + * @hide + */ + public List<UserInfo> getUsers() { + try { + return mService.getUsers(false); + } catch (RemoteException re) { + Log.w(TAG, "Could not get user list", re); + return null; + } + } + + /** + * Returns information for all users on this device. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * @param excludeDying specify if the list should exclude users being removed. + * @return the list of users that were created. + * @hide + */ + public List<UserInfo> getUsers(boolean excludeDying) { + try { + return mService.getUsers(excludeDying); + } catch (RemoteException re) { + Log.w(TAG, "Could not get user list", re); + return null; + } + } + + /** + * Removes a user and all associated data. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * @param userHandle the integer handle of the user, where 0 is the primary user. + * @hide + */ + public boolean removeUser(int userHandle) { + try { + return mService.removeUser(userHandle); + } catch (RemoteException re) { + Log.w(TAG, "Could not remove user ", re); + return false; + } + } + + /** + * Updates the user's name. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * + * @param userHandle the user's integer handle + * @param name the new name for the user + * @hide + */ + public void setUserName(int userHandle, String name) { + try { + mService.setUserName(userHandle, name); + } catch (RemoteException re) { + Log.w(TAG, "Could not set the user name ", re); + } + } + + /** + * Sets the user's photo. + * @param userHandle the user for whom to change the photo. + * @param icon the bitmap to set as the photo. + * @hide + */ + public void setUserIcon(int userHandle, Bitmap icon) { + try { + mService.setUserIcon(userHandle, icon); + } catch (RemoteException re) { + Log.w(TAG, "Could not set the user icon ", re); + } + } + + /** + * Returns a file descriptor for the user's photo. PNG data can be read from this file. + * @param userHandle the user whose photo we want to read. + * @return a {@link Bitmap} of the user's photo, or null if there's no photo. + * @hide + */ + public Bitmap getUserIcon(int userHandle) { + try { + return mService.getUserIcon(userHandle); + } catch (RemoteException re) { + Log.w(TAG, "Could not get the user icon ", re); + return null; + } + } + + /** + * Enable or disable the use of a guest account. If disabled, the existing guest account + * will be wiped. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * @param enable whether to enable a guest account. + * @hide + */ + public void setGuestEnabled(boolean enable) { + try { + mService.setGuestEnabled(enable); + } catch (RemoteException re) { + Log.w(TAG, "Could not change guest account availability to " + enable); + } + } + + /** + * Checks if a guest user is enabled for this device. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * @return whether a guest user is enabled + * @hide + */ + public boolean isGuestEnabled() { + try { + return mService.isGuestEnabled(); + } catch (RemoteException re) { + Log.w(TAG, "Could not retrieve guest enabled state"); + return false; + } + } + + /** + * Wipes all the data for a user, but doesn't remove the user. + * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. + * @param userHandle + * @hide + */ + public void wipeUser(int userHandle) { + try { + mService.wipeUser(userHandle); + } catch (RemoteException re) { + Log.w(TAG, "Could not wipe user " + userHandle); + } + } + + /** + * Returns the maximum number of users that can be created on this device. A return value + * of 1 means that it is a single user device. + * @hide + * @return a value greater than or equal to 1 + */ + public static int getMaxSupportedUsers() { + return SystemProperties.getInt("fw.max_users", + Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers)); + } + + /** + * Returns a serial number on this device for a given userHandle. User handles can be recycled + * when deleting and creating users, but serial numbers are not reused until the device is wiped. + * @param userHandle + * @return a serial number associated with that user, or -1 if the userHandle is not valid. + * @hide + */ + public int getUserSerialNumber(int userHandle) { + try { + return mService.getUserSerialNumber(userHandle); + } catch (RemoteException re) { + Log.w(TAG, "Could not get serial number for user " + userHandle); + } + return -1; + } + + /** + * Returns a userHandle on this device for a given user serial number. User handles can be + * recycled when deleting and creating users, but serial numbers are not reused until the device + * is wiped. + * @param userSerialNumber + * @return the userHandle associated with that user serial number, or -1 if the serial number + * is not valid. + * @hide + */ + public int getUserHandle(int userSerialNumber) { + try { + return mService.getUserHandle(userSerialNumber); + } catch (RemoteException re) { + Log.w(TAG, "Could not get userHandle for user " + userSerialNumber); + } + return -1; + } +} diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index 287c136..ba77df7 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -1,5 +1,9 @@ package android.os; +import com.android.internal.util.ArrayUtils; + +import java.util.Arrays; + /** * Describes the source of some work that may be done by someone else. * Currently the public representation of what a work source is is not @@ -76,6 +80,20 @@ public class WorkSource implements Parcelable { mNum = 0; } + @Override + public boolean equals(Object o) { + return o instanceof WorkSource && !diff((WorkSource)o); + } + + @Override + public int hashCode() { + int result = 0; + for (int i = 0; i < mNum; i++) { + result = ((result << 4) | (result >>> 28)) ^ mUids[i]; + } + return result; + } + /** * Compare this WorkSource with another. * @param other The WorkSource to compare against. @@ -299,6 +317,20 @@ public class WorkSource implements Parcelable { dest.writeIntArray(mUids); } + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("{WorkSource: uids=["); + for (int i = 0; i < mNum; i++) { + if (i != 0) { + result.append(", "); + } + result.append(mUids[i]); + } + result.append("]}"); + return result.toString(); + } + public static final Parcelable.Creator<WorkSource> CREATOR = new Parcelable.Creator<WorkSource>() { public WorkSource createFromParcel(Parcel in) { diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index ab64866..fc18617 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -489,13 +489,14 @@ public interface IMountService extends IInterface { * IObbActionListener to inform it of the terminal state of the * call. */ - public void mountObb(String filename, String key, IObbActionListener token, int nonce) - throws RemoteException { + public void mountObb(String rawPath, String canonicalPath, String key, + IObbActionListener token, int nonce) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(filename); + _data.writeString(rawPath); + _data.writeString(canonicalPath); _data.writeString(key); _data.writeStrongBinder((token != null ? token.asBinder() : null)); _data.writeInt(nonce); @@ -514,13 +515,14 @@ public interface IMountService extends IInterface { * IObbActionListener to inform it of the terminal state of the * call. */ - public void unmountObb(String filename, boolean force, IObbActionListener token, - int nonce) throws RemoteException { + public void unmountObb( + String rawPath, boolean force, IObbActionListener token, int nonce) + throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(filename); + _data.writeString(rawPath); _data.writeInt((force ? 1 : 0)); _data.writeStrongBinder((token != null ? token.asBinder() : null)); _data.writeInt(nonce); @@ -536,13 +538,13 @@ public interface IMountService extends IInterface { * Checks whether the specified Opaque Binary Blob (OBB) is mounted * somewhere. */ - public boolean isObbMounted(String filename) throws RemoteException { + public boolean isObbMounted(String rawPath) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); boolean _result; try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(filename); + _data.writeString(rawPath); mRemote.transact(Stub.TRANSACTION_isObbMounted, _data, _reply, 0); _reply.readException(); _result = 0 != _reply.readInt(); @@ -556,13 +558,13 @@ public interface IMountService extends IInterface { /** * Gets the path to the mounted Opaque Binary Blob (OBB). */ - public String getMountedObbPath(String filename) throws RemoteException { + public String getMountedObbPath(String rawPath) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); String _result; try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(filename); + _data.writeString(rawPath); mRemote.transact(Stub.TRANSACTION_getMountedObbPath, _data, _reply, 0); _reply.readException(); _result = _reply.readString(); @@ -677,15 +679,15 @@ public interface IMountService extends IInterface { return _result; } - public Parcelable[] getVolumeList() throws RemoteException { + public StorageVolume[] getVolumeList() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); - Parcelable[] _result; + StorageVolume[] _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0); _reply.readException(); - _result = _reply.readParcelableArray(StorageVolume.class.getClassLoader()); + _result = _reply.createTypedArray(StorageVolume.CREATOR); } finally { _reply.recycle(); _data.recycle(); @@ -1042,15 +1044,14 @@ public interface IMountService extends IInterface { } case TRANSACTION_mountObb: { data.enforceInterface(DESCRIPTOR); - String filename; - filename = data.readString(); - String key; - key = data.readString(); + final String rawPath = data.readString(); + final String canonicalPath = data.readString(); + final String key = data.readString(); IObbActionListener observer; observer = IObbActionListener.Stub.asInterface(data.readStrongBinder()); int nonce; nonce = data.readInt(); - mountObb(filename, key, observer, nonce); + mountObb(rawPath, canonicalPath, key, observer, nonce); reply.writeNoException(); return true; } @@ -1119,9 +1120,9 @@ public interface IMountService extends IInterface { } case TRANSACTION_getVolumeList: { data.enforceInterface(DESCRIPTOR); - Parcelable[] result = getVolumeList(); + StorageVolume[] result = getVolumeList(); reply.writeNoException(); - reply.writeParcelableArray(result, 0); + reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; } case TRANSACTION_getSecureContainerFilesystemPath: { @@ -1194,7 +1195,7 @@ public interface IMountService extends IInterface { /** * Gets the path to the mounted Opaque Binary Blob (OBB). */ - public String getMountedObbPath(String filename) throws RemoteException; + public String getMountedObbPath(String rawPath) throws RemoteException; /** * Gets an Array of currently known secure container IDs @@ -1220,7 +1221,7 @@ public interface IMountService extends IInterface { * Checks whether the specified Opaque Binary Blob (OBB) is mounted * somewhere. */ - public boolean isObbMounted(String filename) throws RemoteException; + public boolean isObbMounted(String rawPath) throws RemoteException; /* * Returns true if the specified container is mounted @@ -1243,8 +1244,8 @@ public interface IMountService extends IInterface { * MountService will call back to the supplied IObbActionListener to inform * it of the terminal state of the call. */ - public void mountObb(String filename, String key, IObbActionListener token, int nonce) - throws RemoteException; + public void mountObb(String rawPath, String canonicalPath, String key, + IObbActionListener token, int nonce) throws RemoteException; /* * Mount a secure container with the specified key and owner UID. Returns an @@ -1287,7 +1288,7 @@ public interface IMountService extends IInterface { * MountService will call back to the supplied IObbActionListener to inform * it of the terminal state of the call. */ - public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce) + public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) throws RemoteException; /* @@ -1358,7 +1359,7 @@ public interface IMountService extends IInterface { /** * Returns list of all mountable volumes. */ - public Parcelable[] getVolumeList() throws RemoteException; + public StorageVolume[] getVolumeList() throws RemoteException; /** * Gets the path on the filesystem for the ASEC container itself. diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 8a20a6e..862a95c 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -16,6 +16,8 @@ package android.os.storage; +import android.app.NotificationManager; +import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Looper; @@ -26,6 +28,10 @@ import android.os.ServiceManager; import android.util.Log; import android.util.SparseArray; +import com.android.internal.util.Preconditions; + +import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -285,6 +291,11 @@ public class StorageManager } } + /** {@hide} */ + public static StorageManager from(Context context) { + return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + } + /** * Constructs a StorageManager object through which an application can * can communicate with the systems mount service. @@ -436,25 +447,23 @@ public class StorageManager * That is, shared UID applications can attempt to mount any other * application's OBB that shares its UID. * - * @param filename the path to the OBB file + * @param rawPath the path to the OBB file * @param key secret used to encrypt the OBB; may be <code>null</code> if no * encryption was used on the OBB. * @param listener will receive the success or failure of the operation * @return whether the mount call was successfully queued or not */ - public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) { - if (filename == null) { - throw new IllegalArgumentException("filename cannot be null"); - } - - if (listener == null) { - throw new IllegalArgumentException("listener cannot be null"); - } + public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) { + Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); + Preconditions.checkNotNull(listener, "listener cannot be null"); try { + final String canonicalPath = new File(rawPath).getCanonicalPath(); final int nonce = mObbActionListener.addListener(listener); - mMountService.mountObb(filename, key, mObbActionListener, nonce); + mMountService.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce); return true; + } catch (IOException e) { + throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e); } catch (RemoteException e) { Log.e(TAG, "Failed to mount OBB", e); } @@ -476,24 +485,19 @@ public class StorageManager * application's OBB that shares its UID. * <p> * - * @param filename path to the OBB file + * @param rawPath path to the OBB file * @param force whether to kill any programs using this in order to unmount * it * @param listener will receive the success or failure of the operation * @return whether the unmount call was successfully queued or not */ - public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) { - if (filename == null) { - throw new IllegalArgumentException("filename cannot be null"); - } - - if (listener == null) { - throw new IllegalArgumentException("listener cannot be null"); - } + public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) { + Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); + Preconditions.checkNotNull(listener, "listener cannot be null"); try { final int nonce = mObbActionListener.addListener(listener); - mMountService.unmountObb(filename, force, mObbActionListener, nonce); + mMountService.unmountObb(rawPath, force, mObbActionListener, nonce); return true; } catch (RemoteException e) { Log.e(TAG, "Failed to mount OBB", e); @@ -505,16 +509,14 @@ public class StorageManager /** * Check whether an Opaque Binary Blob (OBB) is mounted or not. * - * @param filename path to OBB image + * @param rawPath path to OBB image * @return true if OBB is mounted; false if not mounted or on error */ - public boolean isObbMounted(String filename) { - if (filename == null) { - throw new IllegalArgumentException("filename cannot be null"); - } + public boolean isObbMounted(String rawPath) { + Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); try { - return mMountService.isObbMounted(filename); + return mMountService.isObbMounted(rawPath); } catch (RemoteException e) { Log.e(TAG, "Failed to check if OBB is mounted", e); } @@ -527,17 +529,15 @@ public class StorageManager * give you the path to where you can obtain access to the internals of the * OBB. * - * @param filename path to OBB image + * @param rawPath path to OBB image * @return absolute path to mounted OBB image data or <code>null</code> if * not mounted or exception encountered trying to read status */ - public String getMountedObbPath(String filename) { - if (filename == null) { - throw new IllegalArgumentException("filename cannot be null"); - } + public String getMountedObbPath(String rawPath) { + Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); try { - return mMountService.getMountedObbPath(filename); + return mMountService.getMountedObbPath(rawPath); } catch (RemoteException e) { Log.e(TAG, "Failed to find mounted path for OBB", e); } @@ -594,4 +594,20 @@ public class StorageManager } return paths; } + + /** {@hide} */ + public StorageVolume getPrimaryVolume() { + return getPrimaryVolume(getVolumeList()); + } + + /** {@hide} */ + public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) { + for (StorageVolume volume : volumes) { + if (volume.isPrimary()) { + return volume; + } + } + Log.w(TAG, "No primary storage defined"); + return null; + } } diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 79c8f3b..177a955 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -19,53 +19,69 @@ package android.os.storage; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; + +import java.io.File; /** - * A class representing a storage volume + * Description of a storage volume and its capabilities, including the + * filesystem path where it may be mounted. + * * @hide */ public class StorageVolume implements Parcelable { - //private static final String TAG = "StorageVolume"; + // TODO: switch to more durable token + private int mStorageId; - private final String mPath; + private final File mPath; private final int mDescriptionId; + private final boolean mPrimary; private final boolean mRemovable; private final boolean mEmulated; private final int mMtpReserveSpace; private final boolean mAllowMassStorage; - private int mStorageId; - // maximum file size for the storage, or zero for no limit + /** Maximum file size for the storage, or zero for no limit */ private final long mMaxFileSize; + /** When set, indicates exclusive ownership of this volume */ + private final UserHandle mOwner; // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING, // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED, // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts. public static final String EXTRA_STORAGE_VOLUME = "storage_volume"; - public StorageVolume(String path, int descriptionId, boolean removable, - boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize) { + public StorageVolume(File path, int descriptionId, boolean primary, boolean removable, + boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize, + UserHandle owner) { mPath = path; mDescriptionId = descriptionId; + mPrimary = primary; mRemovable = removable; mEmulated = emulated; mMtpReserveSpace = mtpReserveSpace; mAllowMassStorage = allowMassStorage; mMaxFileSize = maxFileSize; + mOwner = owner; } - // for parcelling only - private StorageVolume(String path, int descriptionId, boolean removable, - boolean emulated, int mtpReserveSpace, int storageId, - boolean allowMassStorage, long maxFileSize) { - mPath = path; - mDescriptionId = descriptionId; - mRemovable = removable; - mEmulated = emulated; - mMtpReserveSpace = mtpReserveSpace; - mAllowMassStorage = allowMassStorage; - mStorageId = storageId; - mMaxFileSize = maxFileSize; + private StorageVolume(Parcel in) { + mStorageId = in.readInt(); + mPath = new File(in.readString()); + mDescriptionId = in.readInt(); + mPrimary = in.readInt() != 0; + mRemovable = in.readInt() != 0; + mEmulated = in.readInt() != 0; + mMtpReserveSpace = in.readInt(); + mAllowMassStorage = in.readInt() != 0; + mMaxFileSize = in.readLong(); + mOwner = in.readParcelable(null); + } + + public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) { + return new StorageVolume(path, template.mDescriptionId, template.mPrimary, + template.mRemovable, template.mEmulated, template.mMtpReserveSpace, + template.mAllowMassStorage, template.mMaxFileSize, owner); } /** @@ -74,6 +90,10 @@ public class StorageVolume implements Parcelable { * @return the mount path */ public String getPath() { + return mPath.toString(); + } + + public File getPathFile() { return mPath; } @@ -90,6 +110,10 @@ public class StorageVolume implements Parcelable { return mDescriptionId; } + public boolean isPrimary() { + return mPrimary; + } + /** * Returns true if the volume is removable. * @@ -161,6 +185,10 @@ public class StorageVolume implements Parcelable { return mMaxFileSize; } + public UserHandle getOwner() { + return mOwner; + } + @Override public boolean equals(Object obj) { if (obj instanceof StorageVolume && mPath != null) { @@ -177,45 +205,49 @@ public class StorageVolume implements Parcelable { @Override public String toString() { - return "StorageVolume [mAllowMassStorage=" + mAllowMassStorage + ", mDescriptionId=" - + mDescriptionId + ", mEmulated=" + mEmulated + ", mMaxFileSize=" + mMaxFileSize - + ", mMtpReserveSpace=" + mMtpReserveSpace + ", mPath=" + mPath + ", mRemovable=" - + mRemovable + ", mStorageId=" + mStorageId + "]"; - } - - public static final Parcelable.Creator<StorageVolume> CREATOR = - new Parcelable.Creator<StorageVolume>() { + final StringBuilder builder = new StringBuilder("StorageVolume ["); + builder.append("mStorageId=").append(mStorageId); + builder.append(" mPath=").append(mPath); + builder.append(" mDescriptionId=").append(mDescriptionId); + builder.append(" mPrimary=").append(mPrimary); + builder.append(" mRemovable=").append(mRemovable); + builder.append(" mEmulated=").append(mEmulated); + builder.append(" mMtpReserveSpace=").append(mMtpReserveSpace); + builder.append(" mAllowMassStorage=").append(mAllowMassStorage); + builder.append(" mMaxFileSize=").append(mMaxFileSize); + builder.append(" mOwner=").append(mOwner); + builder.append("]"); + return builder.toString(); + } + + public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() { + @Override public StorageVolume createFromParcel(Parcel in) { - String path = in.readString(); - int descriptionId = in.readInt(); - int removable = in.readInt(); - int emulated = in.readInt(); - int storageId = in.readInt(); - int mtpReserveSpace = in.readInt(); - int allowMassStorage = in.readInt(); - long maxFileSize = in.readLong(); - return new StorageVolume(path, descriptionId, - removable == 1, emulated == 1, mtpReserveSpace, - storageId, allowMassStorage == 1, maxFileSize); + return new StorageVolume(in); } + @Override public StorageVolume[] newArray(int size) { return new StorageVolume[size]; } }; + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(mPath); + parcel.writeInt(mStorageId); + parcel.writeString(mPath.toString()); parcel.writeInt(mDescriptionId); + parcel.writeInt(mPrimary ? 1 : 0); parcel.writeInt(mRemovable ? 1 : 0); parcel.writeInt(mEmulated ? 1 : 0); - parcel.writeInt(mStorageId); parcel.writeInt(mMtpReserveSpace); parcel.writeInt(mAllowMassStorage ? 1 : 0); parcel.writeLong(mMaxFileSize); + parcel.writeParcelable(mOwner, flags); } } diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java index d186b20..c649879 100644 --- a/core/java/android/preference/TwoStatePreference.java +++ b/core/java/android/preference/TwoStatePreference.java @@ -37,6 +37,7 @@ public abstract class TwoStatePreference extends Preference { private CharSequence mSummaryOn; private CharSequence mSummaryOff; boolean mChecked; + private boolean mCheckedSet; private boolean mSendClickAccessibilityEvent; private boolean mDisableDependentsState; @@ -74,11 +75,16 @@ public abstract class TwoStatePreference extends Preference { * @param checked The checked state. */ public void setChecked(boolean checked) { - if (mChecked != checked) { + // Always persist/notify the first time; don't assume the field's default of false. + final boolean changed = mChecked != checked; + if (changed || !mCheckedSet) { mChecked = checked; + mCheckedSet = true; persistBoolean(checked); - notifyDependencyChange(shouldDisableDependents()); - notifyChanged(); + if (changed) { + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } } } diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index a28585c..af6e88e9 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -467,6 +467,13 @@ public final class CalendarContract { * */ public static final String ALLOWED_ATTENDEE_TYPES = "allowedAttendeeTypes"; + + /** + * Is this the primary calendar for this account. If this column is not explicitly set, the + * provider will return 1 if {@link Calendars#ACCOUNT_NAME} is equal to + * {@link Calendars#OWNER_ACCOUNT}. + */ + public static final String IS_PRIMARY = "isPrimary"; } /** @@ -1206,6 +1213,14 @@ public final class CalendarContract { public static final String ORGANIZER = "organizer"; /** + * Are we the organizer of this event. If this column is not explicitly set, the provider + * will return 1 if {@link #ORGANIZER} is equal to {@link Calendars#OWNER_ACCOUNT}. + * Column name. + * <P>Type: STRING</P> + */ + public static final String IS_ORGANIZER = "isOrganizer"; + + /** * Whether the user can invite others to the event. The * GUESTS_CAN_INVITE_OTHERS is a setting that applies to an arbitrary * guest, while CAN_INVITE_OTHERS indicates if the user can invite @@ -1230,6 +1245,12 @@ public final class CalendarContract { */ public static final String CUSTOM_APP_URI = "customAppUri"; + /** + * The UID for events added from the RFC 2445 iCalendar format. + * Column name. + * <P>Type: TEXT</P> + */ + public static final String UID_2445 = "uid2445"; } /** @@ -1367,7 +1388,9 @@ public final class CalendarContract { DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_SEE_GUESTS); DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CUSTOM_APP_PACKAGE); DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CUSTOM_APP_URI); + DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, UID_2445); DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER); + DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, IS_ORGANIZER); DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID); DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY); DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_SYNCED); @@ -1571,6 +1594,7 @@ public final class CalendarContract { * <li>{@link #GUESTS_CAN_SEE_GUESTS}</li> * <li>{@link #CUSTOM_APP_PACKAGE}</li> * <li>{@link #CUSTOM_APP_URI}</li> + * <li>{@link #UID_2445}</li> * </ul> * The following Events columns are writable only by a sync adapter * <ul> diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 22b68bc..5dca67f 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -17,9 +17,6 @@ package android.provider; -import com.android.internal.telephony.CallerInfo; -import com.android.internal.telephony.PhoneConstants; - import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -30,6 +27,9 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.DataUsageFeedback; import android.text.TextUtils; +import com.android.internal.telephony.CallerInfo; +import com.android.internal.telephony.PhoneConstants; + /** * The CallLog provider contains information about placed and received calls. */ @@ -59,6 +59,20 @@ public class CallLog { Uri.parse("content://call_log/calls/filter"); /** + * Query parameter used to limit the number of call logs returned. + * <p> + * TYPE: integer + */ + public static final String LIMIT_PARAM_KEY = "limit"; + + /** + * Query parameter used to specify the starting record to return. + * <p> + * TYPE: integer + */ + public static final String OFFSET_PARAM_KEY = "offset"; + + /** * An optional URI parameter which instructs the provider to allow the operation to be * applied to voicemail records as well. * <p> diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index e7b0579..5b49ba3 100755 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -37,6 +37,7 @@ import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; +import android.os.UserHandle; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Pair; @@ -345,10 +346,10 @@ public final class ContactsContract { * directory provider URIs by themselves. This level of indirection allows * Contacts Provider to implement additional system-level features and * optimizations. Access to Contacts Provider is protected by the - * READ_CONTACTS permission, but access to the directory provider is not. - * Therefore directory providers must reject requests coming from clients - * other than the Contacts Provider itself. An easy way to prevent such - * unauthorized access is to check the name of the calling package: + * READ_CONTACTS permission, but access to the directory provider is protected by + * BIND_DIRECTORY_SEARCH. This permission was introduced at the API level 17, for previous + * platform versions the provider should perform the following check to make sure the call + * is coming from the ContactsProvider: * <pre> * private boolean isCallerAllowed() { * PackageManager pm = getContext().getPackageManager(); @@ -7658,6 +7659,54 @@ public final class ContactsContract { public static final int MODE_LARGE = 3; /** + * Constructs the QuickContacts intent with a view's rect. + * @hide + */ + public static Intent composeQuickContactsIntent(Context context, View target, Uri lookupUri, + int mode, String[] excludeMimes) { + // Find location and bounds of target view, adjusting based on the + // assumed local density. + final float appScale = context.getResources().getCompatibilityInfo().applicationScale; + final int[] pos = new int[2]; + target.getLocationOnScreen(pos); + + final Rect rect = new Rect(); + rect.left = (int) (pos[0] * appScale + 0.5f); + rect.top = (int) (pos[1] * appScale + 0.5f); + rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f); + rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f); + + return composeQuickContactsIntent(context, rect, lookupUri, mode, excludeMimes); + } + + /** + * Constructs the QuickContacts intent. + * @hide + */ + public static Intent composeQuickContactsIntent(Context context, Rect target, + Uri lookupUri, int mode, String[] excludeMimes) { + // When launching from an Activiy, we don't want to start a new task, but otherwise + // we *must* start a new task. (Otherwise startActivity() would crash.) + Context actualContext = context; + while ((actualContext instanceof ContextWrapper) + && !(actualContext instanceof Activity)) { + actualContext = ((ContextWrapper) actualContext).getBaseContext(); + } + final int intentFlags = (actualContext instanceof Activity) + ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET + : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK; + + // Launch pivot dialog through intent for now + final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags); + + intent.setData(lookupUri); + intent.setSourceBounds(target); + intent.putExtra(EXTRA_MODE, mode); + intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes); + return intent; + } + + /** * Trigger a dialog that lists the various methods of interacting with * the requested {@link Contacts} entry. This may be based on available * {@link ContactsContract.Data} rows under that contact, and may also @@ -7682,20 +7731,10 @@ public final class ContactsContract { */ public static void showQuickContact(Context context, View target, Uri lookupUri, int mode, String[] excludeMimes) { - // Find location and bounds of target view, adjusting based on the - // assumed local density. - final float appScale = context.getResources().getCompatibilityInfo().applicationScale; - final int[] pos = new int[2]; - target.getLocationOnScreen(pos); - - final Rect rect = new Rect(); - rect.left = (int) (pos[0] * appScale + 0.5f); - rect.top = (int) (pos[1] * appScale + 0.5f); - rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f); - rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f); - // Trigger with obtained rectangle - showQuickContact(context, rect, lookupUri, mode, excludeMimes); + Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode, + excludeMimes); + context.startActivity(intent); } /** @@ -7726,24 +7765,8 @@ public final class ContactsContract { */ public static void showQuickContact(Context context, Rect target, Uri lookupUri, int mode, String[] excludeMimes) { - // When launching from an Activiy, we don't want to start a new task, but otherwise - // we *must* start a new task. (Otherwise startActivity() would crash.) - Context actualContext = context; - while ((actualContext instanceof ContextWrapper) - && !(actualContext instanceof Activity)) { - actualContext = ((ContextWrapper) actualContext).getBaseContext(); - } - final int intentFlags = (actualContext instanceof Activity) - ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET - : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK; - - // Launch pivot dialog through intent for now - final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags); - - intent.setData(lookupUri); - intent.setSourceBounds(target); - intent.putExtra(EXTRA_MODE, mode); - intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes); + Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode, + excludeMimes); context.startActivity(intent); } } @@ -7900,6 +7923,16 @@ public final class ContactsContract { "com.android.contacts.action.GET_MULTIPLE_PHONES"; /** + * A broadcast action which is sent when any change has been made to the profile, such + * as the profile name or the picture. A receiver must have + * the android.permission.READ_PROFILE permission. + * + * @hide + */ + public static final String ACTION_PROFILE_CHANGED = + "android.provider.Contacts.PROFILE_CHANGED"; + + /** * Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new * contact if no matching contact found. Otherwise, default behavior is * to prompt user with dialog before creating. diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 79d0144..0e7ab52 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -120,7 +120,39 @@ public final class MediaStore { */ public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH = "android.media.action.MEDIA_PLAY_FROM_SEARCH"; - + + /** + * An intent to perform a search for readable media and automatically play content from the + * result when possible. This can be fired, for example, by the result of a voice recognition + * command to read a book or magazine. + * <p> + * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can + * contain any type of unstructured text search, like the name of a book or magazine, an author + * a genre, a publisher, or any combination of these. + * <p> + * Because this intent includes an open-ended unstructured search string, it makes the most + * sense for apps that can support large-scale search of text media, such as services connected + * to an online database of books and/or magazines which can be read on the device. + */ + public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH = + "android.media.action.TEXT_OPEN_FROM_SEARCH"; + + /** + * An intent to perform a search for video media and automatically play content from the + * result when possible. This can be fired, for example, by the result of a voice recognition + * command to play movies. + * <p> + * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can + * contain any type of unstructured video search, like the name of a movie, one or more actors, + * a genre, or any combination of these. + * <p> + * Because this intent includes an open-ended unstructured search string, it makes the most + * sense for apps that can support large-scale search of video, such as services connected to an + * online database of videos which can be streamed and played on the device. + */ + public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH = + "android.media.action.VIDEO_PLAY_FROM_SEARCH"; + /** * The name of the Intent-extra used to define the artist */ @@ -173,6 +205,21 @@ public final class MediaStore { public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA"; /** + * The name of the Intent action used to launch a camera in still image mode + * for use when the device is secured (e.g. with a pin, password, pattern, + * or face unlock). Applications responding to this intent must not expose + * any personal content like existing photos or videos on the device. The + * applications should be careful not to share any photo or video with other + * applications or internet. The activity should use {@link + * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display + * on top of the lock screen while secured. There is no activity stack when + * this flag is used, so launching more than one activity is strongly + * discouraged. + */ + public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = + "android.media.action.STILL_IMAGE_CAMERA_SECURE"; + + /** * The name of the Intent action used to launch a camera in video mode. */ public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA"; @@ -191,6 +238,28 @@ public final class MediaStore { public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; /** + * Intent action that can be sent to have the camera application capture an image and return + * it when the device is secured (e.g. with a pin, password, pattern, or face unlock). + * Applications responding to this intent must not expose any personal content like existing + * photos or videos on the device. The applications should be careful not to share any photo + * or video with other applications or internet. The activity should use {@link + * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display on top of the + * lock screen while secured. There is no activity stack when this flag is used, so + * launching more than one activity is strongly discouraged. + * <p> + * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written. + * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap + * object in the extra field. This is useful for applications that only need a small image. + * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri + * value of EXTRA_OUTPUT. + * + * @see #ACTION_IMAGE_CAPTURE + * @see #EXTRA_OUTPUT + */ + public static final String ACTION_IMAGE_CAPTURE_SECURE = + "android.media.action.IMAGE_CAPTURE_SECURE"; + + /** * Standard Intent action that can be sent to have the camera application * capture a video and return it. * <p> diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 74c0a97..2a8cf21 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -19,6 +19,7 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.SearchManager; +import android.app.WallpaperManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; @@ -32,16 +33,19 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.database.SQLException; +import android.net.ConnectivityManager; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.BatteryManager; import android.os.Bundle; +import android.os.DropBoxManager; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; -import android.os.UserId; +import android.os.UserHandle; +import android.os.Build.VERSION_CODES; import android.speech.tts.TextToSpeech; import android.text.TextUtils; import android.util.AndroidException; @@ -214,6 +218,21 @@ public final class Settings { "android.settings.BLUETOOTH_SETTINGS"; /** + * Activity Action: Show settings to allow configuration of Wifi Displays. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_WIFI_DISPLAY_SETTINGS = + "android.settings.WIFI_DISPLAY_SETTINGS"; + + /** * Activity Action: Show settings to allow configuration of date and time. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -621,6 +640,25 @@ public final class Settings { public static final String CALL_METHOD_GET_SECURE = "GET_secure"; /** + * @hide - Private call() method on SettingsProvider to read from 'global' table. + */ + public static final String CALL_METHOD_GET_GLOBAL = "GET_global"; + + /** + * @hide - User handle argument extra to the fast-path call()-based requests + */ + public static final String CALL_METHOD_USER_KEY = "_user"; + + /** @hide - Private call() method to write to 'system' table */ + public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system"; + + /** @hide - Private call() method to write to 'secure' table */ + public static final String CALL_METHOD_PUT_SECURE = "PUT_secure"; + + /** @hide - Private call() method to write to 'global' table */ + public static final String CALL_METHOD_PUT_GLOBAL= "PUT_global"; + + /** * Activity Extra: Limit available options in launched activity based on the given authority. * <p> * This can be passed as an extra field in an Activity Intent with one or more syncable content @@ -640,7 +678,7 @@ public final class Settings { public static final String AUTHORITY = "settings"; private static final String TAG = "Settings"; - private static final boolean LOCAL_LOGV = false || false; + private static final boolean LOCAL_LOGV = false; public static class SettingNotFoundException extends AndroidException { public SettingNotFoundException(String msg) { @@ -693,33 +731,18 @@ public final class Settings { // The method we'll call (or null, to not use) on the provider // for the fast path of retrieving settings. - private final String mCallCommand; + private final String mCallGetCommand; + private final String mCallSetCommand; - public NameValueCache(String versionSystemProperty, Uri uri, String callCommand) { + public NameValueCache(String versionSystemProperty, Uri uri, + String getCommand, String setCommand) { mVersionSystemProperty = versionSystemProperty; mUri = uri; - mCallCommand = callCommand; + mCallGetCommand = getCommand; + mCallSetCommand = setCommand; } - public String getString(ContentResolver cr, String name) { - long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0); - - synchronized (this) { - if (mValuesVersion != newValuesVersion) { - if (LOCAL_LOGV) { - Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " + - newValuesVersion + " != cached " + mValuesVersion); - } - - mValues.clear(); - mValuesVersion = newValuesVersion; - } - - if (mValues.containsKey(name)) { - return mValues.get(name); // Could be null, that's OK -- negative caching - } - } - + private IContentProvider lazyGetProvider(ContentResolver cr) { IContentProvider cp = null; synchronized (this) { cp = mContentProvider; @@ -727,18 +750,75 @@ public final class Settings { cp = mContentProvider = cr.acquireProvider(mUri.getAuthority()); } } + return cp; + } + + public boolean putStringForUser(ContentResolver cr, String name, String value, + final int userHandle) { + try { + Bundle arg = new Bundle(); + arg.putString(Settings.NameValueTable.VALUE, value); + arg.putInt(CALL_METHOD_USER_KEY, userHandle); + IContentProvider cp = lazyGetProvider(cr); + cp.call(mCallSetCommand, name, arg); + } catch (RemoteException e) { + Log.w(TAG, "Can't set key " + name + " in " + mUri, e); + return false; + } + return true; + } + + public String getStringForUser(ContentResolver cr, String name, final int userHandle) { + final boolean isSelf = (userHandle == UserHandle.myUserId()); + if (isSelf) { + long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0); + + // Our own user's settings data uses a client-side cache + synchronized (this) { + if (mValuesVersion != newValuesVersion) { + if (LOCAL_LOGV || false) { + Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " + + newValuesVersion + " != cached " + mValuesVersion); + } + + mValues.clear(); + mValuesVersion = newValuesVersion; + } + + if (mValues.containsKey(name)) { + return mValues.get(name); // Could be null, that's OK -- negative caching + } + } + } else { + if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle + + " by user " + UserHandle.myUserId() + " so skipping cache"); + } + + IContentProvider cp = lazyGetProvider(cr); // Try the fast path first, not using query(). If this // fails (alternate Settings provider that doesn't support // this interface?) then we fall back to the query/table // interface. - if (mCallCommand != null) { + if (mCallGetCommand != null) { try { - Bundle b = cp.call(mCallCommand, name, null); + Bundle args = null; + if (!isSelf) { + args = new Bundle(); + args.putInt(CALL_METHOD_USER_KEY, userHandle); + } + Bundle b = cp.call(mCallGetCommand, name, args); if (b != null) { String value = b.getPairValue(); - synchronized (this) { - mValues.put(name, value); + // Don't update our cache for reads of other users' data + if (isSelf) { + synchronized (this) { + mValues.put(name, value); + } + } else { + if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userHandle + + " by " + UserHandle.myUserId() + + " so not updating cache"); } return value; } @@ -785,19 +865,23 @@ public final class Settings { public static final class System extends NameValueTable { public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version"; - // Populated lazily, guarded by class object: - private static NameValueCache sNameValueCache = null; + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/system"); + + private static final NameValueCache sNameValueCache = new NameValueCache( + SYS_PROP_SETTING_VERSION, + CONTENT_URI, + CALL_METHOD_GET_SYSTEM, + CALL_METHOD_PUT_SYSTEM); private static final HashSet<String> MOVED_TO_SECURE; static { MOVED_TO_SECURE = new HashSet<String>(30); - MOVED_TO_SECURE.add(Secure.ADB_ENABLED); MOVED_TO_SECURE.add(Secure.ANDROID_ID); - MOVED_TO_SECURE.add(Secure.BLUETOOTH_ON); - MOVED_TO_SECURE.add(Secure.DATA_ROAMING); - MOVED_TO_SECURE.add(Secure.DEVICE_PROVISIONED); MOVED_TO_SECURE.add(Secure.HTTP_PROXY); - MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS); MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED); MOVED_TO_SECURE.add(Secure.LOCK_BIOMETRIC_WEAK_FLAGS); MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_ENABLED); @@ -808,7 +892,6 @@ public final class Settings { MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_LAST_UPDATE); MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_REDIRECT_URL); MOVED_TO_SECURE.add(Secure.SETTINGS_CLASSNAME); - MOVED_TO_SECURE.add(Secure.USB_MASS_STORAGE_ENABLED); MOVED_TO_SECURE.add(Secure.USE_GOOGLE_MAIL); MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON); MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY); @@ -827,23 +910,74 @@ public final class Settings { MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS); } + private static final HashSet<String> MOVED_TO_GLOBAL; + static { + MOVED_TO_GLOBAL = new HashSet<String>(); + // these were originally in system but migrated to secure in the past, + // so are duplicated in the Secure.* namespace + MOVED_TO_GLOBAL.add(Global.ADB_ENABLED); + MOVED_TO_GLOBAL.add(Global.BLUETOOTH_ON); + MOVED_TO_GLOBAL.add(Global.DATA_ROAMING); + MOVED_TO_GLOBAL.add(Global.DEVICE_PROVISIONED); + MOVED_TO_GLOBAL.add(Global.INSTALL_NON_MARKET_APPS); + MOVED_TO_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED); + MOVED_TO_GLOBAL.add(Global.HTTP_PROXY); + + // these are moving directly from system to global + MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_ON); + MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_RADIOS); + MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + MOVED_TO_GLOBAL.add(Settings.Global.AUTO_TIME); + MOVED_TO_GLOBAL.add(Settings.Global.AUTO_TIME_ZONE); + MOVED_TO_GLOBAL.add(Settings.Global.CAR_DOCK_SOUND); + MOVED_TO_GLOBAL.add(Settings.Global.CAR_UNDOCK_SOUND); + MOVED_TO_GLOBAL.add(Settings.Global.DESK_DOCK_SOUND); + MOVED_TO_GLOBAL.add(Settings.Global.DESK_UNDOCK_SOUND); + MOVED_TO_GLOBAL.add(Settings.Global.DOCK_SOUNDS_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.LOCK_SOUND); + MOVED_TO_GLOBAL.add(Settings.Global.UNLOCK_SOUND); + MOVED_TO_GLOBAL.add(Settings.Global.LOW_BATTERY_SOUND); + MOVED_TO_GLOBAL.add(Settings.Global.POWER_SOUNDS_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SLEEP_POLICY); + MOVED_TO_GLOBAL.add(Settings.Global.MODE_RINGER); + MOVED_TO_GLOBAL.add(Settings.Global.WINDOW_ANIMATION_SCALE); + MOVED_TO_GLOBAL.add(Settings.Global.TRANSITION_ANIMATION_SCALE); + MOVED_TO_GLOBAL.add(Settings.Global.ANIMATOR_DURATION_SCALE); + MOVED_TO_GLOBAL.add(Settings.Global.FANCY_IME_ANIMATIONS); + MOVED_TO_GLOBAL.add(Settings.Global.COMPATIBILITY_MODE); + MOVED_TO_GLOBAL.add(Settings.Global.EMERGENCY_TONE); + MOVED_TO_GLOBAL.add(Settings.Global.CALL_AUTO_RETRY); + MOVED_TO_GLOBAL.add(Settings.Global.DEBUG_APP); + MOVED_TO_GLOBAL.add(Settings.Global.WAIT_FOR_DEBUGGER); + MOVED_TO_GLOBAL.add(Settings.Global.SHOW_PROCESSES); + MOVED_TO_GLOBAL.add(Settings.Global.ALWAYS_FINISH_ACTIVITIES); + } + /** * Look up a name in the database. * @param resolver to access the database with * @param name to look up in the table * @return the corresponding value, or null if not present */ - public synchronized static String getString(ContentResolver resolver, String name) { + public static String getString(ContentResolver resolver, String name) { + return getStringForUser(resolver, name, UserHandle.myUserId()); + } + + /** @hide */ + public static String getStringForUser(ContentResolver resolver, String name, + int userHandle) { if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, returning read-only value."); - return Secure.getString(resolver, name); + return Secure.getStringForUser(resolver, name, userHandle); } - if (sNameValueCache == null) { - sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI, - CALL_METHOD_GET_SYSTEM); + if (MOVED_TO_GLOBAL.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + + " to android.provider.Settings.Global, returning read-only value."); + return Global.getStringForUser(resolver, name, userHandle); } - return sNameValueCache.getString(resolver, name); + return sNameValueCache.getStringForUser(resolver, name, userHandle); } /** @@ -854,12 +988,23 @@ public final class Settings { * @return true if the value was set, false on database errors */ public static boolean putString(ContentResolver resolver, String name, String value) { + return putStringForUser(resolver, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putStringForUser(ContentResolver resolver, String name, String value, + int userHandle) { if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, value is unchanged."); return false; } - return putString(resolver, CONTENT_URI, name, value); + if (MOVED_TO_GLOBAL.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + + " to android.provider.Settings.Global, value is unchanged."); + return false; + } + return sNameValueCache.putStringForUser(resolver, name, value, userHandle); } /** @@ -874,6 +1019,11 @@ public final class Settings { + " to android.provider.Settings.Secure, returning Secure URI."); return Secure.getUriFor(Secure.CONTENT_URI, name); } + if (MOVED_TO_GLOBAL.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + + " to android.provider.Settings.Global, returning read-only global URI."); + return Global.getUriFor(Global.CONTENT_URI, name); + } return getUriFor(CONTENT_URI, name); } @@ -892,7 +1042,12 @@ public final class Settings { * or not a valid integer. */ public static int getInt(ContentResolver cr, String name, int def) { - String v = getString(cr, name); + return getIntForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { + String v = getStringForUser(cr, name, userHandle); try { return v != null ? Integer.parseInt(v) : def; } catch (NumberFormatException e) { @@ -920,7 +1075,13 @@ public final class Settings { */ public static int getInt(ContentResolver cr, String name) throws SettingNotFoundException { - String v = getString(cr, name); + return getIntForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int userHandle) + throws SettingNotFoundException { + String v = getStringForUser(cr, name, userHandle); try { return Integer.parseInt(v); } catch (NumberFormatException e) { @@ -942,7 +1103,13 @@ public final class Settings { * @return true if the value was set, false on database errors */ public static boolean putInt(ContentResolver cr, String name, int value) { - return putString(cr, name, Integer.toString(value)); + return putIntForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putIntForUser(ContentResolver cr, String name, int value, + int userHandle) { + return putStringForUser(cr, name, Integer.toString(value), userHandle); } /** @@ -960,7 +1127,13 @@ public final class Settings { * or not a valid {@code long}. */ public static long getLong(ContentResolver cr, String name, long def) { - String valString = getString(cr, name); + return getLongForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, long def, + int userHandle) { + String valString = getStringForUser(cr, name, userHandle); long value; try { value = valString != null ? Long.parseLong(valString) : def; @@ -989,7 +1162,13 @@ public final class Settings { */ public static long getLong(ContentResolver cr, String name) throws SettingNotFoundException { - String valString = getString(cr, name); + return getLongForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, int userHandle) + throws SettingNotFoundException { + String valString = getStringForUser(cr, name, userHandle); try { return Long.parseLong(valString); } catch (NumberFormatException e) { @@ -1011,7 +1190,13 @@ public final class Settings { * @return true if the value was set, false on database errors */ public static boolean putLong(ContentResolver cr, String name, long value) { - return putString(cr, name, Long.toString(value)); + return putLongForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putLongForUser(ContentResolver cr, String name, long value, + int userHandle) { + return putStringForUser(cr, name, Long.toString(value), userHandle); } /** @@ -1029,7 +1214,13 @@ public final class Settings { * or not a valid float. */ public static float getFloat(ContentResolver cr, String name, float def) { - String v = getString(cr, name); + return getFloatForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, float def, + int userHandle) { + String v = getStringForUser(cr, name, userHandle); try { return v != null ? Float.parseFloat(v) : def; } catch (NumberFormatException e) { @@ -1057,7 +1248,13 @@ public final class Settings { */ public static float getFloat(ContentResolver cr, String name) throws SettingNotFoundException { - String v = getString(cr, name); + return getFloatForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, int userHandle) + throws SettingNotFoundException { + String v = getStringForUser(cr, name, userHandle); if (v == null) { throw new SettingNotFoundException(name); } @@ -1082,7 +1279,13 @@ public final class Settings { * @return true if the value was set, false on database errors */ public static boolean putFloat(ContentResolver cr, String name, float value) { - return putString(cr, name, Float.toString(value)); + return putFloatForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putFloatForUser(ContentResolver cr, String name, float value, + int userHandle) { + return putStringForUser(cr, name, Float.toString(value), userHandle); } /** @@ -1094,8 +1297,14 @@ public final class Settings { * @param outConfig Where to place the configuration settings. */ public static void getConfiguration(ContentResolver cr, Configuration outConfig) { - outConfig.fontScale = Settings.System.getFloat( - cr, FONT_SCALE, outConfig.fontScale); + getConfigurationForUser(cr, outConfig, UserHandle.myUserId()); + } + + /** @hide */ + public static void getConfigurationForUser(ContentResolver cr, Configuration outConfig, + int userHandle) { + outConfig.fontScale = Settings.System.getFloatForUser( + cr, FONT_SCALE, outConfig.fontScale, userHandle); if (outConfig.fontScale < 0) { outConfig.fontScale = 1; } @@ -1118,7 +1327,13 @@ public final class Settings { * @return true if the values were set, false on database errors */ public static boolean putConfiguration(ContentResolver cr, Configuration config) { - return Settings.System.putFloat(cr, FONT_SCALE, config.fontScale); + return putConfigurationForUser(cr, config, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putConfigurationForUser(ContentResolver cr, Configuration config, + int userHandle) { + return Settings.System.putFloatForUser(cr, FONT_SCALE, config.fontScale, userHandle); } /** @hide */ @@ -1126,31 +1341,42 @@ public final class Settings { return (changes&ActivityInfo.CONFIG_FONT_SCALE) != 0; } + /** @deprecated - Do not use */ + @Deprecated public static boolean getShowGTalkServiceStatus(ContentResolver cr) { - return getInt(cr, SHOW_GTALK_SERVICE_STATUS, 0) != 0; + return getShowGTalkServiceStatusForUser(cr, UserHandle.myUserId()); } + /** + * @hide + * @deprecated - Do not use + */ + public static boolean getShowGTalkServiceStatusForUser(ContentResolver cr, + int userHandle) { + return getIntForUser(cr, SHOW_GTALK_SERVICE_STATUS, 0, userHandle) != 0; + } + + /** @deprecated - Do not use */ + @Deprecated public static void setShowGTalkServiceStatus(ContentResolver cr, boolean flag) { - putInt(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0); + setShowGTalkServiceStatusForUser(cr, flag, UserHandle.myUserId()); } /** - * The content:// style URL for this table + * @hide + * @deprecated - Do not use */ - public static final Uri CONTENT_URI = - Uri.parse("content://" + AUTHORITY + "/system"); + @Deprecated + public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean flag, + int userHandle) { + putIntForUser(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0, userHandle); + } /** - * Whether we keep the device on while the device is plugged in. - * Supported values are: - * <ul> - * <li>{@code 0} to never stay on while plugged in</li> - * <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li> - * <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li> - * </ul> - * These values can be OR-ed together. + * @deprecated Use {@link android.provider.Settings.Global#STAY_ON_WHILE_PLUGGED_IN} instead */ - public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in"; + @Deprecated + public static final String STAY_ON_WHILE_PLUGGED_IN = Global.STAY_ON_WHILE_PLUGGED_IN; /** * What happens when the user presses the end call button if they're not @@ -1195,122 +1421,146 @@ public final class Settings { public static final int ADVANCED_SETTINGS_DEFAULT = 0; /** - * Whether Airplane Mode is on. + * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_ON} instead */ - public static final String AIRPLANE_MODE_ON = "airplane_mode_on"; + @Deprecated + public static final String AIRPLANE_MODE_ON = Global.AIRPLANE_MODE_ON; /** - * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio. + * @deprecated Use {@link android.provider.Settings.Global#RADIO_BLUETOOTH} instead */ - public static final String RADIO_BLUETOOTH = "bluetooth"; + @Deprecated + public static final String RADIO_BLUETOOTH = Global.RADIO_BLUETOOTH; /** - * Constant for use in AIRPLANE_MODE_RADIOS to specify Wi-Fi radio. + * @deprecated Use {@link android.provider.Settings.Global#RADIO_WIFI} instead */ - public static final String RADIO_WIFI = "wifi"; + @Deprecated + public static final String RADIO_WIFI = Global.RADIO_WIFI; /** + * @deprecated Use {@link android.provider.Settings.Global#RADIO_WIMAX} instead * {@hide} */ - public static final String RADIO_WIMAX = "wimax"; + @Deprecated + public static final String RADIO_WIMAX = Global.RADIO_WIMAX; + /** - * Constant for use in AIRPLANE_MODE_RADIOS to specify Cellular radio. + * @deprecated Use {@link android.provider.Settings.Global#RADIO_CELL} instead */ - public static final String RADIO_CELL = "cell"; + @Deprecated + public static final String RADIO_CELL = Global.RADIO_CELL; /** - * Constant for use in AIRPLANE_MODE_RADIOS to specify NFC radio. + * @deprecated Use {@link android.provider.Settings.Global#RADIO_NFC} instead */ - public static final String RADIO_NFC = "nfc"; + @Deprecated + public static final String RADIO_NFC = Global.RADIO_NFC; /** - * A comma separated list of radios that need to be disabled when airplane mode - * is on. This overrides WIFI_ON and BLUETOOTH_ON, if Wi-Fi and bluetooth are - * included in the comma separated list. + * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_RADIOS} instead */ - public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios"; + @Deprecated + public static final String AIRPLANE_MODE_RADIOS = Global.AIRPLANE_MODE_RADIOS; /** - * A comma separated list of radios that should to be disabled when airplane mode - * is on, but can be manually reenabled by the user. For example, if RADIO_WIFI is - * added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi - * will be turned off when entering airplane mode, but the user will be able to reenable - * Wifi in the Settings app. + * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_TOGGLEABLE_RADIOS} instead * * {@hide} */ - public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios"; + @Deprecated + public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = + Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS; /** - * The policy for deciding when Wi-Fi should go to sleep (which will in - * turn switch to using the mobile data as an Internet connection). - * <p> - * Set to one of {@link #WIFI_SLEEP_POLICY_DEFAULT}, - * {@link #WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED}, or - * {@link #WIFI_SLEEP_POLICY_NEVER}. + * @deprecated Use {@link android.provider.Settings.Global#WIFI_SLEEP_POLICY} instead */ - public static final String WIFI_SLEEP_POLICY = "wifi_sleep_policy"; + @Deprecated + public static final String WIFI_SLEEP_POLICY = Global.WIFI_SLEEP_POLICY; /** - * Value for {@link #WIFI_SLEEP_POLICY} to use the default Wi-Fi sleep - * policy, which is to sleep shortly after the turning off - * according to the {@link #STAY_ON_WHILE_PLUGGED_IN} setting. + * @deprecated Use {@link android.provider.Settings.Global#WIFI_SLEEP_POLICY_DEFAULT} instead */ - public static final int WIFI_SLEEP_POLICY_DEFAULT = 0; + @Deprecated + public static final int WIFI_SLEEP_POLICY_DEFAULT = Global.WIFI_SLEEP_POLICY_DEFAULT; /** - * Value for {@link #WIFI_SLEEP_POLICY} to use the default policy when - * the device is on battery, and never go to sleep when the device is - * plugged in. + * @deprecated Use {@link android.provider.Settings.Global#WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED} instead */ - public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED = 1; + @Deprecated + public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED = + Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED; /** - * Value for {@link #WIFI_SLEEP_POLICY} to never go to sleep. + * @deprecated Use {@link android.provider.Settings.Global#WIFI_SLEEP_POLICY_NEVER} instead */ - public static final int WIFI_SLEEP_POLICY_NEVER = 2; + @Deprecated + public static final int WIFI_SLEEP_POLICY_NEVER = Global.WIFI_SLEEP_POLICY_NEVER; + + /** + * @deprecated Use {@link android.provider.Settings.Global#MODE_RINGER} instead + */ + @Deprecated + public static final String MODE_RINGER = Global.MODE_RINGER; - //TODO: deprecate static IP constants /** * Whether to use static IP and other static network attributes. * <p> * Set to 1 for true and 0 for false. + * + * @deprecated Use {@link WifiManager} instead */ + @Deprecated public static final String WIFI_USE_STATIC_IP = "wifi_use_static_ip"; /** * The static IP address. * <p> * Example: "192.168.1.51" + * + * @deprecated Use {@link WifiManager} instead */ + @Deprecated public static final String WIFI_STATIC_IP = "wifi_static_ip"; /** * If using static IP, the gateway's IP address. * <p> * Example: "192.168.1.1" + * + * @deprecated Use {@link WifiManager} instead */ + @Deprecated public static final String WIFI_STATIC_GATEWAY = "wifi_static_gateway"; /** * If using static IP, the net mask. * <p> * Example: "255.255.255.0" + * + * @deprecated Use {@link WifiManager} instead */ + @Deprecated public static final String WIFI_STATIC_NETMASK = "wifi_static_netmask"; /** * If using static IP, the primary DNS's IP address. * <p> * Example: "192.168.1.1" + * + * @deprecated Use {@link WifiManager} instead */ + @Deprecated public static final String WIFI_STATIC_DNS1 = "wifi_static_dns1"; /** * If using static IP, the secondary DNS's IP address. * <p> * Example: "192.168.1.2" + * + * @deprecated Use {@link WifiManager} instead */ + @Deprecated public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2"; @@ -1370,18 +1620,26 @@ public final class Settings { /** * Name of an application package to be debugged. + * + * @deprecated Use {@link Global#DEBUG_APP} instead */ - public static final String DEBUG_APP = "debug_app"; + @Deprecated + public static final String DEBUG_APP = Global.DEBUG_APP; /** * If 1, when launching DEBUG_APP it will wait for the debugger before * starting user code. If 0, it will run normally. + * + * @deprecated Use {@link Global#WAIT_FOR_DEBUGGER} instead */ - public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger"; + @Deprecated + public static final String WAIT_FOR_DEBUGGER = Global.WAIT_FOR_DEBUGGER; /** * Whether or not to dim the screen. 0=no 1=yes + * @deprecated This setting is no longer used. */ + @Deprecated public static final String DIM_SCREEN = "dim_screen"; /** @@ -1390,14 +1648,6 @@ public final class Settings { public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout"; /** - * If 0, the compatibility mode is off for all applications. - * If 1, older applications run under compatibility mode. - * TODO: remove this settings before code freeze (bug/1907571) - * @hide - */ - public static final String COMPATIBILITY_MODE = "compatibility_mode"; - - /** * The screen backlight brightness between 0 and 255. */ public static final String SCREEN_BRIGHTNESS = "screen_brightness"; @@ -1426,23 +1676,21 @@ public final class Settings { /** * Control whether the process CPU usage meter should be shown. + * + * @deprecated Use {@link Global#SHOW_PROCESSES} instead */ - public static final String SHOW_PROCESSES = "show_processes"; + @Deprecated + public static final String SHOW_PROCESSES = Global.SHOW_PROCESSES; /** * If 1, the activity manager will aggressively finish activities and * processes as soon as they are no longer needed. If 0, the normal * extended lifetime is used. + * + * @deprecated Use {@link Global#ALWAYS_FINISH_ACTIVITIES} instead */ - public static final String ALWAYS_FINISH_ACTIVITIES = - "always_finish_activities"; - - - /** - * Ringer mode. This is used internally, changing this value will not - * change the ringer mode. See AudioManager. - */ - public static final String MODE_RINGER = "mode_ringer"; + @Deprecated + public static final String ALWAYS_FINISH_ACTIVITIES = Global.ALWAYS_FINISH_ACTIVITIES; /** * Determines which streams are affected by ringer mode changes. The @@ -1666,20 +1914,25 @@ public final class Settings { /** * Name of activity to use for wallpaper on the home screen. + * + * @deprecated Use {@link WallpaperManager} instead. */ + @Deprecated public static final String WALLPAPER_ACTIVITY = "wallpaper_activity"; /** - * Value to specify if the user prefers the date, time and time zone - * to be automatically fetched from the network (NITZ). 1=yes, 0=no + * @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME} + * instead */ - public static final String AUTO_TIME = "auto_time"; + @Deprecated + public static final String AUTO_TIME = Global.AUTO_TIME; /** - * Value to specify if the user prefers the time zone - * to be automatically fetched from the network (NITZ). 1=yes, 0=no + * @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME_ZONE} + * instead */ - public static final String AUTO_TIME_ZONE = "auto_time_zone"; + @Deprecated + public static final String AUTO_TIME_ZONE = Global.AUTO_TIME_ZONE; /** * Display times as 12 or 24 hours @@ -1708,28 +1961,30 @@ public final class Settings { /** * Scaling factor for normal window animations. Setting to 0 will disable window * animations. + * + * @deprecated Use {@link Global#WINDOW_ANIMATION_SCALE} instead */ - public static final String WINDOW_ANIMATION_SCALE = "window_animation_scale"; + @Deprecated + public static final String WINDOW_ANIMATION_SCALE = Global.WINDOW_ANIMATION_SCALE; /** * Scaling factor for activity transition animations. Setting to 0 will disable window * animations. + * + * @deprecated Use {@link Global#TRANSITION_ANIMATION_SCALE} instead */ - public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale"; + @Deprecated + public static final String TRANSITION_ANIMATION_SCALE = Global.TRANSITION_ANIMATION_SCALE; /** * Scaling factor for Animator-based animations. This affects both the start delay and * duration of all such animations. Setting to 0 will cause animations to end immediately. * The default value is 1. + * + * @deprecated Use {@link Global#ANIMATOR_DURATION_SCALE} instead */ - public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale"; - - /** - * Scaling factor for normal window animations. Setting to 0 will disable window - * animations. - * @hide - */ - public static final String FANCY_IME_ANIMATIONS = "fancy_ime_animations"; + @Deprecated + public static final String ANIMATOR_DURATION_SCALE = Global.ANIMATOR_DURATION_SCALE; /** * Control whether the accelerometer will be used to change screen @@ -1793,23 +2048,6 @@ public final class Settings { public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type"; /** - * CDMA only settings - * Emergency Tone 0 = Off - * 1 = Alert - * 2 = Vibrate - * @hide - */ - public static final String EMERGENCY_TONE = "emergency_tone"; - - /** - * CDMA only settings - * Whether the auto retry is enabled. The value is - * boolean (1 or 0). - * @hide - */ - public static final String CALL_AUTO_RETRY = "call_auto_retry"; - - /** * Whether the hearing aid is enabled. The value is * boolean (1 or 0). * @hide @@ -1880,16 +2118,20 @@ public final class Settings { "window_orientation_listener_log"; /** - * Whether to play a sound for low-battery alerts. + * @deprecated Use {@link android.provider.Settings.Global#POWER_SOUNDS_ENABLED} + * instead * @hide */ - public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled"; + @Deprecated + public static final String POWER_SOUNDS_ENABLED = Global.POWER_SOUNDS_ENABLED; /** - * Whether to play a sound for dock events. + * @deprecated Use {@link android.provider.Settings.Global#DOCK_SOUNDS_ENABLED} + * instead * @hide */ - public static final String DOCK_SOUNDS_ENABLED = "dock_sounds_enabled"; + @Deprecated + public static final String DOCK_SOUNDS_ENABLED = Global.DOCK_SOUNDS_ENABLED; /** * Whether to play sounds when the keyguard is shown and dismissed. @@ -1904,46 +2146,60 @@ public final class Settings { public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled"; /** - * URI for the low battery sound file. + * @deprecated Use {@link android.provider.Settings.Global#LOW_BATTERY_SOUND} + * instead * @hide */ - public static final String LOW_BATTERY_SOUND = "low_battery_sound"; + @Deprecated + public static final String LOW_BATTERY_SOUND = Global.LOW_BATTERY_SOUND; /** - * URI for the desk dock "in" event sound. + * @deprecated Use {@link android.provider.Settings.Global#DESK_DOCK_SOUND} + * instead * @hide */ - public static final String DESK_DOCK_SOUND = "desk_dock_sound"; + @Deprecated + public static final String DESK_DOCK_SOUND = Global.DESK_DOCK_SOUND; /** - * URI for the desk dock "out" event sound. + * @deprecated Use {@link android.provider.Settings.Global#DESK_UNDOCK_SOUND} + * instead * @hide */ - public static final String DESK_UNDOCK_SOUND = "desk_undock_sound"; + @Deprecated + public static final String DESK_UNDOCK_SOUND = Global.DESK_UNDOCK_SOUND; /** - * URI for the car dock "in" event sound. + * @deprecated Use {@link android.provider.Settings.Global#CAR_DOCK_SOUND} + * instead * @hide */ - public static final String CAR_DOCK_SOUND = "car_dock_sound"; + @Deprecated + public static final String CAR_DOCK_SOUND = Global.CAR_DOCK_SOUND; /** - * URI for the car dock "out" event sound. + * @deprecated Use {@link android.provider.Settings.Global#CAR_UNDOCK_SOUND} + * instead * @hide */ - public static final String CAR_UNDOCK_SOUND = "car_undock_sound"; + @Deprecated + public static final String CAR_UNDOCK_SOUND = Global.CAR_UNDOCK_SOUND; /** - * URI for the "device locked" (keyguard shown) sound. + * @deprecated Use {@link android.provider.Settings.Global#LOCK_SOUND} + * instead * @hide */ - public static final String LOCK_SOUND = "lock_sound"; + @Deprecated + public static final String LOCK_SOUND = Global.LOCK_SOUND; /** - * URI for the "device unlocked" (keyguard dismissed) sound. + * @deprecated Use {@link android.provider.Settings.Global#UNLOCK_SOUND} + * instead * @hide */ - public static final String UNLOCK_SOUND = "unlock_sound"; + @Deprecated + public static final String UNLOCK_SOUND = Global.UNLOCK_SOUND; /** * Receive incoming SIP calls? @@ -2043,8 +2299,8 @@ public final class Settings { DATE_FORMAT, DTMF_TONE_WHEN_DIALING, DTMF_TONE_TYPE_WHEN_DIALING, - EMERGENCY_TONE, - CALL_AUTO_RETRY, + Global.EMERGENCY_TONE, + Global.CALL_AUTO_RETRY, HEARING_AID, TTY_MODE, SOUND_EFFECTS_ENABLED, @@ -2063,11 +2319,11 @@ public final class Settings { // Settings moved to Settings.Secure /** - * @deprecated Use {@link android.provider.Settings.Secure#ADB_ENABLED} + * @deprecated Use {@link android.provider.Settings.Global#ADB_ENABLED} * instead */ @Deprecated - public static final String ADB_ENABLED = Secure.ADB_ENABLED; + public static final String ADB_ENABLED = Global.ADB_ENABLED; /** * @deprecated Use {@link android.provider.Settings.Secure#ANDROID_ID} instead @@ -2076,34 +2332,34 @@ public final class Settings { public static final String ANDROID_ID = Secure.ANDROID_ID; /** - * @deprecated Use {@link android.provider.Settings.Secure#BLUETOOTH_ON} instead + * @deprecated Use {@link android.provider.Settings.Global#BLUETOOTH_ON} instead */ @Deprecated - public static final String BLUETOOTH_ON = Secure.BLUETOOTH_ON; + public static final String BLUETOOTH_ON = Global.BLUETOOTH_ON; /** - * @deprecated Use {@link android.provider.Settings.Secure#DATA_ROAMING} instead + * @deprecated Use {@link android.provider.Settings.Global#DATA_ROAMING} instead */ @Deprecated - public static final String DATA_ROAMING = Secure.DATA_ROAMING; + public static final String DATA_ROAMING = Global.DATA_ROAMING; /** - * @deprecated Use {@link android.provider.Settings.Secure#DEVICE_PROVISIONED} instead + * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead */ @Deprecated - public static final String DEVICE_PROVISIONED = Secure.DEVICE_PROVISIONED; + public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED; /** - * @deprecated Use {@link android.provider.Settings.Secure#HTTP_PROXY} instead + * @deprecated Use {@link android.provider.Settings.Global#HTTP_PROXY} instead */ @Deprecated - public static final String HTTP_PROXY = Secure.HTTP_PROXY; + public static final String HTTP_PROXY = Global.HTTP_PROXY; /** - * @deprecated Use {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS} instead + * @deprecated Use {@link android.provider.Settings.Global#INSTALL_NON_MARKET_APPS} instead */ @Deprecated - public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS; + public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS; /** * @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED} @@ -2119,10 +2375,10 @@ public final class Settings { public static final String LOGGING_ID = Secure.LOGGING_ID; /** - * @deprecated Use {@link android.provider.Settings.Secure#NETWORK_PREFERENCE} instead + * @deprecated Use {@link android.provider.Settings.Global#NETWORK_PREFERENCE} instead */ @Deprecated - public static final String NETWORK_PREFERENCE = Secure.NETWORK_PREFERENCE; + public static final String NETWORK_PREFERENCE = Global.NETWORK_PREFERENCE; /** * @deprecated Use {@link android.provider.Settings.Secure#PARENTAL_CONTROL_ENABLED} @@ -2153,60 +2409,60 @@ public final class Settings { public static final String SETTINGS_CLASSNAME = Secure.SETTINGS_CLASSNAME; /** - * @deprecated Use {@link android.provider.Settings.Secure#USB_MASS_STORAGE_ENABLED} instead + * @deprecated Use {@link android.provider.Settings.Global#USB_MASS_STORAGE_ENABLED} instead */ @Deprecated - public static final String USB_MASS_STORAGE_ENABLED = Secure.USB_MASS_STORAGE_ENABLED; + public static final String USB_MASS_STORAGE_ENABLED = Global.USB_MASS_STORAGE_ENABLED; /** - * @deprecated Use {@link android.provider.Settings.Secure#USE_GOOGLE_MAIL} instead + * @deprecated Use {@link android.provider.Settings.Global#USE_GOOGLE_MAIL} instead */ @Deprecated - public static final String USE_GOOGLE_MAIL = Secure.USE_GOOGLE_MAIL; + public static final String USE_GOOGLE_MAIL = Global.USE_GOOGLE_MAIL; /** * @deprecated Use - * {@link android.provider.Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT} instead + * {@link android.provider.Settings.Global#WIFI_MAX_DHCP_RETRY_COUNT} instead */ @Deprecated - public static final String WIFI_MAX_DHCP_RETRY_COUNT = Secure.WIFI_MAX_DHCP_RETRY_COUNT; + public static final String WIFI_MAX_DHCP_RETRY_COUNT = Global.WIFI_MAX_DHCP_RETRY_COUNT; /** * @deprecated Use - * {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS} instead + * {@link android.provider.Settings.Global#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS} instead */ @Deprecated public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = - Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS; + Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS; /** * @deprecated Use - * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} instead + * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} instead */ @Deprecated public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = - Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON; + Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON; /** * @deprecated Use - * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} instead + * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} instead */ @Deprecated public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = - Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY; + Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY; /** - * @deprecated Use {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT} + * @deprecated Use {@link android.provider.Settings.Global#WIFI_NUM_OPEN_NETWORKS_KEPT} * instead */ @Deprecated - public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Secure.WIFI_NUM_OPEN_NETWORKS_KEPT; + public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Global.WIFI_NUM_OPEN_NETWORKS_KEPT; /** - * @deprecated Use {@link android.provider.Settings.Secure#WIFI_ON} instead + * @deprecated Use {@link android.provider.Settings.Global#WIFI_ON} instead */ @Deprecated - public static final String WIFI_ON = Secure.WIFI_ON; + public static final String WIFI_ON = Global.WIFI_ON; /** * @deprecated Use @@ -2264,10 +2520,10 @@ public final class Settings { public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = Secure.WIFI_WATCHDOG_MAX_AP_CHECKS; /** - * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_ON} instead + * @deprecated Use {@link android.provider.Settings.Global#WIFI_WATCHDOG_ON} instead */ @Deprecated - public static final String WIFI_WATCHDOG_ON = Secure.WIFI_WATCHDOG_ON; + public static final String WIFI_WATCHDOG_ON = Global.WIFI_WATCHDOG_ON; /** * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT} instead @@ -2300,18 +2556,150 @@ public final class Settings { public static final class Secure extends NameValueTable { public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version"; + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/secure"); + // Populated lazily, guarded by class object: - private static NameValueCache sNameValueCache = null; + private static final NameValueCache sNameValueCache = new NameValueCache( + SYS_PROP_SETTING_VERSION, + CONTENT_URI, + CALL_METHOD_GET_SECURE, + CALL_METHOD_PUT_SECURE); private static ILockSettings sLockSettings = null; private static boolean sIsSystemProcess; private static final HashSet<String> MOVED_TO_LOCK_SETTINGS; + private static final HashSet<String> MOVED_TO_GLOBAL; static { MOVED_TO_LOCK_SETTINGS = new HashSet<String>(3); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_ENABLED); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_VISIBLE); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED); + + MOVED_TO_GLOBAL = new HashSet<String>(); + MOVED_TO_GLOBAL.add(Settings.Global.ADB_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.ASSISTED_GPS_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.BLUETOOTH_ON); + MOVED_TO_GLOBAL.add(Settings.Global.CDMA_CELL_BROADCAST_SMS); + MOVED_TO_GLOBAL.add(Settings.Global.CDMA_ROAMING_MODE); + MOVED_TO_GLOBAL.add(Settings.Global.CDMA_SUBSCRIPTION_MODE); + MOVED_TO_GLOBAL.add(Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE); + MOVED_TO_GLOBAL.add(Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI); + MOVED_TO_GLOBAL.add(Settings.Global.DATA_ROAMING); + MOVED_TO_GLOBAL.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.DEVICE_PROVISIONED); + MOVED_TO_GLOBAL.add(Settings.Global.DISPLAY_DENSITY_FORCED); + MOVED_TO_GLOBAL.add(Settings.Global.DISPLAY_SIZE_FORCED); + MOVED_TO_GLOBAL.add(Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE); + MOVED_TO_GLOBAL.add(Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); + MOVED_TO_GLOBAL.add(Settings.Global.INSTALL_NON_MARKET_APPS); + MOVED_TO_GLOBAL.add(Settings.Global.MOBILE_DATA); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_BUCKET_DURATION); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_DELETE_AGE); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_PERSIST_BYTES); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_ROTATE_AGE); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_POLL_INTERVAL); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_REPORT_XT_OVER_DEV); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_SAMPLE_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_BUCKET_DURATION); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_DELETE_AGE); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_PERSIST_BYTES); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_ROTATE_AGE); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_TAG_DELETE_AGE); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES); + MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE); + MOVED_TO_GLOBAL.add(Settings.Global.NETWORK_PREFERENCE); + MOVED_TO_GLOBAL.add(Settings.Global.NITZ_UPDATE_DIFF); + MOVED_TO_GLOBAL.add(Settings.Global.NITZ_UPDATE_SPACING); + MOVED_TO_GLOBAL.add(Settings.Global.NTP_SERVER); + MOVED_TO_GLOBAL.add(Settings.Global.NTP_TIMEOUT); + MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_ERROR_POLL_COUNT); + MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS); + MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT); + MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS); + MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT); + MOVED_TO_GLOBAL.add(Settings.Global.SAMPLING_PROFILER_MS); + MOVED_TO_GLOBAL.add(Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL); + MOVED_TO_GLOBAL.add(Settings.Global.SETUP_PREPAID_DETECTION_REDIR_HOST); + MOVED_TO_GLOBAL.add(Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL); + MOVED_TO_GLOBAL.add(Settings.Global.TETHER_DUN_APN); + MOVED_TO_GLOBAL.add(Settings.Global.TETHER_DUN_REQUIRED); + MOVED_TO_GLOBAL.add(Settings.Global.TETHER_SUPPORTED); + MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_HELP_URI); + MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_MAX_NTP_CACHE_AGE_SEC); + MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_NOTIFICATION_TYPE); + MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_POLLING_SEC); + MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_RESET_DAY); + MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_THRESHOLD_BYTES); + MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_VALUE_KBITSPS); + MOVED_TO_GLOBAL.add(Settings.Global.USB_MASS_STORAGE_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.USE_GOOGLE_MAIL); + MOVED_TO_GLOBAL.add(Settings.Global.WEB_AUTOFILL_QUERY_URL); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_COUNTRY_CODE); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_FREQUENCY_BAND); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_IDLE_MS); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ON); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_WATCHDOG_ON); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON); + MOVED_TO_GLOBAL.add(Settings.Global.PACKAGE_VERIFIER_ENABLE); + MOVED_TO_GLOBAL.add(Settings.Global.PACKAGE_VERIFIER_TIMEOUT); + MOVED_TO_GLOBAL.add(Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE); + MOVED_TO_GLOBAL.add(Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS); + MOVED_TO_GLOBAL.add(Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS); + MOVED_TO_GLOBAL.add(Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS); + MOVED_TO_GLOBAL.add(Settings.Global.WTF_IS_FATAL); + MOVED_TO_GLOBAL.add(Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD); + MOVED_TO_GLOBAL.add(Settings.Global.BATTERY_DISCHARGE_THRESHOLD); + MOVED_TO_GLOBAL.add(Settings.Global.SEND_ACTION_APP_ERROR); + MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_AGE_SECONDS); + MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_MAX_FILES); + MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_QUOTA_KB); + MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_QUOTA_PERCENT); + MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_RESERVE_PERCENT); + MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_TAG_PREFIX); + MOVED_TO_GLOBAL.add(Settings.Global.ERROR_LOGCAT_PREFIX); + MOVED_TO_GLOBAL.add(Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL); + MOVED_TO_GLOBAL.add(Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD); + MOVED_TO_GLOBAL.add(Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE); + MOVED_TO_GLOBAL.add(Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES); + MOVED_TO_GLOBAL.add(Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES); + MOVED_TO_GLOBAL.add(Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS); + MOVED_TO_GLOBAL.add(Settings.Global.CONNECTIVITY_CHANGE_DELAY); + MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_SERVER); + MOVED_TO_GLOBAL.add(Settings.Global.NSD_ON); + MOVED_TO_GLOBAL.add(Settings.Global.SET_INSTALL_LOCATION); + MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_INSTALL_LOCATION); + MOVED_TO_GLOBAL.add(Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY); + MOVED_TO_GLOBAL.add(Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY); + MOVED_TO_GLOBAL.add(Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT); + MOVED_TO_GLOBAL.add(Settings.Global.HTTP_PROXY); + MOVED_TO_GLOBAL.add(Settings.Global.GLOBAL_HTTP_PROXY_HOST); + MOVED_TO_GLOBAL.add(Settings.Global.GLOBAL_HTTP_PROXY_PORT); + MOVED_TO_GLOBAL.add(Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); + MOVED_TO_GLOBAL.add(Settings.Global.SET_GLOBAL_HTTP_PROXY); + MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_DNS_SERVER); + MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_NETWORK_MODE); + MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_CDMA_SUBSCRIPTION); } /** @@ -2320,27 +2708,37 @@ public final class Settings { * @param name to look up in the table * @return the corresponding value, or null if not present */ - public synchronized static String getString(ContentResolver resolver, String name) { - if (sNameValueCache == null) { - sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI, - CALL_METHOD_GET_SECURE); - } + public static String getString(ContentResolver resolver, String name) { + return getStringForUser(resolver, name, UserHandle.myUserId()); + } - if (sLockSettings == null) { - sLockSettings = ILockSettings.Stub.asInterface( - (IBinder) ServiceManager.getService("lock_settings")); - sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID; + /** @hide */ + public static String getStringForUser(ContentResolver resolver, String name, + int userHandle) { + if (MOVED_TO_GLOBAL.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure" + + " to android.provider.Settings.Global."); + return Global.getStringForUser(resolver, name, userHandle); } - if (sLockSettings != null && !sIsSystemProcess - && MOVED_TO_LOCK_SETTINGS.contains(name)) { - try { - return sLockSettings.getString(name, "0", UserId.getCallingUserId()); - } catch (RemoteException re) { - // Fall through + + if (MOVED_TO_LOCK_SETTINGS.contains(name)) { + synchronized (Secure.class) { + if (sLockSettings == null) { + sLockSettings = ILockSettings.Stub.asInterface( + (IBinder) ServiceManager.getService("lock_settings")); + sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID; + } + } + if (sLockSettings != null && !sIsSystemProcess) { + try { + return sLockSettings.getString(name, "0", userHandle); + } catch (RemoteException re) { + // Fall through + } } } - return sNameValueCache.getString(resolver, name); + return sNameValueCache.getStringForUser(resolver, name, userHandle); } /** @@ -2350,9 +2748,19 @@ public final class Settings { * @param value to associate with the name * @return true if the value was set, false on database errors */ - public static boolean putString(ContentResolver resolver, - String name, String value) { - return putString(resolver, CONTENT_URI, name, value); + public static boolean putString(ContentResolver resolver, String name, String value) { + return putStringForUser(resolver, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putStringForUser(ContentResolver resolver, String name, String value, + int userHandle) { + if (MOVED_TO_GLOBAL.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + + " to android.provider.Settings.Global"); + return Global.putStringForUser(resolver, name, value, userHandle); + } + return sNameValueCache.putStringForUser(resolver, name, value, userHandle); } /** @@ -2362,6 +2770,11 @@ public final class Settings { * @return the corresponding content URI, or null if not present */ public static Uri getUriFor(String name) { + if (MOVED_TO_GLOBAL.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure" + + " to android.provider.Settings.Global, returning global URI."); + return Global.getUriFor(Global.CONTENT_URI, name); + } return getUriFor(CONTENT_URI, name); } @@ -2380,7 +2793,12 @@ public final class Settings { * or not a valid integer. */ public static int getInt(ContentResolver cr, String name, int def) { - String v = getString(cr, name); + return getIntForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { + String v = getStringForUser(cr, name, userHandle); try { return v != null ? Integer.parseInt(v) : def; } catch (NumberFormatException e) { @@ -2408,7 +2826,13 @@ public final class Settings { */ public static int getInt(ContentResolver cr, String name) throws SettingNotFoundException { - String v = getString(cr, name); + return getIntForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static int getIntForUser(ContentResolver cr, String name, int userHandle) + throws SettingNotFoundException { + String v = getStringForUser(cr, name, userHandle); try { return Integer.parseInt(v); } catch (NumberFormatException e) { @@ -2430,7 +2854,13 @@ public final class Settings { * @return true if the value was set, false on database errors */ public static boolean putInt(ContentResolver cr, String name, int value) { - return putString(cr, name, Integer.toString(value)); + return putIntForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putIntForUser(ContentResolver cr, String name, int value, + int userHandle) { + return putStringForUser(cr, name, Integer.toString(value), userHandle); } /** @@ -2448,7 +2878,13 @@ public final class Settings { * or not a valid {@code long}. */ public static long getLong(ContentResolver cr, String name, long def) { - String valString = getString(cr, name); + return getLongForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, long def, + int userHandle) { + String valString = getStringForUser(cr, name, userHandle); long value; try { value = valString != null ? Long.parseLong(valString) : def; @@ -2477,7 +2913,13 @@ public final class Settings { */ public static long getLong(ContentResolver cr, String name) throws SettingNotFoundException { - String valString = getString(cr, name); + return getLongForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static long getLongForUser(ContentResolver cr, String name, int userHandle) + throws SettingNotFoundException { + String valString = getStringForUser(cr, name, userHandle); try { return Long.parseLong(valString); } catch (NumberFormatException e) { @@ -2499,7 +2941,13 @@ public final class Settings { * @return true if the value was set, false on database errors */ public static boolean putLong(ContentResolver cr, String name, long value) { - return putString(cr, name, Long.toString(value)); + return putLongForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putLongForUser(ContentResolver cr, String name, long value, + int userHandle) { + return putStringForUser(cr, name, Long.toString(value), userHandle); } /** @@ -2517,7 +2965,13 @@ public final class Settings { * or not a valid float. */ public static float getFloat(ContentResolver cr, String name, float def) { - String v = getString(cr, name); + return getFloatForUser(cr, name, def, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, float def, + int userHandle) { + String v = getStringForUser(cr, name, userHandle); try { return v != null ? Float.parseFloat(v) : def; } catch (NumberFormatException e) { @@ -2545,7 +2999,13 @@ public final class Settings { */ public static float getFloat(ContentResolver cr, String name) throws SettingNotFoundException { - String v = getString(cr, name); + return getFloatForUser(cr, name, UserHandle.myUserId()); + } + + /** @hide */ + public static float getFloatForUser(ContentResolver cr, String name, int userHandle) + throws SettingNotFoundException { + String v = getStringForUser(cr, name, userHandle); if (v == null) { throw new SettingNotFoundException(name); } @@ -2570,24 +3030,35 @@ public final class Settings { * @return true if the value was set, false on database errors */ public static boolean putFloat(ContentResolver cr, String name, float value) { - return putString(cr, name, Float.toString(value)); + return putFloatForUser(cr, name, value, UserHandle.myUserId()); + } + + /** @hide */ + public static boolean putFloatForUser(ContentResolver cr, String name, float value, + int userHandle) { + return putStringForUser(cr, name, Float.toString(value), userHandle); } /** - * The content:// style URL for this table + * @deprecated Use {@link android.provider.Settings.Global#DEVELOPMENT_SETTINGS_ENABLED} + * instead */ - public static final Uri CONTENT_URI = - Uri.parse("content://" + AUTHORITY + "/secure"); + @Deprecated + public static final String DEVELOPMENT_SETTINGS_ENABLED = + Global.DEVELOPMENT_SETTINGS_ENABLED; /** - * Whether user has enabled development settings. + * When the user has enable the option to have a "bug report" command + * in the power menu. + * @hide */ - public static final String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled"; + public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu"; /** - * Whether ADB is enabled. + * @deprecated Use {@link android.provider.Settings.Global#ADB_ENABLED} instead */ - public static final String ADB_ENABLED = "adb_enabled"; + @Deprecated + public static final String ADB_ENABLED = Global.ADB_ENABLED; /** * Setting to allow mock locations and location provider status to be injected into the @@ -2606,39 +3077,16 @@ public final class Settings { public static final String ANDROID_ID = "android_id"; /** - * Whether bluetooth is enabled/disabled - * 0=disabled. 1=enabled. + * @deprecated Use {@link android.provider.Settings.Global#BLUETOOTH_ON} instead */ - public static final String BLUETOOTH_ON = "bluetooth_on"; - - /** - * Get the key that retrieves a bluetooth headset's priority. - * @hide - */ - public static final String getBluetoothHeadsetPriorityKey(String address) { - return ("bluetooth_headset_priority_" + address.toUpperCase()); - } - - /** - * Get the key that retrieves a bluetooth a2dp sink's priority. - * @hide - */ - public static final String getBluetoothA2dpSinkPriorityKey(String address) { - return ("bluetooth_a2dp_sink_priority_" + address.toUpperCase()); - } - - /** - * Get the key that retrieves a bluetooth Input Device's priority. - * @hide - */ - public static final String getBluetoothInputDevicePriorityKey(String address) { - return ("bluetooth_input_device_priority_" + address.toUpperCase()); - } + @Deprecated + public static final String BLUETOOTH_ON = Global.BLUETOOTH_ON; /** - * Whether or not data roaming is enabled. (0 = false, 1 = true) + * @deprecated Use {@link android.provider.Settings.Global#DATA_ROAMING} instead */ - public static final String DATA_ROAMING = "data_roaming"; + @Deprecated + public static final String DATA_ROAMING = Global.DATA_ROAMING; /** * Setting to record the input method used by default, holding the ID @@ -2668,9 +3116,16 @@ public final class Settings { "input_method_selector_visibility"; /** - * Whether the device has been provisioned (0 = false, 1 = true) + * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead + */ + @Deprecated + public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED; + + /** + * Whether the current user has been set up via setup wizard (0 = false, 1 = true) + * @hide */ - public static final String DEVICE_PROVISIONED = "device_provisioned"; + public static final String USER_SETUP_COMPLETE = "user_setup_complete"; /** * List of input methods that are currently enabled. This is a string @@ -2688,54 +3143,19 @@ public final class Settings { public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods"; /** - * Host name and port for global http proxy. Uses ':' seperator for between host and port - * TODO - deprecate in favor of global_http_proxy_host, etc - */ - public static final String HTTP_PROXY = "http_proxy"; - - /** - * Host name for global http proxy. Set via ConnectivityManager. - * @hide - */ - public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host"; - - /** - * Integer host port for global http proxy. Set via ConnectivityManager. - * @hide - */ - public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port"; - - /** - * Exclusion list for global proxy. This string contains a list of comma-separated - * domains where the global proxy does not apply. Domains should be listed in a comma- - * separated list. Example of acceptable formats: ".domain1.com,my.domain2.com" - * Use ConnectivityManager to set/get. - * @hide - */ - public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST = - "global_http_proxy_exclusion_list"; - - /** - * Enables the UI setting to allow the user to specify the global HTTP proxy - * and associated exclusion list. - * @hide - */ - public static final String SET_GLOBAL_HTTP_PROXY = "set_global_http_proxy"; - - /** - * Setting for default DNS in case nobody suggests one - * @hide + * Host name and port for global http proxy. Uses ':' seperator for + * between host and port. + * + * @deprecated Use {@link Global#HTTP_PROXY} */ - public static final String DEFAULT_DNS_SERVER = "default_dns_server"; + @Deprecated + public static final String HTTP_PROXY = Global.HTTP_PROXY; /** - * Whether the package installer should allow installation of apps downloaded from - * sources other than Google Play. - * - * 1 = allow installing from other sources - * 0 = only allow installing from Google Play + * @deprecated Use {@link android.provider.Settings.Global#INSTALL_NON_MARKET_APPS} instead */ - public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; + @Deprecated + public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS; /** * Comma-separated list of location providers that activities may access. @@ -2780,24 +3200,25 @@ public final class Settings { public static final String LOCK_SCREEN_OWNER_INFO = "lock_screen_owner_info"; /** - * This preference enables showing the owner info on LockScren. + * Id of the time appwidget on the lockscreen, or -1 if none * @hide */ - public static final String LOCK_SCREEN_OWNER_INFO_ENABLED = - "lock_screen_owner_info_enabled"; + public static final String LOCK_SCREEN_STATUS_APPWIDGET_ID = + "lock_screen_status_appwidget_id"; /** - * The saved value for WindowManagerService.setForcedDisplaySize(). - * Two integers separated by a comma. If unset, then use the real display size. + * Id of the user-selected appwidget on the lockscreen, or -1 if none * @hide */ - public static final String DISPLAY_SIZE_FORCED = "display_size_forced"; + public static final String LOCK_SCREEN_USER_SELECTED_APPWIDGET_ID = + "lock_screen_user_selected_appwidget_id"; /** - * Whether assisted GPS should be enabled or not. + * This preference enables showing the owner info on LockScren. * @hide */ - public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled"; + public static final String LOCK_SCREEN_OWNER_INFO_ENABLED = + "lock_screen_owner_info_enabled"; /** * The Logging ID (a unique 64-bit value) as a hex string. @@ -2809,34 +3230,10 @@ public final class Settings { public static final String LOGGING_ID = "logging_id"; /** - * User preference for which network(s) should be used. Only the - * connectivity service should touch this. - */ - public static final String NETWORK_PREFERENCE = "network_preference"; - - /** - * Used to disable Tethering on a device - defaults to true - * @hide - */ - public static final String TETHER_SUPPORTED = "tether_supported"; - - /** - * Used to require DUN APN on the device or not - defaults to a build config value - * which defaults to false - * @hide - */ - public static final String TETHER_DUN_REQUIRED = "tether_dun_required"; - - /** - * Used to hold a gservices-provisioned apn value for DUN. If set, or the - * corresponding build config values are set it will override the APN DB - * values. - * Consists of a comma seperated list of strings: - * "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type" - * note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" - * @hide + * @deprecated Use {@link android.provider.Settings.Global#NETWORK_PREFERENCE} instead */ - public static final String TETHER_DUN_APN = "tether_dun_apn"; + @Deprecated + public static final String NETWORK_PREFERENCE = Global.NETWORK_PREFERENCE; /** * No longer supported. @@ -2854,15 +3251,6 @@ public final class Settings { public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url"; /** - * A positive value indicates how often the SamplingProfiler - * should take snapshots. Zero value means SamplingProfiler - * is disabled. - * - * @hide - */ - public static final String SAMPLING_PROFILER_MS = "sampling_profiler_ms"; - - /** * Settings classname to launch when Settings is clicked from All * Applications. Needed because of user testing between the old * and new Settings apps. @@ -2871,15 +3259,16 @@ public final class Settings { public static final String SETTINGS_CLASSNAME = "settings_classname"; /** - * USB Mass Storage Enabled + * @deprecated Use {@link android.provider.Settings.Global#USB_MASS_STORAGE_ENABLED} instead */ - public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled"; + @Deprecated + public static final String USB_MASS_STORAGE_ENABLED = Global.USB_MASS_STORAGE_ENABLED; /** - * If this setting is set (to anything), then all references - * to Gmail on the device must change to Google Mail. + * @deprecated Use {@link android.provider.Settings.Global#USE_GOOGLE_MAIL} instead */ - public static final String USE_GOOGLE_MAIL = "use_google_mail"; + @Deprecated + public static final String USE_GOOGLE_MAIL = Global.USE_GOOGLE_MAIL; /** * If accessibility is enabled. @@ -2898,7 +3287,7 @@ public final class Settings { "enabled_accessibility_services"; /** - * List of the accessibility services to which the user has graned + * List of the accessibility services to which the user has granted * permission to put the device into touch exploration mode. * * @hide @@ -2917,7 +3306,7 @@ public final class Settings { * <p> * Note: The JavaScript based screen-reader is served by the * Google infrastructure and enable users with disabilities to - * efficiantly navigate in and explore web content. + * efficiently navigate in and explore web content. * </p> * <p> * This property represents a boolean value. @@ -2929,7 +3318,7 @@ public final class Settings { /** * The URL for the injected JavaScript based screen-reader used - * for providing accessiblity of content in WebView. + * for providing accessibility of content in WebView. * <p> * Note: The JavaScript based screen-reader is served by the * Google infrastructure and enable users with disabilities to @@ -2982,6 +3371,46 @@ public final class Settings { "accessibility_web_content_key_bindings"; /** + * Setting that specifies whether the display magnification is enabled. + * Display magnifications allows the user to zoom in the display content + * and is targeted to low vision users. The current magnification scale + * is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = + "accessibility_display_magnification_enabled"; + + /** + * Setting that specifies what the display magnification scale is. + * Display magnifications allows the user to zoom in the display + * content and is targeted to low vision users. Whether a display + * magnification is performed is controlled by + * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED} + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE = + "accessibility_display_magnification_scale"; + + /** + * Setting that specifies whether the display magnification should be + * automatically updated. If this fearture is enabled the system will + * exit magnification mode or pan the viewport when a context change + * occurs. For example, on staring a new activity or rotating the screen, + * the system may zoom out so the user can see the new context he is in. + * Another example is on showing a window that is not visible in the + * magnified viewport the system may pan the viewport to make the window + * the has popped up so the user knows that the context has changed. + * Whether a screen magnification is performed is controlled by + * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED} + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE = + "accessibility_display_magnification_auto_update"; + + /** * The timout for considering a press to be a long press in milliseconds. * @hide */ @@ -3067,80 +3496,40 @@ public final class Settings { public static final String TTS_ENABLED_PLUGINS = "tts_enabled_plugins"; /** - * Whether to notify the user of open networks. - * <p> - * If not connected and the scan results have an open network, we will - * put this notification up. If we attempt to connect to a network or - * the open network(s) disappear, we remove the notification. When we - * show the notification, we will not show it again for - * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time. + * @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} + * instead. */ + @Deprecated public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = - "wifi_networks_available_notification_on"; - /** - * {@hide} - */ - public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON = - "wimax_networks_available_notification_on"; + Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON; /** - * Delay (in seconds) before repeating the Wi-Fi networks available notification. - * Connecting to a network will reset the timer. + * @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} + * instead. */ + @Deprecated public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = - "wifi_networks_available_repeat_delay"; - - /** - * 802.11 country code in ISO 3166 format - * @hide - */ - public static final String WIFI_COUNTRY_CODE = "wifi_country_code"; - - - /** - * When the number of open networks exceeds this number, the - * least-recently-used excess networks will be removed. - */ - public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept"; + Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY; /** - * Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this. + * @deprecated Use {@link android.provider.Settings.Global#WIFI_NUM_OPEN_NETWORKS_KEPT} + * instead. */ - public static final String WIFI_ON = "wifi_on"; - - /** - * Used to save the Wifi_ON state prior to tethering. - * This state will be checked to restore Wifi after - * the user turns off tethering. - * - * @hide - */ - public static final String WIFI_SAVED_STATE = "wifi_saved_state"; - - /** - * AP SSID - * - * @hide - */ - public static final String WIFI_AP_SSID = "wifi_ap_ssid"; - - /** - * AP security - * - * @hide - */ - public static final String WIFI_AP_SECURITY = "wifi_ap_security"; + @Deprecated + public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = + Global.WIFI_NUM_OPEN_NETWORKS_KEPT; /** - * AP passphrase - * - * @hide + * @deprecated Use {@link android.provider.Settings.Global#WIFI_ON} + * instead. */ - public static final String WIFI_AP_PASSWD = "wifi_ap_passwd"; + @Deprecated + public static final String WIFI_ON = Global.WIFI_ON; /** * The acceptable packet loss percentage (range 0 - 100) before trying * another AP on the same network. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE = @@ -3149,12 +3538,14 @@ public final class Settings { /** * The number of access points required for a network in order for the * watchdog to monitor it. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_AP_COUNT = "wifi_watchdog_ap_count"; /** * The delay between background checks. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS = @@ -3163,6 +3554,7 @@ public final class Settings { /** * Whether the Wi-Fi watchdog is enabled for background checking even * after it thinks the user has connected to a good access point. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED = @@ -3170,6 +3562,7 @@ public final class Settings { /** * The timeout for a background ping + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS = @@ -3180,6 +3573,7 @@ public final class Settings { * fail. Again, if these fail, they will *not* be used in packet loss * calculation. For example, one network always seemed to time out for * the first couple pings, so this is set to 3 by default. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT = @@ -3190,1181 +3584,1934 @@ public final class Settings { * If this number is reached, the watchdog will no longer monitor the * initial connection state for the network. This is a safeguard for * networks containing multiple APs whose DNS does not respond to pings. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = "wifi_watchdog_max_ap_checks"; /** - * Whether the Wi-Fi watchdog is enabled. + * @deprecated Use {@link android.provider.Settings.Global#WIFI_WATCHDOG_ON} instead */ + @Deprecated public static final String WIFI_WATCHDOG_ON = "wifi_watchdog_on"; /** * A comma-separated list of SSIDs for which the Wi-Fi watchdog should be enabled. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_WATCH_LIST = "wifi_watchdog_watch_list"; /** * The number of pings to test if an access point is a good connection. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_PING_COUNT = "wifi_watchdog_ping_count"; /** * The delay between pings. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_PING_DELAY_MS = "wifi_watchdog_ping_delay_ms"; /** * The timeout per ping. + * @deprecated This setting is not used. */ @Deprecated public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS = "wifi_watchdog_ping_timeout_ms"; /** - * ms delay before rechecking an 'online' wifi connection when it is thought to be unstable. - * @hide + * @deprecated Use + * {@link android.provider.Settings.Global#WIFI_MAX_DHCP_RETRY_COUNT} instead */ - public static final String WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS = - "wifi_watchdog_arp_interval_ms"; + @Deprecated + public static final String WIFI_MAX_DHCP_RETRY_COUNT = Global.WIFI_MAX_DHCP_RETRY_COUNT; /** - * ms delay interval between rssi polling when the signal is known to be weak - * @hide + * @deprecated Use + * {@link android.provider.Settings.Global#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS} instead */ - public static final String WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS = - "wifi_watchdog_rssi_fetch_interval_ms"; - + @Deprecated + public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = + Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS; /** - * ms delay before rechecking a connect SSID for walled garden with a http download. - * @hide + * Whether background data usage is allowed. + * + * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, + * availability of background data depends on several + * combined factors. When background data is unavailable, + * {@link ConnectivityManager#getActiveNetworkInfo()} will + * now appear disconnected. */ - public static final String WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS = - "wifi_watchdog_walled_garden_interval_ms"; + @Deprecated + public static final String BACKGROUND_DATA = "background_data"; /** - * Number of ARP pings per check. - * @hide + * Origins for which browsers should allow geolocation by default. + * The value is a space-separated list of origins. */ - public static final String WIFI_WATCHDOG_NUM_ARP_PINGS = "wifi_watchdog_num_arp_pings"; + public static final String ALLOWED_GEOLOCATION_ORIGINS + = "allowed_geolocation_origins"; /** - * Minimum number of responses to the arp pings to consider the test 'successful'. + * The preferred TTY mode 0 = TTy Off, CDMA default + * 1 = TTY Full + * 2 = TTY HCO + * 3 = TTY VCO * @hide */ - public static final String WIFI_WATCHDOG_MIN_ARP_RESPONSES = - "wifi_watchdog_min_arp_responses"; + public static final String PREFERRED_TTY_MODE = + "preferred_tty_mode"; /** - * Timeout on ARP pings + * Whether the enhanced voice privacy mode is enabled. + * 0 = normal voice privacy + * 1 = enhanced voice privacy * @hide */ - public static final String WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS = - "wifi_watchdog_arp_ping_timeout_ms"; + public static final String ENHANCED_VOICE_PRIVACY_ENABLED = "enhanced_voice_privacy_enabled"; /** - * Setting to turn off poor network avoidance on Wi-Fi. Feature is enabled by default and - * the setting needs to be set to 0 to disable it. + * Whether the TTY mode mode is enabled. + * 0 = disabled + * 1 = enabled * @hide */ - public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED = - "wifi_watchdog_poor_network_test_enabled"; + public static final String TTY_MODE_ENABLED = "tty_mode_enabled"; /** - * Setting to turn off walled garden test on Wi-Fi. Feature is enabled by default and - * the setting needs to be set to 0 to disable it. + * Controls whether settings backup is enabled. + * Type: int ( 0 = disabled, 1 = enabled ) * @hide */ - public static final String WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED = - "wifi_watchdog_walled_garden_test_enabled"; + public static final String BACKUP_ENABLED = "backup_enabled"; /** - * The URL used for walled garden check upon a new conection. WifiWatchdogService - * fetches the URL and checks to see if {@link #WIFI_WATCHDOG_WALLED_GARDEN_PATTERN} - * is not part of the title string to notify the user on the presence of a walled garden. + * Controls whether application data is automatically restored from backup + * at install time. + * Type: int ( 0 = disabled, 1 = enabled ) * @hide */ - public static final String WIFI_WATCHDOG_WALLED_GARDEN_URL = - "wifi_watchdog_walled_garden_url"; + public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore"; /** - * The maximum number of times we will retry a connection to an access - * point for which we have failed in acquiring an IP address from DHCP. - * A value of N means that we will make N+1 connection attempts in all. + * Indicates whether settings backup has been fully provisioned. + * Type: int ( 0 = unprovisioned, 1 = fully provisioned ) + * @hide */ - public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count"; + public static final String BACKUP_PROVISIONED = "backup_provisioned"; /** - * The operational wifi frequency band - * Set to one of {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO}, - * {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ} or - * {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ} - * + * Component of the transport to use for backup/restore. * @hide */ - public static final String WIFI_FREQUENCY_BAND = "wifi_frequency_band"; + public static final String BACKUP_TRANSPORT = "backup_transport"; /** - * The Wi-Fi peer-to-peer device name + * Version for which the setup wizard was last shown. Bumped for + * each release when there is new setup information to show. * @hide */ - public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name"; + public static final String LAST_SETUP_SHOWN = "last_setup_shown"; /** - * Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile - * data connectivity to be established after a disconnect from Wi-Fi. + * The interval in milliseconds after which Wi-Fi is considered idle. + * When idle, it is possible for the device to be switched from Wi-Fi to + * the mobile data network. + * @hide + * @deprecated Use {@link android.provider.Settings.Global#WIFI_IDLE_MS} + * instead. */ - public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = - "wifi_mobile_data_transition_wakelock_timeout_ms"; + @Deprecated + public static final String WIFI_IDLE_MS = Global.WIFI_IDLE_MS; /** - * Whether network service discovery is enabled. + * The global search provider chosen by the user (if multiple global + * search providers are installed). This will be the provider returned + * by {@link SearchManager#getGlobalSearchActivity()} if it's still + * installed. This setting is stored as a flattened component name as + * per {@link ComponentName#flattenToString()}. + * * @hide */ - public static final String NSD_ON = "nsd_on"; + public static final String SEARCH_GLOBAL_SEARCH_ACTIVITY = + "search_global_search_activity"; /** - * Whether background data usage is allowed by the user. See - * ConnectivityManager for more info. + * The number of promoted sources in GlobalSearch. + * @hide */ - @Deprecated - public static final String BACKGROUND_DATA = "background_data"; - + public static final String SEARCH_NUM_PROMOTED_SOURCES = "search_num_promoted_sources"; /** - * Origins for which browsers should allow geolocation by default. - * The value is a space-separated list of origins. + * The maximum number of suggestions returned by GlobalSearch. + * @hide */ - public static final String ALLOWED_GEOLOCATION_ORIGINS - = "allowed_geolocation_origins"; - + public static final String SEARCH_MAX_RESULTS_TO_DISPLAY = "search_max_results_to_display"; /** - * Whether mobile data connections are allowed by the user. See - * ConnectivityManager for more info. + * The number of suggestions GlobalSearch will ask each non-web search source for. * @hide */ - public static final String MOBILE_DATA = "mobile_data"; - + public static final String SEARCH_MAX_RESULTS_PER_SOURCE = "search_max_results_per_source"; /** - * The CDMA roaming mode 0 = Home Networks, CDMA default - * 1 = Roaming on Affiliated networks - * 2 = Roaming on any networks + * The number of suggestions the GlobalSearch will ask the web search source for. * @hide */ - public static final String CDMA_ROAMING_MODE = "roaming_settings"; - + public static final String SEARCH_WEB_RESULTS_OVERRIDE_LIMIT = + "search_web_results_override_limit"; /** - * The CDMA subscription mode 0 = RUIM/SIM (default) - * 1 = NV + * The number of milliseconds that GlobalSearch will wait for suggestions from + * promoted sources before continuing with all other sources. * @hide */ - public static final String CDMA_SUBSCRIPTION_MODE = "subscription_mode"; - + public static final String SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS = + "search_promoted_source_deadline_millis"; /** - * The preferred network mode 7 = Global - * 6 = EvDo only - * 5 = CDMA w/o EvDo - * 4 = CDMA / EvDo auto - * 3 = GSM / WCDMA auto - * 2 = WCDMA only - * 1 = GSM only - * 0 = GSM / WCDMA preferred + * The number of milliseconds before GlobalSearch aborts search suggesiton queries. * @hide */ - public static final String PREFERRED_NETWORK_MODE = - "preferred_network_mode"; - + public static final String SEARCH_SOURCE_TIMEOUT_MILLIS = "search_source_timeout_millis"; /** - * The preferred TTY mode 0 = TTy Off, CDMA default - * 1 = TTY Full - * 2 = TTY HCO - * 3 = TTY VCO + * The maximum number of milliseconds that GlobalSearch shows the previous results + * after receiving a new query. * @hide */ - public static final String PREFERRED_TTY_MODE = - "preferred_tty_mode"; - - + public static final String SEARCH_PREFILL_MILLIS = "search_prefill_millis"; /** - * CDMA Cell Broadcast SMS - * 0 = CDMA Cell Broadcast SMS disabled - * 1 = CDMA Cell Broadcast SMS enabled + * The maximum age of log data used for shortcuts in GlobalSearch. * @hide */ - public static final String CDMA_CELL_BROADCAST_SMS = - "cdma_cell_broadcast_sms"; - + public static final String SEARCH_MAX_STAT_AGE_MILLIS = "search_max_stat_age_millis"; /** - * The cdma subscription 0 = Subscription from RUIM, when available - * 1 = Subscription from NV + * The maximum age of log data used for source ranking in GlobalSearch. * @hide */ - public static final String PREFERRED_CDMA_SUBSCRIPTION = - "preferred_cdma_subscription"; - + public static final String SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS = + "search_max_source_event_age_millis"; /** - * Whether the enhanced voice privacy mode is enabled. - * 0 = normal voice privacy - * 1 = enhanced voice privacy + * The minimum number of impressions needed to rank a source in GlobalSearch. * @hide */ - public static final String ENHANCED_VOICE_PRIVACY_ENABLED = "enhanced_voice_privacy_enabled"; - + public static final String SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING = + "search_min_impressions_for_source_ranking"; /** - * Whether the TTY mode mode is enabled. - * 0 = disabled - * 1 = enabled + * The minimum number of clicks needed to rank a source in GlobalSearch. * @hide */ - public static final String TTY_MODE_ENABLED = "tty_mode_enabled"; - + public static final String SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING = + "search_min_clicks_for_source_ranking"; /** - * The number of milliseconds to delay before sending out Connectivyt Change broadcasts + * The maximum number of shortcuts shown by GlobalSearch. * @hide */ - public static final String CONNECTIVITY_CHANGE_DELAY = "connectivity_change_delay"; - + public static final String SEARCH_MAX_SHORTCUTS_RETURNED = "search_max_shortcuts_returned"; /** - * Default value for CONNECTIVITY_CHANGE_DELAY in milliseconds. + * The size of the core thread pool for suggestion queries in GlobalSearch. * @hide */ - public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000; - + public static final String SEARCH_QUERY_THREAD_CORE_POOL_SIZE = + "search_query_thread_core_pool_size"; /** - * Controls whether settings backup is enabled. - * Type: int ( 0 = disabled, 1 = enabled ) + * The maximum size of the thread pool for suggestion queries in GlobalSearch. * @hide */ - public static final String BACKUP_ENABLED = "backup_enabled"; - + public static final String SEARCH_QUERY_THREAD_MAX_POOL_SIZE = + "search_query_thread_max_pool_size"; /** - * Controls whether application data is automatically restored from backup - * at install time. - * Type: int ( 0 = disabled, 1 = enabled ) + * The size of the core thread pool for shortcut refreshing in GlobalSearch. * @hide */ - public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore"; - + public static final String SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE = + "search_shortcut_refresh_core_pool_size"; /** - * Indicates whether settings backup has been fully provisioned. - * Type: int ( 0 = unprovisioned, 1 = fully provisioned ) + * The maximum size of the thread pool for shortcut refreshing in GlobalSearch. * @hide */ - public static final String BACKUP_PROVISIONED = "backup_provisioned"; - + public static final String SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE = + "search_shortcut_refresh_max_pool_size"; /** - * Component of the transport to use for backup/restore. + * The maximun time that excess threads in the GlobalSeach thread pools will + * wait before terminating. * @hide */ - public static final String BACKUP_TRANSPORT = "backup_transport"; - + public static final String SEARCH_THREAD_KEEPALIVE_SECONDS = + "search_thread_keepalive_seconds"; /** - * Version for which the setup wizard was last shown. Bumped for - * each release when there is new setup information to show. + * The maximum number of concurrent suggestion queries to each source. * @hide */ - public static final String LAST_SETUP_SHOWN = "last_setup_shown"; + public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT = + "search_per_source_concurrent_query_limit"; /** - * How frequently (in seconds) to check the memory status of the - * device. + * Whether or not alert sounds are played on MountService events. (0 = false, 1 = true) * @hide */ - public static final String MEMCHECK_INTERVAL = "memcheck_interval"; + public static final String MOUNT_PLAY_NOTIFICATION_SND = "mount_play_not_snd"; /** - * Max frequency (in seconds) to log memory check stats, in realtime - * seconds. This allows for throttling of logs when the device is - * running for large amounts of time. + * Whether or not UMS auto-starts on UMS host detection. (0 = false, 1 = true) * @hide */ - public static final String MEMCHECK_LOG_REALTIME_INTERVAL = - "memcheck_log_realtime_interval"; + public static final String MOUNT_UMS_AUTOSTART = "mount_ums_autostart"; /** - * Boolean indicating whether rebooting due to system memory checks - * is enabled. + * Whether or not a notification is displayed on UMS host detection. (0 = false, 1 = true) * @hide */ - public static final String MEMCHECK_SYSTEM_ENABLED = "memcheck_system_enabled"; + public static final String MOUNT_UMS_PROMPT = "mount_ums_prompt"; /** - * How many bytes the system process must be below to avoid scheduling - * a soft reboot. This reboot will happen when it is next determined - * to be a good time. + * Whether or not a notification is displayed while UMS is enabled. (0 = false, 1 = true) * @hide */ - public static final String MEMCHECK_SYSTEM_SOFT_THRESHOLD = "memcheck_system_soft"; + public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled"; /** - * How many bytes the system process must be below to avoid scheduling - * a hard reboot. This reboot will happen immediately. + * If nonzero, ANRs in invisible background processes bring up a dialog. + * Otherwise, the process will be silently killed. * @hide */ - public static final String MEMCHECK_SYSTEM_HARD_THRESHOLD = "memcheck_system_hard"; + public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; /** - * How many bytes the phone process must be below to avoid scheduling - * a soft restart. This restart will happen when it is next determined - * to be a good time. + * The {@link ComponentName} string of the service to be used as the voice recognition + * service. + * * @hide */ - public static final String MEMCHECK_PHONE_SOFT_THRESHOLD = "memcheck_phone_soft"; + public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service"; + /** - * How many bytes the phone process must be below to avoid scheduling - * a hard restart. This restart will happen immediately. + * The {@link ComponentName} string of the selected spell checker service which is + * one of the services managed by the text service manager. + * * @hide */ - public static final String MEMCHECK_PHONE_HARD_THRESHOLD = "memcheck_phone_hard"; + public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker"; /** - * Boolean indicating whether restarting the phone process due to - * memory checks is enabled. + * The {@link ComponentName} string of the selected subtype of the selected spell checker + * service which is one of the services managed by the text service manager. + * * @hide */ - public static final String MEMCHECK_PHONE_ENABLED = "memcheck_phone_enabled"; + public static final String SELECTED_SPELL_CHECKER_SUBTYPE = + "selected_spell_checker_subtype"; /** - * First time during the day it is okay to kill processes - * or reboot the device due to low memory situations. This number is - * in seconds since midnight. + * The {@link ComponentName} string whether spell checker is enabled or not. + * * @hide */ - public static final String MEMCHECK_EXEC_START_TIME = "memcheck_exec_start_time"; + public static final String SPELL_CHECKER_ENABLED = "spell_checker_enabled"; /** - * Last time during the day it is okay to kill processes - * or reboot the device due to low memory situations. This number is - * in seconds since midnight. + * What happens when the user presses the Power button while in-call + * and the screen is on.<br/> + * <b>Values:</b><br/> + * 1 - The Power button turns off the screen and locks the device. (Default behavior)<br/> + * 2 - The Power button hangs up the current call.<br/> + * * @hide */ - public static final String MEMCHECK_EXEC_END_TIME = "memcheck_exec_end_time"; + public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior"; /** - * How long the screen must have been off in order to kill processes - * or reboot. This number is in seconds. A value of -1 means to - * entirely disregard whether the screen is on. + * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen". * @hide */ - public static final String MEMCHECK_MIN_SCREEN_OFF = "memcheck_min_screen_off"; + public static final int INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF = 0x1; /** - * How much time there must be until the next alarm in order to kill processes - * or reboot. This number is in seconds. Note: this value must be - * smaller than {@link #MEMCHECK_RECHECK_INTERVAL} or else it will - * always see an alarm scheduled within its time. + * INCALL_POWER_BUTTON_BEHAVIOR value for "hang up". * @hide */ - public static final String MEMCHECK_MIN_ALARM = "memcheck_min_alarm"; + public static final int INCALL_POWER_BUTTON_BEHAVIOR_HANGUP = 0x2; /** - * How frequently to check whether it is a good time to restart things, - * if the device is in a bad state. This number is in seconds. Note: - * this value must be larger than {@link #MEMCHECK_MIN_ALARM} or else - * the alarm to schedule the recheck will always appear within the - * minimum "do not execute now" time. + * INCALL_POWER_BUTTON_BEHAVIOR default value. * @hide */ - public static final String MEMCHECK_RECHECK_INTERVAL = "memcheck_recheck_interval"; + public static final int INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT = + INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF; /** - * How frequently (in DAYS) to reboot the device. If 0, no reboots - * will occur. + * The current night mode that has been selected by the user. Owned + * and controlled by UiModeManagerService. Constants are as per + * UiModeManager. * @hide */ - public static final String REBOOT_INTERVAL = "reboot_interval"; + public static final String UI_NIGHT_MODE = "ui_night_mode"; /** - * First time during the day it is okay to force a reboot of the - * device (if REBOOT_INTERVAL is set). This number is - * in seconds since midnight. + * Whether screensavers are enabled. * @hide */ - public static final String REBOOT_START_TIME = "reboot_start_time"; + public static final String SCREENSAVER_ENABLED = "screensaver_enabled"; /** - * The window of time (in seconds) after each REBOOT_INTERVAL in which - * a reboot can be executed. If 0, a reboot will always be executed at - * exactly the given time. Otherwise, it will only be executed if - * the device is idle within the window. + * The user's chosen screensaver components. + * + * These will be launched by the PhoneWindowManager after a timeout when not on + * battery, or upon dock insertion (if SCREENSAVER_ACTIVATE_ON_DOCK is set to 1). * @hide */ - public static final String REBOOT_WINDOW = "reboot_window"; + public static final String SCREENSAVER_COMPONENTS = "screensaver_components"; /** - * Threshold values for the duration and level of a discharge cycle, under - * which we log discharge cycle info. + * If screensavers are enabled, whether the screensaver should be automatically launched + * when the device is inserted into a (desk) dock. * @hide */ - public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD = - "battery_discharge_duration_threshold"; - /** @hide */ - public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold"; + public static final String SCREENSAVER_ACTIVATE_ON_DOCK = "screensaver_activate_on_dock"; /** - * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents - * on application crashes and ANRs. If this is disabled, the crash/ANR dialog - * will never display the "Report" button. - * Type: int ( 0 = disallow, 1 = allow ) + * If screensavers are enabled, whether the screensaver should be automatically launched + * when the screen times out when not on battery. * @hide */ - public static final String SEND_ACTION_APP_ERROR = "send_action_app_error"; + public static final String SCREENSAVER_ACTIVATE_ON_SLEEP = "screensaver_activate_on_sleep"; /** - * Nonzero causes Log.wtf() to crash. + * If screensavers are enabled, the default screensaver component. * @hide */ - public static final String WTF_IS_FATAL = "wtf_is_fatal"; + public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component"; /** - * Maximum age of entries kept by {@link com.android.internal.os.IDropBoxManagerService}. + * This are the settings to be backed up. + * + * NOTE: Settings are backed up and restored in the order they appear + * in this array. If you have one setting depending on another, + * make sure that they are ordered appropriately. + * * @hide */ - public static final String DROPBOX_AGE_SECONDS = - "dropbox_age_seconds"; + public static final String[] SETTINGS_TO_BACKUP = { + ADB_ENABLED, + BUGREPORT_IN_POWER_MENU, + ALLOW_MOCK_LOCATION, + PARENTAL_CONTROL_ENABLED, + PARENTAL_CONTROL_REDIRECT_URL, + USB_MASS_STORAGE_ENABLED, + ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, + ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, + ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, + ACCESSIBILITY_SCRIPT_INJECTION, + BACKUP_AUTO_RESTORE, + ENABLED_ACCESSIBILITY_SERVICES, + TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + TOUCH_EXPLORATION_ENABLED, + ACCESSIBILITY_ENABLED, + ACCESSIBILITY_SPEAK_PASSWORD, + TTS_USE_DEFAULTS, + TTS_DEFAULT_RATE, + TTS_DEFAULT_PITCH, + TTS_DEFAULT_SYNTH, + TTS_DEFAULT_LANG, + TTS_DEFAULT_COUNTRY, + TTS_ENABLED_PLUGINS, + TTS_DEFAULT_LOCALE, + WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, + WIFI_NUM_OPEN_NETWORKS_KEPT, + MOUNT_PLAY_NOTIFICATION_SND, + MOUNT_UMS_AUTOSTART, + MOUNT_UMS_PROMPT, + MOUNT_UMS_NOTIFY_ENABLED, + UI_NIGHT_MODE, + LOCK_SCREEN_OWNER_INFO, + LOCK_SCREEN_OWNER_INFO_ENABLED + }; + /** - * Maximum number of entry files which {@link com.android.internal.os.IDropBoxManagerService} will keep around. - * @hide + * Helper method for determining if a location provider is enabled. + * @param cr the content resolver to use + * @param provider the location provider to query + * @return true if the provider is enabled */ - public static final String DROPBOX_MAX_FILES = - "dropbox_max_files"; + public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) { + String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED); + return TextUtils.delimitedStringContains(allowedProviders, ',', provider); + } + /** - * Maximum amount of disk space used by {@link com.android.internal.os.IDropBoxManagerService} no matter what. - * @hide + * Thread-safe method for enabling or disabling a single location provider. + * @param cr the content resolver to use + * @param provider the location provider to enable or disable + * @param enabled true if the provider should be enabled */ - public static final String DROPBOX_QUOTA_KB = - "dropbox_quota_kb"; + public static final void setLocationProviderEnabled(ContentResolver cr, + String provider, boolean enabled) { + // to ensure thread safety, we write the provider name with a '+' or '-' + // and let the SettingsProvider handle it rather than reading and modifying + // the list of enabled providers. + if (enabled) { + provider = "+" + provider; + } else { + provider = "-" + provider; + } + putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider); + } + } + + /** + * Global system settings, containing preferences that always apply identically + * to all defined users. Applications can read these but are not allowed to write; + * like the "Secure" settings, these are for preferences that the user must + * explicitly modify through the system UI or specialized APIs for those values. + */ + public static final class Global extends NameValueTable { + public static final String SYS_PROP_SETTING_VERSION = "sys.settings_global_version"; + /** - * Percent of free disk (excluding reserve) which {@link com.android.internal.os.IDropBoxManagerService} will use. - * @hide + * The content:// style URL for global secure settings items. Not public. */ - public static final String DROPBOX_QUOTA_PERCENT = - "dropbox_quota_percent"; + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global"); + /** - * Percent of total disk which {@link com.android.internal.os.IDropBoxManagerService} will never dip into. + * Setting whether the global gesture for enabling accessibility is enabled. + * If this gesture is enabled the user will be able to perfrom it to enable + * the accessibility state without visiting the settings app. * @hide */ - public static final String DROPBOX_RESERVE_PERCENT = - "dropbox_reserve_percent"; + public static final String ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED = + "enable_accessibility_global_gesture_enabled"; + /** - * Prefix for per-tag dropbox disable/enable settings. - * @hide + * Whether Airplane Mode is on. */ - public static final String DROPBOX_TAG_PREFIX = - "dropbox:"; + public static final String AIRPLANE_MODE_ON = "airplane_mode_on"; + /** - * Lines of logcat to include with system crash/ANR/etc. reports, - * as a prefix of the dropbox tag of the report type. - * For example, "logcat_for_system_server_anr" controls the lines - * of logcat captured with system server ANR reports. 0 to disable. - * @hide + * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio. */ - public static final String ERROR_LOGCAT_PREFIX = - "logcat_for_"; - + public static final String RADIO_BLUETOOTH = "bluetooth"; /** - * Screen timeout in milliseconds corresponding to the - * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest - * possible screen timeout behavior.) - * @hide + * Constant for use in AIRPLANE_MODE_RADIOS to specify Wi-Fi radio. */ - public static final String SHORT_KEYLIGHT_DELAY_MS = - "short_keylight_delay_ms"; + public static final String RADIO_WIFI = "wifi"; /** - * The interval in minutes after which the amount of free storage left on the - * device is logged to the event log - * @hide + * {@hide} */ - public static final String SYS_FREE_STORAGE_LOG_INTERVAL = - "sys_free_storage_log_interval"; - + public static final String RADIO_WIMAX = "wimax"; /** - * Threshold for the amount of change in disk free space required to report the amount of - * free space. Used to prevent spamming the logs when the disk free space isn't changing - * frequently. - * @hide + * Constant for use in AIRPLANE_MODE_RADIOS to specify Cellular radio. */ - public static final String DISK_FREE_CHANGE_REPORTING_THRESHOLD = - "disk_free_change_reporting_threshold"; - + public static final String RADIO_CELL = "cell"; /** - * Minimum percentage of free storage on the device that is used to determine if - * the device is running low on storage. The default is 10. - * <p>Say this value is set to 10, the device is considered running low on storage - * if 90% or more of the device storage is filled up. - * @hide + * Constant for use in AIRPLANE_MODE_RADIOS to specify NFC radio. */ - public static final String SYS_STORAGE_THRESHOLD_PERCENTAGE = - "sys_storage_threshold_percentage"; + public static final String RADIO_NFC = "nfc"; /** - * Maximum byte size of the low storage threshold. This is to ensure - * that {@link #SYS_STORAGE_THRESHOLD_PERCENTAGE} does not result in - * an overly large threshold for large storage devices. Currently this - * must be less than 2GB. This default is 500MB. - * @hide + * A comma separated list of radios that need to be disabled when airplane mode + * is on. This overrides WIFI_ON and BLUETOOTH_ON, if Wi-Fi and bluetooth are + * included in the comma separated list. */ - public static final String SYS_STORAGE_THRESHOLD_MAX_BYTES = - "sys_storage_threshold_max_bytes"; + public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios"; /** - * Minimum bytes of free storage on the device before the data - * partition is considered full. By default, 1 MB is reserved - * to avoid system-wide SQLite disk full exceptions. - * @hide + * A comma separated list of radios that should to be disabled when airplane mode + * is on, but can be manually reenabled by the user. For example, if RADIO_WIFI is + * added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi + * will be turned off when entering airplane mode, but the user will be able to reenable + * Wifi in the Settings app. + * + * {@hide} */ - public static final String SYS_STORAGE_FULL_THRESHOLD_BYTES = - "sys_storage_full_threshold_bytes"; + public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios"; /** - * The interval in milliseconds after which Wi-Fi is considered idle. - * When idle, it is possible for the device to be switched from Wi-Fi to - * the mobile data network. - * @hide + * The policy for deciding when Wi-Fi should go to sleep (which will in + * turn switch to using the mobile data as an Internet connection). + * <p> + * Set to one of {@link #WIFI_SLEEP_POLICY_DEFAULT}, + * {@link #WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED}, or + * {@link #WIFI_SLEEP_POLICY_NEVER}. */ - public static final String WIFI_IDLE_MS = "wifi_idle_ms"; + public static final String WIFI_SLEEP_POLICY = "wifi_sleep_policy"; /** - * The interval in milliseconds to issue wake up scans when wifi needs - * to connect. This is necessary to connect to an access point when - * device is on the move and the screen is off. - * @hide + * Value for {@link #WIFI_SLEEP_POLICY} to use the default Wi-Fi sleep + * policy, which is to sleep shortly after the turning off + * according to the {@link #STAY_ON_WHILE_PLUGGED_IN} setting. */ - public static final String WIFI_FRAMEWORK_SCAN_INTERVAL_MS = - "wifi_framework_scan_interval_ms"; + public static final int WIFI_SLEEP_POLICY_DEFAULT = 0; /** - * The interval in milliseconds to scan as used by the wifi supplicant - * @hide + * Value for {@link #WIFI_SLEEP_POLICY} to use the default policy when + * the device is on battery, and never go to sleep when the device is + * plugged in. */ - public static final String WIFI_SUPPLICANT_SCAN_INTERVAL_MS = - "wifi_supplicant_scan_interval_ms"; + public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED = 1; /** - * The interval in milliseconds at which to check packet counts on the - * mobile data interface when screen is on, to detect possible data - * connection problems. - * @hide + * Value for {@link #WIFI_SLEEP_POLICY} to never go to sleep. */ - public static final String PDP_WATCHDOG_POLL_INTERVAL_MS = - "pdp_watchdog_poll_interval_ms"; + public static final int WIFI_SLEEP_POLICY_NEVER = 2; /** - * The interval in milliseconds at which to check packet counts on the - * mobile data interface when screen is off, to detect possible data - * connection problems. - * @hide + * Value to specify if the user prefers the date, time and time zone + * to be automatically fetched from the network (NITZ). 1=yes, 0=no */ - public static final String PDP_WATCHDOG_LONG_POLL_INTERVAL_MS = - "pdp_watchdog_long_poll_interval_ms"; + public static final String AUTO_TIME = "auto_time"; /** - * The interval in milliseconds at which to check packet counts on the - * mobile data interface after {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} - * outgoing packets has been reached without incoming packets. - * @hide + * Value to specify if the user prefers the time zone + * to be automatically fetched from the network (NITZ). 1=yes, 0=no */ - public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS = - "pdp_watchdog_error_poll_interval_ms"; + public static final String AUTO_TIME_ZONE = "auto_time_zone"; /** - * The number of outgoing packets sent without seeing an incoming packet - * that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT} - * device is logged to the event log + * URI for the car dock "in" event sound. * @hide */ - public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT = - "pdp_watchdog_trigger_packet_count"; + public static final String CAR_DOCK_SOUND = "car_dock_sound"; /** - * The number of polls to perform (at {@link #PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS}) - * after hitting {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} before - * attempting data connection recovery. + * URI for the car dock "out" event sound. * @hide */ - public static final String PDP_WATCHDOG_ERROR_POLL_COUNT = - "pdp_watchdog_error_poll_count"; + public static final String CAR_UNDOCK_SOUND = "car_undock_sound"; /** - * The number of failed PDP reset attempts before moving to something more - * drastic: re-registering to the network. + * URI for the desk dock "in" event sound. * @hide */ - public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT = - "pdp_watchdog_max_pdp_reset_fail_count"; + public static final String DESK_DOCK_SOUND = "desk_dock_sound"; /** - * The number of milliseconds to delay when checking for data stalls during - * non-aggressive detection. (screen is turned off.) + * URI for the desk dock "out" event sound. * @hide */ - public static final String DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS = - "data_stall_alarm_non_aggressive_delay_in_ms"; + public static final String DESK_UNDOCK_SOUND = "desk_undock_sound"; /** - * The number of milliseconds to delay when checking for data stalls during - * aggressive detection. (screen on or suspected data stall) + * Whether to play a sound for dock events. * @hide */ - public static final String DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS = - "data_stall_alarm_aggressive_delay_in_ms"; + public static final String DOCK_SOUNDS_ENABLED = "dock_sounds_enabled"; /** - * The interval in milliseconds at which to check gprs registration - * after the first registration mismatch of gprs and voice service, - * to detect possible data network registration problems. - * + * URI for the "device locked" (keyguard shown) sound. * @hide */ - public static final String GPRS_REGISTER_CHECK_PERIOD_MS = - "gprs_register_check_period_ms"; + public static final String LOCK_SOUND = "lock_sound"; /** - * The length of time in milli-seconds that automatic small adjustments to - * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded. + * URI for the "device unlocked" sound. * @hide */ - public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing"; + public static final String UNLOCK_SOUND = "unlock_sound"; /** - * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment - * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been - * exceeded. + * URI for the low battery sound file. * @hide */ - public static final String NITZ_UPDATE_DIFF = "nitz_update_diff"; + public static final String LOW_BATTERY_SOUND = "low_battery_sound"; /** - * The maximum reconnect delay for short network outages or when the network is suspended - * due to phone use. + * Whether to play a sound for low-battery alerts. * @hide */ - public static final String SYNC_MAX_RETRY_DELAY_IN_SECONDS = - "sync_max_retry_delay_in_seconds"; + public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled"; /** - * The interval in milliseconds at which to check the number of SMS sent - * out without asking for use permit, to limit the un-authorized SMS - * usage. - * @hide + * Whether we keep the device on while the device is plugged in. + * Supported values are: + * <ul> + * <li>{@code 0} to never stay on while plugged in</li> + * <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li> + * <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li> + * <li>{@link BatteryManager#BATTERY_PLUGGED_WIRELESS} to stay on for wireless charger</li> + * </ul> + * These values can be OR-ed together. */ - public static final String SMS_OUTGOING_CHECK_INTERVAL_MS = - "sms_outgoing_check_interval_ms"; + public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in"; /** - * The number of outgoing SMS sent without asking for user permit - * (of {@link #SMS_OUTGOING_CHECK_INTERVAL_MS} - * @hide + * Whether ADB is enabled. */ - public static final String SMS_OUTGOING_CHECK_MAX_COUNT = - "sms_outgoing_check_max_count"; + public static final String ADB_ENABLED = "adb_enabled"; /** - * The global search provider chosen by the user (if multiple global - * search providers are installed). This will be the provider returned - * by {@link SearchManager#getGlobalSearchActivity()} if it's still - * installed. This setting is stored as a flattened component name as - * per {@link ComponentName#flattenToString()}. - * + * Whether assisted GPS should be enabled or not. * @hide */ - public static final String SEARCH_GLOBAL_SEARCH_ACTIVITY = - "search_global_search_activity"; + public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled"; /** - * The number of promoted sources in GlobalSearch. - * @hide + * Whether bluetooth is enabled/disabled + * 0=disabled. 1=enabled. */ - public static final String SEARCH_NUM_PROMOTED_SOURCES = "search_num_promoted_sources"; + public static final String BLUETOOTH_ON = "bluetooth_on"; + /** - * The maximum number of suggestions returned by GlobalSearch. + * CDMA Cell Broadcast SMS + * 0 = CDMA Cell Broadcast SMS disabled + * 1 = CDMA Cell Broadcast SMS enabled * @hide */ - public static final String SEARCH_MAX_RESULTS_TO_DISPLAY = "search_max_results_to_display"; + public static final String CDMA_CELL_BROADCAST_SMS = + "cdma_cell_broadcast_sms"; + /** - * The number of suggestions GlobalSearch will ask each non-web search source for. + * The CDMA roaming mode 0 = Home Networks, CDMA default + * 1 = Roaming on Affiliated networks + * 2 = Roaming on any networks * @hide */ - public static final String SEARCH_MAX_RESULTS_PER_SOURCE = "search_max_results_per_source"; + public static final String CDMA_ROAMING_MODE = "roaming_settings"; + /** - * The number of suggestions the GlobalSearch will ask the web search source for. + * The CDMA subscription mode 0 = RUIM/SIM (default) + * 1 = NV * @hide */ - public static final String SEARCH_WEB_RESULTS_OVERRIDE_LIMIT = - "search_web_results_override_limit"; + public static final String CDMA_SUBSCRIPTION_MODE = "subscription_mode"; + + /** Inactivity timeout to track mobile data activity. + * + * If set to a positive integer, it indicates the inactivity timeout value in seconds to + * infer the data activity of mobile network. After a period of no activity on mobile + * networks with length specified by the timeout, an {@code ACTION_DATA_ACTIVITY_CHANGE} + * intent is fired to indicate a transition of network status from "active" to "idle". Any + * subsequent activity on mobile networks triggers the firing of {@code + * ACTION_DATA_ACTIVITY_CHANGE} intent indicating transition from "idle" to "active". + * + * Network activity refers to transmitting or receiving data on the network interfaces. + * + * Tracking is disabled if set to zero or negative value. + * + * @hide + */ + public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile"; + + /** Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE} + * but for Wifi network. + * @hide + */ + public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi"; + + /** + * Whether or not data roaming is enabled. (0 = false, 1 = true) + */ + public static final String DATA_ROAMING = "data_roaming"; + + /** + * Whether user has enabled development settings. + */ + public static final String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled"; + + /** + * Whether the device has been provisioned (0 = false, 1 = true) + */ + public static final String DEVICE_PROVISIONED = "device_provisioned"; + + /** + * The saved value for WindowManagerService.setForcedDisplayDensity(). + * One integer in dpi. If unset, then use the real display density. + * @hide + */ + public static final String DISPLAY_DENSITY_FORCED = "display_density_forced"; + + /** + * The saved value for WindowManagerService.setForcedDisplaySize(). + * Two integers separated by a comma. If unset, then use the real display size. + * @hide + */ + public static final String DISPLAY_SIZE_FORCED = "display_size_forced"; + + /** + * The maximum size, in bytes, of a download that the download manager will transfer over + * a non-wifi connection. + * @hide + */ + public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE = + "download_manager_max_bytes_over_mobile"; + + /** + * The recommended maximum size, in bytes, of a download that the download manager should + * transfer over a non-wifi connection. Over this size, the use will be warned, but will + * have the option to start the download over the mobile connection anyway. + * @hide + */ + public static final String DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE = + "download_manager_recommended_max_bytes_over_mobile"; + + /** + * Whether the package installer should allow installation of apps downloaded from + * sources other than Google Play. + * + * 1 = allow installing from other sources + * 0 = only allow installing from Google Play + */ + public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; + + /** + * Whether mobile data connections are allowed by the user. See + * ConnectivityManager for more info. + * @hide + */ + public static final String MOBILE_DATA = "mobile_data"; + + /** {@hide} */ + public static final String NETSTATS_ENABLED = "netstats_enabled"; + /** {@hide} */ + public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval"; + /** {@hide} */ + public static final String NETSTATS_TIME_CACHE_MAX_AGE = "netstats_time_cache_max_age"; + /** {@hide} */ + public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes"; + /** {@hide} */ + public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled"; + /** {@hide} */ + public static final String NETSTATS_REPORT_XT_OVER_DEV = "netstats_report_xt_over_dev"; + + /** {@hide} */ + public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration"; + /** {@hide} */ + public static final String NETSTATS_DEV_PERSIST_BYTES = "netstats_dev_persist_bytes"; + /** {@hide} */ + public static final String NETSTATS_DEV_ROTATE_AGE = "netstats_dev_rotate_age"; + /** {@hide} */ + public static final String NETSTATS_DEV_DELETE_AGE = "netstats_dev_delete_age"; + + /** {@hide} */ + public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration"; + /** {@hide} */ + public static final String NETSTATS_UID_PERSIST_BYTES = "netstats_uid_persist_bytes"; + /** {@hide} */ + public static final String NETSTATS_UID_ROTATE_AGE = "netstats_uid_rotate_age"; + /** {@hide} */ + public static final String NETSTATS_UID_DELETE_AGE = "netstats_uid_delete_age"; + + /** {@hide} */ + public static final String NETSTATS_UID_TAG_BUCKET_DURATION = "netstats_uid_tag_bucket_duration"; + /** {@hide} */ + public static final String NETSTATS_UID_TAG_PERSIST_BYTES = "netstats_uid_tag_persist_bytes"; + /** {@hide} */ + public static final String NETSTATS_UID_TAG_ROTATE_AGE = "netstats_uid_tag_rotate_age"; + /** {@hide} */ + public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age"; + + /** + * User preference for which network(s) should be used. Only the + * connectivity service should touch this. + */ + public static final String NETWORK_PREFERENCE = "network_preference"; + + /** + * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment + * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been + * exceeded. + * @hide + */ + public static final String NITZ_UPDATE_DIFF = "nitz_update_diff"; + + /** + * The length of time in milli-seconds that automatic small adjustments to + * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded. + * @hide + */ + public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing"; + + /** Preferred NTP server. {@hide} */ + public static final String NTP_SERVER = "ntp_server"; + /** Timeout in milliseconds to wait for NTP server. {@hide} */ + public static final String NTP_TIMEOUT = "ntp_timeout"; + + /** + * Whether the package manager should send package verification broadcasts for verifiers to + * review apps prior to installation. + * 1 = request apps to be verified prior to installation, if a verifier exists. + * 0 = do not verify apps before installation + * @hide + */ + public static final String PACKAGE_VERIFIER_ENABLE = "package_verifier_enable"; + + /** Timeout for package verification. + * @hide */ + public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout"; + + /** Default response code for package verification. + * @hide */ + public static final String PACKAGE_VERIFIER_DEFAULT_RESPONSE = "verifier_default_response"; + + /** + * Show package verification setting in the Settings app. + * 1 = show (default) + * 0 = hide + * @hide + */ + public static final String PACKAGE_VERIFIER_SETTING_VISIBLE = "verifier_setting_visible"; + + /** + * Run package verificaiton on apps installed through ADB/ADT/USB + * 1 = perform package verification on ADB installs (default) + * 0 = bypass package verification on ADB installs + * @hide + */ + public static final String PACKAGE_VERIFIER_INCLUDE_ADB = "verifier_verify_adb_installs"; + + /** + * The interval in milliseconds at which to check packet counts on the + * mobile data interface when screen is on, to detect possible data + * connection problems. + * @hide + */ + public static final String PDP_WATCHDOG_POLL_INTERVAL_MS = + "pdp_watchdog_poll_interval_ms"; + + /** + * The interval in milliseconds at which to check packet counts on the + * mobile data interface when screen is off, to detect possible data + * connection problems. + * @hide + */ + public static final String PDP_WATCHDOG_LONG_POLL_INTERVAL_MS = + "pdp_watchdog_long_poll_interval_ms"; + + /** + * The interval in milliseconds at which to check packet counts on the + * mobile data interface after {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} + * outgoing packets has been reached without incoming packets. + * @hide + */ + public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS = + "pdp_watchdog_error_poll_interval_ms"; + + /** + * The number of outgoing packets sent without seeing an incoming packet + * that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT} + * device is logged to the event log + * @hide + */ + public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT = + "pdp_watchdog_trigger_packet_count"; + + /** + * The number of polls to perform (at {@link #PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS}) + * after hitting {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} before + * attempting data connection recovery. + * @hide + */ + public static final String PDP_WATCHDOG_ERROR_POLL_COUNT = + "pdp_watchdog_error_poll_count"; + + /** + * The number of failed PDP reset attempts before moving to something more + * drastic: re-registering to the network. + * @hide + */ + public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT = + "pdp_watchdog_max_pdp_reset_fail_count"; + + /** + * A positive value indicates how often the SamplingProfiler + * should take snapshots. Zero value means SamplingProfiler + * is disabled. + * + * @hide + */ + public static final String SAMPLING_PROFILER_MS = "sampling_profiler_ms"; + + /** + * URL to open browser on to allow user to manage a prepay account + * @hide + */ + public static final String SETUP_PREPAID_DATA_SERVICE_URL = + "setup_prepaid_data_service_url"; + + /** + * URL to attempt a GET on to see if this is a prepay device + * @hide + */ + public static final String SETUP_PREPAID_DETECTION_TARGET_URL = + "setup_prepaid_detection_target_url"; + + /** + * Host to check for a redirect to after an attempt to GET + * SETUP_PREPAID_DETECTION_TARGET_URL. (If we redirected there, + * this is a prepaid device with zero balance.) + * @hide + */ + public static final String SETUP_PREPAID_DETECTION_REDIR_HOST = + "setup_prepaid_detection_redir_host"; + + /** + * The interval in milliseconds at which to check the number of SMS sent out without asking + * for use permit, to limit the un-authorized SMS usage. + * + * @hide + */ + public static final String SMS_OUTGOING_CHECK_INTERVAL_MS = + "sms_outgoing_check_interval_ms"; + + /** + * The number of outgoing SMS sent without asking for user permit (of {@link + * #SMS_OUTGOING_CHECK_INTERVAL_MS} + * + * @hide + */ + public static final String SMS_OUTGOING_CHECK_MAX_COUNT = + "sms_outgoing_check_max_count"; + + /** + * Used to disable SMS short code confirmation - defaults to true. + * True indcates we will do the check, etc. Set to false to disable. + * @see com.android.internal.telephony.SmsUsageMonitor + * @hide + */ + public static final String SMS_SHORT_CODE_CONFIRMATION = "sms_short_code_confirmation"; + /** - * The number of milliseconds that GlobalSearch will wait for suggestions from - * promoted sources before continuing with all other sources. + * Used to select which country we use to determine premium sms codes. + * One of com.android.internal.telephony.SMSDispatcher.PREMIUM_RULE_USE_SIM, + * com.android.internal.telephony.SMSDispatcher.PREMIUM_RULE_USE_NETWORK, + * or com.android.internal.telephony.SMSDispatcher.PREMIUM_RULE_USE_BOTH. * @hide */ - public static final String SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS = - "search_promoted_source_deadline_millis"; - /** - * The number of milliseconds before GlobalSearch aborts search suggesiton queries. + public static final String SMS_SHORT_CODE_RULE = "sms_short_code_rule"; + + /** + * Used to disable Tethering on a device - defaults to true + * @hide + */ + public static final String TETHER_SUPPORTED = "tether_supported"; + + /** + * Used to require DUN APN on the device or not - defaults to a build config value + * which defaults to false + * @hide + */ + public static final String TETHER_DUN_REQUIRED = "tether_dun_required"; + + /** + * Used to hold a gservices-provisioned apn value for DUN. If set, or the + * corresponding build config values are set it will override the APN DB + * values. + * Consists of a comma seperated list of strings: + * "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type" + * note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" + * @hide + */ + public static final String TETHER_DUN_APN = "tether_dun_apn"; + + /** + * The bandwidth throttle polling freqency in seconds + * @hide + */ + public static final String THROTTLE_POLLING_SEC = "throttle_polling_sec"; + + /** + * The bandwidth throttle threshold (long) + * @hide + */ + public static final String THROTTLE_THRESHOLD_BYTES = "throttle_threshold_bytes"; + + /** + * The bandwidth throttle value (kbps) + * @hide + */ + public static final String THROTTLE_VALUE_KBITSPS = "throttle_value_kbitsps"; + + /** + * The bandwidth throttle reset calendar day (1-28) + * @hide + */ + public static final String THROTTLE_RESET_DAY = "throttle_reset_day"; + + /** + * The throttling notifications we should send + * @hide + */ + public static final String THROTTLE_NOTIFICATION_TYPE = "throttle_notification_type"; + + /** + * Help URI for data throttling policy + * @hide + */ + public static final String THROTTLE_HELP_URI = "throttle_help_uri"; + + /** + * The length of time in Sec that we allow our notion of NTP time + * to be cached before we refresh it + * @hide + */ + public static final String THROTTLE_MAX_NTP_CACHE_AGE_SEC = + "throttle_max_ntp_cache_age_sec"; + + /** + * USB Mass Storage Enabled + */ + public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled"; + + /** + * If this setting is set (to anything), then all references + * to Gmail on the device must change to Google Mail. + */ + public static final String USE_GOOGLE_MAIL = "use_google_mail"; + + /** Autofill server address (Used in WebView/browser). + * {@hide} */ + public static final String WEB_AUTOFILL_QUERY_URL = + "web_autofill_query_url"; + + /** + * Whether Wifi display is enabled/disabled + * 0=disabled. 1=enabled. + * @hide + */ + public static final String WIFI_DISPLAY_ON = "wifi_display_on"; + + /** + * Whether to notify the user of open networks. + * <p> + * If not connected and the scan results have an open network, we will + * put this notification up. If we attempt to connect to a network or + * the open network(s) disappear, we remove the notification. When we + * show the notification, we will not show it again for + * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time. + */ + public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = + "wifi_networks_available_notification_on"; + /** + * {@hide} + */ + public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON = + "wimax_networks_available_notification_on"; + + /** + * Delay (in seconds) before repeating the Wi-Fi networks available notification. + * Connecting to a network will reset the timer. + */ + public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = + "wifi_networks_available_repeat_delay"; + + /** + * 802.11 country code in ISO 3166 format + * @hide + */ + public static final String WIFI_COUNTRY_CODE = "wifi_country_code"; + + /** + * The interval in milliseconds to issue wake up scans when wifi needs + * to connect. This is necessary to connect to an access point when + * device is on the move and the screen is off. + * @hide + */ + public static final String WIFI_FRAMEWORK_SCAN_INTERVAL_MS = + "wifi_framework_scan_interval_ms"; + + /** + * The interval in milliseconds after which Wi-Fi is considered idle. + * When idle, it is possible for the device to be switched from Wi-Fi to + * the mobile data network. + * @hide + */ + public static final String WIFI_IDLE_MS = "wifi_idle_ms"; + + /** + * When the number of open networks exceeds this number, the + * least-recently-used excess networks will be removed. + */ + public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept"; + + /** + * Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this. + */ + public static final String WIFI_ON = "wifi_on"; + + /** + * Used to save the Wifi_ON state prior to tethering. + * This state will be checked to restore Wifi after + * the user turns off tethering. + * + * @hide + */ + public static final String WIFI_SAVED_STATE = "wifi_saved_state"; + + /** + * The interval in milliseconds to scan as used by the wifi supplicant + * @hide + */ + public static final String WIFI_SUPPLICANT_SCAN_INTERVAL_MS = + "wifi_supplicant_scan_interval_ms"; + + /** + * The interval in milliseconds to scan at supplicant when p2p is connected + * @hide + */ + public static final String WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS = + "wifi_scan_interval_p2p_connected_ms"; + + /** + * Whether the Wi-Fi watchdog is enabled. + */ + public static final String WIFI_WATCHDOG_ON = "wifi_watchdog_on"; + + /** + * Setting to turn off poor network avoidance on Wi-Fi. Feature is enabled by default and + * the setting needs to be set to 0 to disable it. + * @hide + */ + public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED = + "wifi_watchdog_poor_network_test_enabled"; + + /** + * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and + * needs to be set to 0 to disable it. + * @hide + */ + public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED = + "wifi_suspend_optimizations_enabled"; + + /** + * The maximum number of times we will retry a connection to an access + * point for which we have failed in acquiring an IP address from DHCP. + * A value of N means that we will make N+1 connection attempts in all. + */ + public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count"; + + /** + * Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile + * data connectivity to be established after a disconnect from Wi-Fi. + */ + public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = + "wifi_mobile_data_transition_wakelock_timeout_ms"; + + /** + * The operational wifi frequency band + * Set to one of {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO}, + * {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ} or + * {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ} + * + * @hide + */ + public static final String WIFI_FREQUENCY_BAND = "wifi_frequency_band"; + + /** + * The Wi-Fi peer-to-peer device name + * @hide + */ + public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name"; + + /** + * The number of milliseconds to delay when checking for data stalls during + * non-aggressive detection. (screen is turned off.) + * @hide + */ + public static final String DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS = + "data_stall_alarm_non_aggressive_delay_in_ms"; + + /** + * The number of milliseconds to delay when checking for data stalls during + * aggressive detection. (screen on or suspected data stall) + * @hide + */ + public static final String DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS = + "data_stall_alarm_aggressive_delay_in_ms"; + + /** + * The interval in milliseconds at which to check gprs registration + * after the first registration mismatch of gprs and voice service, + * to detect possible data network registration problems. + * + * @hide + */ + public static final String GPRS_REGISTER_CHECK_PERIOD_MS = + "gprs_register_check_period_ms"; + + /** + * Nonzero causes Log.wtf() to crash. + * @hide + */ + public static final String WTF_IS_FATAL = "wtf_is_fatal"; + + /** + * Ringer mode. This is used internally, changing this value will not + * change the ringer mode. See AudioManager. + */ + public static final String MODE_RINGER = "mode_ringer"; + + /** + * Overlay display devices setting. + * The associated value is a specially formatted string that describes the + * size and density of simulated secondary display devices. + * <p> + * Format: {width}x{height}/{dpi};... + * </p><p> + * Example: + * <ul> + * <li><code>1280x720/213</code>: make one overlay that is 1280x720 at 213dpi.</li> + * <li><code>1920x1080/320;1280x720/213</code>: make two overlays, the first + * at 1080p and the second at 720p.</li> + * <li>If the value is empty, then no overlay display devices are created.</li> + * </ul></p> + * + * @hide + */ + public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices"; + + /** + * Threshold values for the duration and level of a discharge cycle, + * under which we log discharge cycle info. + * * @hide */ - public static final String SEARCH_SOURCE_TIMEOUT_MILLIS = "search_source_timeout_millis"; + public static final String + BATTERY_DISCHARGE_DURATION_THRESHOLD = "battery_discharge_duration_threshold"; + + /** @hide */ + public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold"; + /** - * The maximum number of milliseconds that GlobalSearch shows the previous results - * after receiving a new query. + * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR + * intents on application crashes and ANRs. If this is disabled, the + * crash/ANR dialog will never display the "Report" button. + * <p> + * Type: int (0 = disallow, 1 = allow) + * * @hide */ - public static final String SEARCH_PREFILL_MILLIS = "search_prefill_millis"; + public static final String SEND_ACTION_APP_ERROR = "send_action_app_error"; + /** - * The maximum age of log data used for shortcuts in GlobalSearch. + * Maximum age of entries kept by {@link DropBoxManager}. + * * @hide */ - public static final String SEARCH_MAX_STAT_AGE_MILLIS = "search_max_stat_age_millis"; + public static final String DROPBOX_AGE_SECONDS = "dropbox_age_seconds"; + /** - * The maximum age of log data used for source ranking in GlobalSearch. + * Maximum number of entry files which {@link DropBoxManager} will keep + * around. + * * @hide */ - public static final String SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS = - "search_max_source_event_age_millis"; + public static final String DROPBOX_MAX_FILES = "dropbox_max_files"; + /** - * The minimum number of impressions needed to rank a source in GlobalSearch. + * Maximum amount of disk space used by {@link DropBoxManager} no matter + * what. + * * @hide */ - public static final String SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING = - "search_min_impressions_for_source_ranking"; + public static final String DROPBOX_QUOTA_KB = "dropbox_quota_kb"; + /** - * The minimum number of clicks needed to rank a source in GlobalSearch. + * Percent of free disk (excluding reserve) which {@link DropBoxManager} + * will use. + * * @hide */ - public static final String SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING = - "search_min_clicks_for_source_ranking"; + public static final String DROPBOX_QUOTA_PERCENT = "dropbox_quota_percent"; + /** - * The maximum number of shortcuts shown by GlobalSearch. + * Percent of total disk which {@link DropBoxManager} will never dip + * into. + * * @hide */ - public static final String SEARCH_MAX_SHORTCUTS_RETURNED = "search_max_shortcuts_returned"; + public static final String DROPBOX_RESERVE_PERCENT = "dropbox_reserve_percent"; + /** - * The size of the core thread pool for suggestion queries in GlobalSearch. + * Prefix for per-tag dropbox disable/enable settings. + * * @hide */ - public static final String SEARCH_QUERY_THREAD_CORE_POOL_SIZE = - "search_query_thread_core_pool_size"; + public static final String DROPBOX_TAG_PREFIX = "dropbox:"; + /** - * The maximum size of the thread pool for suggestion queries in GlobalSearch. + * Lines of logcat to include with system crash/ANR/etc. reports, as a + * prefix of the dropbox tag of the report type. For example, + * "logcat_for_system_server_anr" controls the lines of logcat captured + * with system server ANR reports. 0 to disable. + * * @hide */ - public static final String SEARCH_QUERY_THREAD_MAX_POOL_SIZE = - "search_query_thread_max_pool_size"; + public static final String ERROR_LOGCAT_PREFIX = "logcat_for_"; + /** - * The size of the core thread pool for shortcut refreshing in GlobalSearch. + * The interval in minutes after which the amount of free storage left + * on the device is logged to the event log + * * @hide */ - public static final String SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE = - "search_shortcut_refresh_core_pool_size"; + public static final String SYS_FREE_STORAGE_LOG_INTERVAL = "sys_free_storage_log_interval"; + /** - * The maximum size of the thread pool for shortcut refreshing in GlobalSearch. + * Threshold for the amount of change in disk free space required to + * report the amount of free space. Used to prevent spamming the logs + * when the disk free space isn't changing frequently. + * * @hide */ - public static final String SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE = - "search_shortcut_refresh_max_pool_size"; + public static final String + DISK_FREE_CHANGE_REPORTING_THRESHOLD = "disk_free_change_reporting_threshold"; + /** - * The maximun time that excess threads in the GlobalSeach thread pools will - * wait before terminating. + * Minimum percentage of free storage on the device that is used to + * determine if the device is running low on storage. The default is 10. + * <p> + * Say this value is set to 10, the device is considered running low on + * storage if 90% or more of the device storage is filled up. + * * @hide */ - public static final String SEARCH_THREAD_KEEPALIVE_SECONDS = - "search_thread_keepalive_seconds"; + public static final String + SYS_STORAGE_THRESHOLD_PERCENTAGE = "sys_storage_threshold_percentage"; + /** - * The maximum number of concurrent suggestion queries to each source. + * Maximum byte size of the low storage threshold. This is to ensure + * that {@link #SYS_STORAGE_THRESHOLD_PERCENTAGE} does not result in an + * overly large threshold for large storage devices. Currently this must + * be less than 2GB. This default is 500MB. + * * @hide */ - public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT = - "search_per_source_concurrent_query_limit"; + public static final String + SYS_STORAGE_THRESHOLD_MAX_BYTES = "sys_storage_threshold_max_bytes"; /** - * Whether or not alert sounds are played on MountService events. (0 = false, 1 = true) + * Minimum bytes of free storage on the device before the data partition + * is considered full. By default, 1 MB is reserved to avoid system-wide + * SQLite disk full exceptions. + * * @hide */ - public static final String MOUNT_PLAY_NOTIFICATION_SND = "mount_play_not_snd"; + public static final String + SYS_STORAGE_FULL_THRESHOLD_BYTES = "sys_storage_full_threshold_bytes"; /** - * Whether or not UMS auto-starts on UMS host detection. (0 = false, 1 = true) + * The maximum reconnect delay for short network outages or when the + * network is suspended due to phone use. + * * @hide */ - public static final String MOUNT_UMS_AUTOSTART = "mount_ums_autostart"; + public static final String + SYNC_MAX_RETRY_DELAY_IN_SECONDS = "sync_max_retry_delay_in_seconds"; /** - * Whether or not a notification is displayed on UMS host detection. (0 = false, 1 = true) + * The number of milliseconds to delay before sending out + * {@link ConnectivityManager#CONNECTIVITY_ACTION} broadcasts. + * * @hide */ - public static final String MOUNT_UMS_PROMPT = "mount_ums_prompt"; + public static final String CONNECTIVITY_CHANGE_DELAY = "connectivity_change_delay"; /** - * Whether or not a notification is displayed while UMS is enabled. (0 = false, 1 = true) + * Setting to turn off captive portal detection. Feature is enabled by + * default and the setting needs to be set to 0 to disable it. + * * @hide */ - public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled"; + public static final String + CAPTIVE_PORTAL_DETECTION_ENABLED = "captive_portal_detection_enabled"; /** - * If nonzero, ANRs in invisible background processes bring up a dialog. - * Otherwise, the process will be silently killed. + * The server used for captive portal detection upon a new conection. A + * 204 response code from the server is used for validation. + * * @hide */ - public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; + public static final String CAPTIVE_PORTAL_SERVER = "captive_portal_server"; /** - * The {@link ComponentName} string of the service to be used as the voice recognition - * service. + * Whether network service discovery is enabled. * * @hide */ - public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service"; - + public static final String NSD_ON = "nsd_on"; /** - * The {@link ComponentName} string of the selected spell checker service which is - * one of the services managed by the text service manager. + * Let user pick default install location. * * @hide */ - public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker"; + public static final String SET_INSTALL_LOCATION = "set_install_location"; /** - * The {@link ComponentName} string of the selected subtype of the selected spell checker - * service which is one of the services managed by the text service manager. - * + * Default install location value. + * 0 = auto, let system decide + * 1 = internal + * 2 = sdcard * @hide */ - public static final String SELECTED_SPELL_CHECKER_SUBTYPE = - "selected_spell_checker_subtype"; + public static final String DEFAULT_INSTALL_LOCATION = "default_install_location"; /** - * The {@link ComponentName} string whether spell checker is enabled or not. + * ms during which to consume extra events related to Inet connection + * condition after a transtion to fully-connected * * @hide */ - public static final String SPELL_CHECKER_ENABLED = "spell_checker_enabled"; + public static final String + INET_CONDITION_DEBOUNCE_UP_DELAY = "inet_condition_debounce_up_delay"; /** - * What happens when the user presses the Power button while in-call - * and the screen is on.<br/> - * <b>Values:</b><br/> - * 1 - The Power button turns off the screen and locks the device. (Default behavior)<br/> - * 2 - The Power button hangs up the current call.<br/> + * ms during which to consume extra events related to Inet connection + * condtion after a transtion to partly-connected * * @hide */ - public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior"; + public static final String + INET_CONDITION_DEBOUNCE_DOWN_DELAY = "inet_condition_debounce_down_delay"; + + /** {@hide} */ + public static final String + READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT = "read_external_storage_enforced_default"; /** - * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen". - * @hide + * Host name and port for global http proxy. Uses ':' seperator for + * between host and port. */ - public static final int INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF = 0x1; + public static final String HTTP_PROXY = "http_proxy"; /** - * INCALL_POWER_BUTTON_BEHAVIOR value for "hang up". + * Host name for global http proxy. Set via ConnectivityManager. + * * @hide */ - public static final int INCALL_POWER_BUTTON_BEHAVIOR_HANGUP = 0x2; + public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host"; /** - * INCALL_POWER_BUTTON_BEHAVIOR default value. + * Integer host port for global http proxy. Set via ConnectivityManager. + * * @hide */ - public static final int INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT = - INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF; + public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port"; /** - * The current night mode that has been selected by the user. Owned - * and controlled by UiModeManagerService. Constants are as per - * UiModeManager. + * Exclusion list for global proxy. This string contains a list of + * comma-separated domains where the global proxy does not apply. + * Domains should be listed in a comma- separated list. Example of + * acceptable formats: ".domain1.com,my.domain2.com" Use + * ConnectivityManager to set/get. + * * @hide */ - public static final String UI_NIGHT_MODE = "ui_night_mode"; + public static final String + GLOBAL_HTTP_PROXY_EXCLUSION_LIST = "global_http_proxy_exclusion_list"; /** - * Let user pick default install location. + * Enables the UI setting to allow the user to specify the global HTTP + * proxy and associated exclusion list. + * * @hide */ - public static final String SET_INSTALL_LOCATION = "set_install_location"; + public static final String SET_GLOBAL_HTTP_PROXY = "set_global_http_proxy"; /** - * Default install location value. - * 0 = auto, let system decide - * 1 = internal - * 2 = sdcard + * Setting for default DNS in case nobody suggests one + * * @hide */ - public static final String DEFAULT_INSTALL_LOCATION = "default_install_location"; + public static final String DEFAULT_DNS_SERVER = "default_dns_server"; + + /** {@hide} */ + public static final String + BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_"; + /** {@hide} */ + public static final String + BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_"; + /** {@hide} */ + public static final String + BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_"; /** - * The bandwidth throttle polling freqency in seconds + * Get the key that retrieves a bluetooth headset's priority. * @hide */ - public static final String THROTTLE_POLLING_SEC = "throttle_polling_sec"; + public static final String getBluetoothHeadsetPriorityKey(String address) { + return BLUETOOTH_HEADSET_PRIORITY_PREFIX + address.toUpperCase(); + } /** - * The bandwidth throttle threshold (long) + * Get the key that retrieves a bluetooth a2dp sink's priority. * @hide */ - public static final String THROTTLE_THRESHOLD_BYTES = "throttle_threshold_bytes"; + public static final String getBluetoothA2dpSinkPriorityKey(String address) { + return BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(); + } /** - * The bandwidth throttle value (kbps) + * Get the key that retrieves a bluetooth Input Device's priority. * @hide */ - public static final String THROTTLE_VALUE_KBITSPS = "throttle_value_kbitsps"; + public static final String getBluetoothInputDevicePriorityKey(String address) { + return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(); + } /** - * The bandwidth throttle reset calendar day (1-28) - * @hide + * Scaling factor for normal window animations. Setting to 0 will + * disable window animations. */ - public static final String THROTTLE_RESET_DAY = "throttle_reset_day"; + public static final String WINDOW_ANIMATION_SCALE = "window_animation_scale"; /** - * The throttling notifications we should send - * @hide + * Scaling factor for activity transition animations. Setting to 0 will + * disable window animations. */ - public static final String THROTTLE_NOTIFICATION_TYPE = "throttle_notification_type"; + public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale"; /** - * Help URI for data throttling policy - * @hide + * Scaling factor for Animator-based animations. This affects both the + * start delay and duration of all such animations. Setting to 0 will + * cause animations to end immediately. The default value is 1. */ - public static final String THROTTLE_HELP_URI = "throttle_help_uri"; + public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale"; /** - * The length of time in Sec that we allow our notion of NTP time - * to be cached before we refresh it + * Scaling factor for normal window animations. Setting to 0 will + * disable window animations. + * * @hide */ - public static final String THROTTLE_MAX_NTP_CACHE_AGE_SEC = - "throttle_max_ntp_cache_age_sec"; + public static final String FANCY_IME_ANIMATIONS = "fancy_ime_animations"; /** - * The maximum size, in bytes, of a download that the download manager will transfer over - * a non-wifi connection. + * If 0, the compatibility mode is off for all applications. + * If 1, older applications run under compatibility mode. + * TODO: remove this settings before code freeze (bug/1907571) * @hide */ - public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE = - "download_manager_max_bytes_over_mobile"; + public static final String COMPATIBILITY_MODE = "compatibility_mode"; /** - * The recommended maximum size, in bytes, of a download that the download manager should - * transfer over a non-wifi connection. Over this size, the use will be warned, but will - * have the option to start the download over the mobile connection anyway. + * CDMA only settings + * Emergency Tone 0 = Off + * 1 = Alert + * 2 = Vibrate * @hide */ - public static final String DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE = - "download_manager_recommended_max_bytes_over_mobile"; + public static final String EMERGENCY_TONE = "emergency_tone"; /** - * ms during which to consume extra events related to Inet connection condition - * after a transtion to fully-connected + * CDMA only settings + * Whether the auto retry is enabled. The value is + * boolean (1 or 0). * @hide */ - public static final String INET_CONDITION_DEBOUNCE_UP_DELAY = - "inet_condition_debounce_up_delay"; + public static final String CALL_AUTO_RETRY = "call_auto_retry"; /** - * ms during which to consume extra events related to Inet connection condtion - * after a transtion to partly-connected + * The preferred network mode 7 = Global + * 6 = EvDo only + * 5 = CDMA w/o EvDo + * 4 = CDMA / EvDo auto + * 3 = GSM / WCDMA auto + * 2 = WCDMA only + * 1 = GSM only + * 0 = GSM / WCDMA preferred * @hide */ - public static final String INET_CONDITION_DEBOUNCE_DOWN_DELAY = - "inet_condition_debounce_down_delay"; + public static final String PREFERRED_NETWORK_MODE = + "preferred_network_mode"; /** - * URL to open browser on to allow user to manage a prepay account + * The cdma subscription 0 = Subscription from RUIM, when available + * 1 = Subscription from NV * @hide */ - public static final String SETUP_PREPAID_DATA_SERVICE_URL = - "setup_prepaid_data_service_url"; + public static final String PREFERRED_CDMA_SUBSCRIPTION = + "preferred_cdma_subscription"; /** - * URL to attempt a GET on to see if this is a prepay device - * @hide + * Name of an application package to be debugged. */ - public static final String SETUP_PREPAID_DETECTION_TARGET_URL = - "setup_prepaid_detection_target_url"; + public static final String DEBUG_APP = "debug_app"; /** - * Host to check for a redirect to after an attempt to GET - * SETUP_PREPAID_DETECTION_TARGET_URL. (If we redirected there, - * this is a prepaid device with zero balance.) - * @hide + * If 1, when launching DEBUG_APP it will wait for the debugger before + * starting user code. If 0, it will run normally. */ - public static final String SETUP_PREPAID_DETECTION_REDIR_HOST = - "setup_prepaid_detection_redir_host"; + public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger"; /** - * Whether the screensaver is enabled. - * @hide + * Control whether the process CPU usage meter should be shown. */ - public static final String SCREENSAVER_ENABLED = "screensaver_enabled"; + public static final String SHOW_PROCESSES = "show_processes"; /** - * The user's chosen screensaver component. - * - * This component will be launched by the PhoneWindowManager after a timeout when not on - * battery, or upon dock insertion (if SCREENSAVER_ACTIVATE_ON_DOCK is set to 1). - * @hide + * If 1, the activity manager will aggressively finish activities and + * processes as soon as they are no longer needed. If 0, the normal + * extended lifetime is used. */ - public static final String SCREENSAVER_COMPONENT = "screensaver_component"; + public static final String ALWAYS_FINISH_ACTIVITIES = + "always_finish_activities"; + + // Populated lazily, guarded by class object: + private static NameValueCache sNameValueCache = new NameValueCache( + SYS_PROP_SETTING_VERSION, + CONTENT_URI, + CALL_METHOD_GET_GLOBAL, + CALL_METHOD_PUT_GLOBAL); /** - * Whether the screensaver should be automatically launched when the device is inserted - * into a (desk) dock. - * @hide + * Look up a name in the database. + * @param resolver to access the database with + * @param name to look up in the table + * @return the corresponding value, or null if not present */ - public static final String SCREENSAVER_ACTIVATE_ON_DOCK = "screensaver_activate_on_dock"; - - /** {@hide} */ - public static final String NETSTATS_ENABLED = "netstats_enabled"; - /** {@hide} */ - public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval"; - /** {@hide} */ - public static final String NETSTATS_TIME_CACHE_MAX_AGE = "netstats_time_cache_max_age"; - /** {@hide} */ - public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes"; - /** {@hide} */ - public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled"; - /** {@hide} */ - public static final String NETSTATS_REPORT_XT_OVER_DEV = "netstats_report_xt_over_dev"; + public static String getString(ContentResolver resolver, String name) { + return getStringForUser(resolver, name, UserHandle.myUserId()); + } - /** {@hide} */ - public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration"; - /** {@hide} */ - public static final String NETSTATS_DEV_PERSIST_BYTES = "netstats_dev_persist_bytes"; - /** {@hide} */ - public static final String NETSTATS_DEV_ROTATE_AGE = "netstats_dev_rotate_age"; - /** {@hide} */ - public static final String NETSTATS_DEV_DELETE_AGE = "netstats_dev_delete_age"; + /** @hide */ + public static String getStringForUser(ContentResolver resolver, String name, + int userHandle) { + return sNameValueCache.getStringForUser(resolver, name, userHandle); + } - /** {@hide} */ - public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration"; - /** {@hide} */ - public static final String NETSTATS_UID_PERSIST_BYTES = "netstats_uid_persist_bytes"; - /** {@hide} */ - public static final String NETSTATS_UID_ROTATE_AGE = "netstats_uid_rotate_age"; - /** {@hide} */ - public static final String NETSTATS_UID_DELETE_AGE = "netstats_uid_delete_age"; + /** + * Store a name/value pair into the database. + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + public static boolean putString(ContentResolver resolver, + String name, String value) { + return putStringForUser(resolver, name, value, UserHandle.myUserId()); + } - /** {@hide} */ - public static final String NETSTATS_UID_TAG_BUCKET_DURATION = "netstats_uid_tag_bucket_duration"; - /** {@hide} */ - public static final String NETSTATS_UID_TAG_PERSIST_BYTES = "netstats_uid_tag_persist_bytes"; - /** {@hide} */ - public static final String NETSTATS_UID_TAG_ROTATE_AGE = "netstats_uid_tag_rotate_age"; - /** {@hide} */ - public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age"; + /** @hide */ + public static boolean putStringForUser(ContentResolver resolver, + String name, String value, int userHandle) { + if (LOCAL_LOGV) { + Log.v(TAG, "Global.putString(name=" + name + ", value=" + value + + " for " + userHandle); + } + return sNameValueCache.putStringForUser(resolver, name, value, userHandle); + } - /** Preferred NTP server. {@hide} */ - public static final String NTP_SERVER = "ntp_server"; - /** Timeout in milliseconds to wait for NTP server. {@hide} */ - public static final String NTP_TIMEOUT = "ntp_timeout"; + /** + * Construct the content URI for a particular name/value pair, + * useful for monitoring changes with a ContentObserver. + * @param name to look up in the table + * @return the corresponding content URI, or null if not present + */ + public static Uri getUriFor(String name) { + return getUriFor(CONTENT_URI, name); + } - /** Autofill server address (Used in WebView/browser). {@hide} */ - public static final String WEB_AUTOFILL_QUERY_URL = - "web_autofill_query_url"; + /** + * Convenience function for retrieving a single secure settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. The default value will be returned if the setting is + * not defined or not an integer. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid integer. + */ + public static int getInt(ContentResolver cr, String name, int def) { + String v = getString(cr, name); + try { + return v != null ? Integer.parseInt(v) : def; + } catch (NumberFormatException e) { + return def; + } + } - /** Whether package verification is enabled. {@hide} */ - public static final String PACKAGE_VERIFIER_ENABLE = "verifier_enable"; + /** + * Convenience function for retrieving a single secure settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link SettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @throws SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + * + * @return The setting's current value. + */ + public static int getInt(ContentResolver cr, String name) + throws SettingNotFoundException { + String v = getString(cr, name); + try { + return Integer.parseInt(v); + } catch (NumberFormatException e) { + throw new SettingNotFoundException(name); + } + } - /** Timeout for package verification. {@hide} */ - public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout"; + /** + * Convenience function for updating a single settings value as an + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + public static boolean putInt(ContentResolver cr, String name, int value) { + return putString(cr, name, Integer.toString(value)); + } - /** {@hide} */ - public static final String - READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT = "read_external_storage_enforced_default"; + /** + * Convenience function for retrieving a single secure settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. The default value will be returned if the setting is + * not defined or not a {@code long}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid {@code long}. + */ + public static long getLong(ContentResolver cr, String name, long def) { + String valString = getString(cr, name); + long value; + try { + value = valString != null ? Long.parseLong(valString) : def; + } catch (NumberFormatException e) { + value = def; + } + return value; + } /** - * Duration in milliseconds before pre-authorized URIs for the contacts - * provider should expire. - * @hide + * Convenience function for retrieving a single secure settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link SettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @return The setting's current value. + * @throws SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. */ - public static final String CONTACTS_PREAUTH_URI_EXPIRATION = - "contacts_preauth_uri_expiration"; + public static long getLong(ContentResolver cr, String name) + throws SettingNotFoundException { + String valString = getString(cr, name); + try { + return Long.parseLong(valString); + } catch (NumberFormatException e) { + throw new SettingNotFoundException(name); + } + } /** - * Prefix for SMS short code regex patterns (country code is appended). - * @see com.android.internal.telephony.SmsUsageMonitor - * @hide + * Convenience function for updating a secure settings value as a long + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors */ - public static final String SMS_SHORT_CODES_PREFIX = "sms_short_codes_"; + public static boolean putLong(ContentResolver cr, String name, long value) { + return putString(cr, name, Long.toString(value)); + } /** - * This are the settings to be backed up. + * Convenience function for retrieving a single secure settings value + * as a floating point number. Note that internally setting values are + * always stored as strings; this function converts the string to an + * float for you. The default value will be returned if the setting + * is not defined or not a valid float. * - * NOTE: Settings are backed up and restored in the order they appear - * in this array. If you have one setting depending on another, - * make sure that they are ordered appropriately. + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. * - * @hide + * @return The setting's current value, or 'def' if it is not defined + * or not a valid float. */ - public static final String[] SETTINGS_TO_BACKUP = { - ADB_ENABLED, - ALLOW_MOCK_LOCATION, - PARENTAL_CONTROL_ENABLED, - PARENTAL_CONTROL_REDIRECT_URL, - USB_MASS_STORAGE_ENABLED, - ACCESSIBILITY_SCRIPT_INJECTION, - BACKUP_AUTO_RESTORE, - ENABLED_ACCESSIBILITY_SERVICES, - TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - TOUCH_EXPLORATION_ENABLED, - ACCESSIBILITY_ENABLED, - ACCESSIBILITY_SPEAK_PASSWORD, - TTS_USE_DEFAULTS, - TTS_DEFAULT_RATE, - TTS_DEFAULT_PITCH, - TTS_DEFAULT_SYNTH, - TTS_DEFAULT_LANG, - TTS_DEFAULT_COUNTRY, - TTS_ENABLED_PLUGINS, - TTS_DEFAULT_LOCALE, - WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, - WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, - WIFI_NUM_OPEN_NETWORKS_KEPT, - MOUNT_PLAY_NOTIFICATION_SND, - MOUNT_UMS_AUTOSTART, - MOUNT_UMS_PROMPT, - MOUNT_UMS_NOTIFY_ENABLED, - UI_NIGHT_MODE, - LOCK_SCREEN_OWNER_INFO, - LOCK_SCREEN_OWNER_INFO_ENABLED - }; + public static float getFloat(ContentResolver cr, String name, float def) { + String v = getString(cr, name); + try { + return v != null ? Float.parseFloat(v) : def; + } catch (NumberFormatException e) { + return def; + } + } /** - * Helper method for determining if a location provider is enabled. - * @param cr the content resolver to use - * @param provider the location provider to query - * @return true if the provider is enabled + * Convenience function for retrieving a single secure settings value + * as a float. Note that internally setting values are always + * stored as strings; this function converts the string to a float + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link SettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * + * @throws SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not a float. + * + * @return The setting's current value. */ - public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) { - String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED); - return TextUtils.delimitedStringContains(allowedProviders, ',', provider); + public static float getFloat(ContentResolver cr, String name) + throws SettingNotFoundException { + String v = getString(cr, name); + if (v == null) { + throw new SettingNotFoundException(name); + } + try { + return Float.parseFloat(v); + } catch (NumberFormatException e) { + throw new SettingNotFoundException(name); + } } /** - * Thread-safe method for enabling or disabling a single location provider. - * @param cr the content resolver to use - * @param provider the location provider to enable or disable - * @param enabled true if the provider should be enabled + * Convenience function for updating a single settings value as a + * floating point number. This will either create a new entry in the + * table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values + * are always stored as strings, so this function converts the given + * value to a string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors */ - public static final void setLocationProviderEnabled(ContentResolver cr, - String provider, boolean enabled) { - // to ensure thread safety, we write the provider name with a '+' or '-' - // and let the SettingsProvider handle it rather than reading and modifying - // the list of enabled providers. - if (enabled) { - provider = "+" + provider; - } else { - provider = "-" + provider; - } - putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider); + public static boolean putFloat(ContentResolver cr, String name, float value) { + return putString(cr, name, Float.toString(value)); } } diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java deleted file mode 100644 index 08a99d2..0000000 --- a/core/java/android/server/BluetoothA2dpService.java +++ /dev/null @@ -1,653 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * TODO: Move this to services.jar - * and make the constructor package private again. - * @hide - */ - -package android.server; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothUuid; -import android.bluetooth.IBluetoothA2dp; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; -import android.os.Handler; -import android.os.Message; -import android.os.ParcelUuid; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.provider.Settings; -import android.util.Log; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - - -public class BluetoothA2dpService extends IBluetoothA2dp.Stub { - private static final String TAG = "BluetoothA2dpService"; - private static final boolean DBG = true; - - public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp"; - - private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; - private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; - - private static final String BLUETOOTH_ENABLED = "bluetooth_enabled"; - - private static final String PROPERTY_STATE = "State"; - - private final Context mContext; - private final IntentFilter mIntentFilter; - private HashMap<BluetoothDevice, Integer> mAudioDevices; - private final AudioManager mAudioManager; - private final BluetoothService mBluetoothService; - private final BluetoothAdapter mAdapter; - private int mTargetA2dpState; - private BluetoothDevice mPlayingA2dpDevice; - private IntentBroadcastHandler mIntentBroadcastHandler; - private final WakeLock mWakeLock; - - private static final int MSG_CONNECTION_STATE_CHANGED = 0; - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - BluetoothDevice device = - intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - switch (state) { - case BluetoothAdapter.STATE_ON: - onBluetoothEnable(); - break; - case BluetoothAdapter.STATE_TURNING_OFF: - onBluetoothDisable(); - break; - } - } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { - synchronized (this) { - if (mAudioDevices.containsKey(device)) { - int state = mAudioDevices.get(device); - handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); - } - } - } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { - int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); - if (streamType == AudioManager.STREAM_MUSIC) { - List<BluetoothDevice> sinks = getConnectedDevices(); - - if (sinks.size() != 0 && isPhoneDocked(sinks.get(0))) { - String address = sinks.get(0).getAddress(); - int newVolLevel = - intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); - int oldVolLevel = - intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0); - String path = mBluetoothService.getObjectPathFromAddress(address); - if (newVolLevel > oldVolLevel) { - avrcpVolumeUpNative(path); - } else if (newVolLevel < oldVolLevel) { - avrcpVolumeDownNative(path); - } - } - } - } - } - }; - - private boolean isPhoneDocked(BluetoothDevice device) { - // This works only because these broadcast intents are "sticky" - Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - if (i != null) { - int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (dockDevice != null && device.equals(dockDevice)) { - return true; - } - } - } - return false; - } - - public BluetoothA2dpService(Context context, BluetoothService bluetoothService) { - mContext = context; - - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService"); - - mIntentBroadcastHandler = new IntentBroadcastHandler(); - - mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - - mBluetoothService = bluetoothService; - if (mBluetoothService == null) { - throw new RuntimeException("Platform does not support Bluetooth"); - } - - if (!initNative()) { - throw new RuntimeException("Could not init BluetoothA2dpService"); - } - - mAdapter = BluetoothAdapter.getDefaultAdapter(); - - mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); - mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); - mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION); - mContext.registerReceiver(mReceiver, mIntentFilter); - - mAudioDevices = new HashMap<BluetoothDevice, Integer>(); - - if (mBluetoothService.isEnabled()) - onBluetoothEnable(); - mTargetA2dpState = -1; - mBluetoothService.setA2dpService(this); - } - - @Override - protected void finalize() throws Throwable { - try { - cleanupNative(); - } finally { - super.finalize(); - } - } - - private int convertBluezSinkStringToState(String value) { - if (value.equalsIgnoreCase("disconnected")) - return BluetoothA2dp.STATE_DISCONNECTED; - if (value.equalsIgnoreCase("connecting")) - return BluetoothA2dp.STATE_CONNECTING; - if (value.equalsIgnoreCase("connected")) - return BluetoothA2dp.STATE_CONNECTED; - if (value.equalsIgnoreCase("playing")) - return BluetoothA2dp.STATE_PLAYING; - return -1; - } - - private boolean isSinkDevice(BluetoothDevice device) { - ParcelUuid[] uuids = mBluetoothService.getRemoteUuids(device.getAddress()); - if (uuids != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) { - return true; - } - return false; - } - - private synchronized void addAudioSink(BluetoothDevice device) { - if (mAudioDevices.get(device) == null) { - mAudioDevices.put(device, BluetoothA2dp.STATE_DISCONNECTED); - } - } - - private synchronized void onBluetoothEnable() { - String devices = mBluetoothService.getProperty("Devices", true); - if (devices != null) { - String [] paths = devices.split(","); - for (String path: paths) { - String address = mBluetoothService.getAddressFromObjectPath(path); - BluetoothDevice device = mAdapter.getRemoteDevice(address); - ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address); - if (remoteUuids != null) - if (BluetoothUuid.containsAnyUuid(remoteUuids, - new ParcelUuid[] {BluetoothUuid.AudioSink, - BluetoothUuid.AdvAudioDist})) { - addAudioSink(device); - } - } - } - mAudioManager.setParameters(BLUETOOTH_ENABLED+"=true"); - mAudioManager.setParameters("A2dpSuspended=false"); - } - - private synchronized void onBluetoothDisable() { - if (!mAudioDevices.isEmpty()) { - BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()]; - devices = mAudioDevices.keySet().toArray(devices); - for (BluetoothDevice device : devices) { - int state = getConnectionState(device); - switch (state) { - case BluetoothA2dp.STATE_CONNECTING: - case BluetoothA2dp.STATE_CONNECTED: - case BluetoothA2dp.STATE_PLAYING: - disconnectSinkNative(mBluetoothService.getObjectPathFromAddress( - device.getAddress())); - handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); - break; - case BluetoothA2dp.STATE_DISCONNECTING: - handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTING, - BluetoothA2dp.STATE_DISCONNECTED); - break; - } - } - mAudioDevices.clear(); - } - - mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false"); - } - - private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) { - if (!mBluetoothService.isEnabled() || !isSinkDevice(device) || - getPriority(device) == BluetoothA2dp.PRIORITY_OFF) { - return false; - } - - addAudioSink(device); - - String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (path == null) { - return false; - } - return true; - } - - public synchronized boolean isA2dpPlaying(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (DBG) log("isA2dpPlaying(" + device + ")"); - if (device.equals(mPlayingA2dpDevice)) return true; - return false; - } - - public synchronized boolean connect(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (DBG) log("connectSink(" + device + ")"); - if (!isConnectSinkFeasible(device)) return false; - - for (BluetoothDevice sinkDevice : mAudioDevices.keySet()) { - if (getConnectionState(sinkDevice) != BluetoothProfile.STATE_DISCONNECTED) { - disconnect(sinkDevice); - } - } - - return mBluetoothService.connectSink(device.getAddress()); - } - - public synchronized boolean connectSinkInternal(BluetoothDevice device) { - if (!mBluetoothService.isEnabled()) return false; - - int state = mAudioDevices.get(device); - - // ignore if there are any active sinks - if (getDevicesMatchingConnectionStates(new int[] { - BluetoothA2dp.STATE_CONNECTING, - BluetoothA2dp.STATE_CONNECTED, - BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) { - return false; - } - - switch (state) { - case BluetoothA2dp.STATE_CONNECTED: - case BluetoothA2dp.STATE_DISCONNECTING: - return false; - case BluetoothA2dp.STATE_CONNECTING: - return true; - } - - String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - - // State is DISCONNECTED and we are connecting. - if (getPriority(device) < BluetoothA2dp.PRIORITY_AUTO_CONNECT) { - setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); - } - handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING); - - if (!connectSinkNative(path)) { - // Restore previous state - handleSinkStateChange(device, mAudioDevices.get(device), state); - return false; - } - return true; - } - - private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) { - String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (path == null) { - return false; - } - - int state = getConnectionState(device); - switch (state) { - case BluetoothA2dp.STATE_DISCONNECTED: - case BluetoothA2dp.STATE_DISCONNECTING: - return false; - } - return true; - } - - public synchronized boolean disconnect(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (DBG) log("disconnectSink(" + device + ")"); - if (!isDisconnectSinkFeasible(device)) return false; - return mBluetoothService.disconnectSink(device.getAddress()); - } - - public synchronized boolean disconnectSinkInternal(BluetoothDevice device) { - int state = getConnectionState(device); - String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - - switch (state) { - case BluetoothA2dp.STATE_DISCONNECTED: - case BluetoothA2dp.STATE_DISCONNECTING: - return false; - } - // State is CONNECTING or CONNECTED or PLAYING - handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING); - if (!disconnectSinkNative(path)) { - // Restore previous state - handleSinkStateChange(device, mAudioDevices.get(device), state); - return false; - } - return true; - } - - public synchronized boolean suspendSink(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState); - if (device == null || mAudioDevices == null) { - return false; - } - String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - Integer state = mAudioDevices.get(device); - if (path == null || state == null) { - return false; - } - - mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED; - return checkSinkSuspendState(state.intValue()); - } - - public synchronized boolean resumeSink(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState); - if (device == null || mAudioDevices == null) { - return false; - } - String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - Integer state = mAudioDevices.get(device); - if (path == null || state == null) { - return false; - } - mTargetA2dpState = BluetoothA2dp.STATE_PLAYING; - return checkSinkSuspendState(state.intValue()); - } - - public synchronized int getConnectionState(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - Integer state = mAudioDevices.get(device); - if (state == null) - return BluetoothA2dp.STATE_DISCONNECTED; - return state; - } - - public synchronized List<BluetoothDevice> getConnectedDevices() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates( - new int[] {BluetoothA2dp.STATE_CONNECTED}); - return sinks; - } - - public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - ArrayList<BluetoothDevice> sinks = new ArrayList<BluetoothDevice>(); - for (BluetoothDevice device: mAudioDevices.keySet()) { - int sinkState = getConnectionState(device); - for (int state : states) { - if (state == sinkState) { - sinks.add(device); - break; - } - } - } - return sinks; - } - - public synchronized int getPriority(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), - BluetoothA2dp.PRIORITY_UNDEFINED); - } - - public synchronized boolean setPriority(BluetoothDevice device, int priority) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - return Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority); - } - - public synchronized boolean allowIncomingConnect(BluetoothDevice device, boolean value) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - String address = device.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - Integer data = mBluetoothService.getAuthorizationAgentRequestData(address); - if (data == null) { - Log.w(TAG, "allowIncomingConnect(" + device + ") called but no native data available"); - return false; - } - log("allowIncomingConnect: A2DP: " + device + ":" + value); - return mBluetoothService.setAuthorizationNative(address, value, data.intValue()); - } - - /** - * Called by native code on a PropertyChanged signal from - * org.bluez.AudioSink. - * - * @param path the object path for the changed device - * @param propValues a string array containing the key and one or more - * values. - */ - private synchronized void onSinkPropertyChanged(String path, String[] propValues) { - if (!mBluetoothService.isEnabled()) { - return; - } - - String name = propValues[0]; - String address = mBluetoothService.getAddressFromObjectPath(path); - if (address == null) { - Log.e(TAG, "onSinkPropertyChanged: Address of the remote device in null"); - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - - if (name.equals(PROPERTY_STATE)) { - int state = convertBluezSinkStringToState(propValues[1]); - log("A2DP: onSinkPropertyChanged newState is: " + state + "mPlayingA2dpDevice: " + mPlayingA2dpDevice); - - if (mAudioDevices.get(device) == null) { - // This is for an incoming connection for a device not known to us. - // We have authorized it and bluez state has changed. - addAudioSink(device); - handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTED, state); - } else { - if (state == BluetoothA2dp.STATE_PLAYING && mPlayingA2dpDevice == null) { - mPlayingA2dpDevice = device; - handleSinkPlayingStateChange(device, state, BluetoothA2dp.STATE_NOT_PLAYING); - } else if (state == BluetoothA2dp.STATE_CONNECTED && mPlayingA2dpDevice != null) { - mPlayingA2dpDevice = null; - handleSinkPlayingStateChange(device, BluetoothA2dp.STATE_NOT_PLAYING, - BluetoothA2dp.STATE_PLAYING); - } else { - mPlayingA2dpDevice = null; - int prevState = mAudioDevices.get(device); - handleSinkStateChange(device, prevState, state); - } - } - } - } - - private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) { - if (state != prevState) { - mAudioDevices.put(device, state); - - checkSinkSuspendState(state); - mTargetA2dpState = -1; - - if (getPriority(device) > BluetoothA2dp.PRIORITY_OFF && - state == BluetoothA2dp.STATE_CONNECTED) { - // We have connected or attempting to connect. - // Bump priority - setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); - // We will only have 1 device with AUTO_CONNECT priority - // To be backward compatible set everyone else to have PRIORITY_ON - adjustOtherSinkPriorities(device); - } - - int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, state); - - mWakeLock.acquire(); - mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage( - MSG_CONNECTION_STATE_CHANGED, - prevState, - state, - device), - delay); - } - } - - private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) { - Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothProfile.EXTRA_STATE, state); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - - if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state); - } - - private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { - for (BluetoothDevice device : mAdapter.getBondedDevices()) { - if (getPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT && - !device.equals(connectedDevice)) { - setPriority(device, BluetoothA2dp.PRIORITY_ON); - } - } - } - - private boolean checkSinkSuspendState(int state) { - boolean result = true; - - if (state != mTargetA2dpState) { - if (state == BluetoothA2dp.STATE_PLAYING && - mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) { - mAudioManager.setParameters("A2dpSuspended=true"); - } else if (state == BluetoothA2dp.STATE_CONNECTED && - mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) { - mAudioManager.setParameters("A2dpSuspended=false"); - } else { - result = false; - } - } - return result; - } - - /** - * Called by native code for the async response to a Connect - * method call to org.bluez.AudioSink. - * - * @param deviceObjectPath the object path for the connecting device - * @param result true on success; false on error - */ - private void onConnectSinkResult(String deviceObjectPath, boolean result) { - // If the call was a success, ignore we will update the state - // when we a Sink Property Change - if (!result) { - if (deviceObjectPath != null) { - String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); - if (address == null) return; - BluetoothDevice device = mAdapter.getRemoteDevice(address); - int state = getConnectionState(device); - handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); - } - } - } - - /** Handles A2DP connection state change intent broadcasts. */ - private class IntentBroadcastHandler extends Handler { - - private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) { - Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothProfile.EXTRA_STATE, state); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - - if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); - - mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.A2DP, state, - prevState); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_CONNECTION_STATE_CHANGED: - onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2); - mWakeLock.release(); - break; - } - } - } - - @Override - protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); - - if (mAudioDevices.isEmpty()) return; - pw.println("Cached audio devices:"); - for (BluetoothDevice device : mAudioDevices.keySet()) { - int state = mAudioDevices.get(device); - pw.println(device + " " + BluetoothA2dp.stateToString(state)); - } - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private native boolean initNative(); - private native void cleanupNative(); - private synchronized native boolean connectSinkNative(String path); - private synchronized native boolean disconnectSinkNative(String path); - private synchronized native boolean suspendSinkNative(String path); - private synchronized native boolean resumeSinkNative(String path); - private synchronized native Object []getSinkPropertiesNative(String path); - private synchronized native boolean avrcpVolumeUpNative(String path); - private synchronized native boolean avrcpVolumeDownNative(String path); -} diff --git a/core/java/android/server/BluetoothAdapterProperties.java b/core/java/android/server/BluetoothAdapterProperties.java deleted file mode 100644 index 9723f60..0000000 --- a/core/java/android/server/BluetoothAdapterProperties.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.server; - -import android.content.Context; -import android.util.Log; - -import java.util.HashMap; -import java.util.Map; - -class BluetoothAdapterProperties { - - private static final String TAG = "BluetoothAdapterProperties"; - - private final Map<String, String> mPropertiesMap; - private final Context mContext; - private final BluetoothService mService; - - BluetoothAdapterProperties(Context context, BluetoothService service) { - mPropertiesMap = new HashMap<String, String>(); - mContext = context; - mService = service; - } - - synchronized String getProperty(String name) { - if (mPropertiesMap.isEmpty()) { - getAllProperties(); - } - return mPropertiesMap.get(name); - } - - String getObjectPath() { - return getProperty("ObjectPath"); - } - - synchronized void clear() { - mPropertiesMap.clear(); - } - - synchronized boolean isEmpty() { - return mPropertiesMap.isEmpty(); - } - - synchronized void setProperty(String name, String value) { - mPropertiesMap.put(name, value); - } - - synchronized void getAllProperties() { - mContext.enforceCallingOrSelfPermission( - BluetoothService.BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - mPropertiesMap.clear(); - - String properties[] = (String[]) mService - .getAdapterPropertiesNative(); - // The String Array consists of key-value pairs. - if (properties == null) { - Log.e(TAG, "*Error*: GetAdapterProperties returned NULL"); - return; - } - - for (int i = 0; i < properties.length; i++) { - String name = properties[i]; - String newValue = null; - if (name == null) { - Log.e(TAG, "Error:Adapter Property at index " + i + " is null"); - continue; - } - if (name.equals("Devices") || name.equals("UUIDs")) { - StringBuilder str = new StringBuilder(); - int len = Integer.valueOf(properties[++i]); - for (int j = 0; j < len; j++) { - str.append(properties[++i]); - str.append(","); - } - if (len > 0) { - newValue = str.toString(); - } - } else { - newValue = properties[++i]; - } - mPropertiesMap.put(name, newValue); - } - - // Add adapter object path property. - String adapterPath = mService.getAdapterPathNative(); - if (adapterPath != null) { - mPropertiesMap.put("ObjectPath", adapterPath + "/dev_"); - } - } -} diff --git a/core/java/android/server/BluetoothAdapterStateMachine.java b/core/java/android/server/BluetoothAdapterStateMachine.java deleted file mode 100644 index 1de1839..0000000 --- a/core/java/android/server/BluetoothAdapterStateMachine.java +++ /dev/null @@ -1,822 +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.server; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.IBluetoothStateChangeCallback; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.os.Binder; -import android.os.Message; -import android.os.RemoteException; -import android.provider.Settings; -import android.util.Log; - -import com.android.internal.util.IState; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; - -import java.io.PrintWriter; - -/** - * Bluetooth Adapter StateMachine - * All the states are at the same level, ie, no hierarchy. - * (BluetootOn)<----------------------<- - * | ^ -------------------->- | - * | | | | - * USER_TURN_OFF | | SCAN_MODE_CHANGED m1 | | USER_TURN_ON - * AIRPLANE_MODE_ON | | | | - * V | | | - * (Switching) (PerProcessState) - * | ^ | | - * POWER_STATE_CHANGED & | | TURN_ON(_CONTINUE) | | - * ALL_DEVICES_DISCONNECTED | | m2 | | - * V |------------------------< | SCAN_MODE_CHANGED - * (HotOff)-------------------------->- PER_PROCESS_TURN_ON - * / ^ - * / | SERVICE_RECORD_LOADED - * | | - * TURN_COLD | (Warmup) - * \ ^ - * \ | TURN_HOT/TURN_ON - * | | AIRPLANE_MODE_OFF(when Bluetooth was on before) - * V | - * (PowerOff) <----- initial state - * - * Legend: - * m1 = TURN_HOT - * m2 = Transition to HotOff when number of process wanting BT on is 0. - * POWER_STATE_CHANGED will make the transition. - * Note: - * The diagram above shows all the states and messages that trigger normal state changes. - * The diagram above does not capture everything: - * The diagram does not capture following messages. - * - messages that do not trigger state changes - * For example, PER_PROCESS_TURN_ON received in BluetoothOn state - * - unhandled messages - * For example, USER_TURN_ON received in BluetoothOn state - * - timeout messages - * The diagram does not capture error conditions and state recoveries. - * - For example POWER_STATE_CHANGED received in BluetoothOn state - */ -final class BluetoothAdapterStateMachine extends StateMachine { - private static final String TAG = "BluetoothAdapterStateMachine"; - private static final boolean DBG = false; - - // Message(what) to take an action - // - // We get this message when user tries to turn on BT - static final int USER_TURN_ON = 1; - // We get this message when user tries to turn off BT - static final int USER_TURN_OFF = 2; - // Per process enable / disable messages - static final int PER_PROCESS_TURN_ON = 3; - static final int PER_PROCESS_TURN_OFF = 4; - - // Turn on Bluetooth Module, Load firmware, and do all the preparation - // needed to get the Bluetooth Module ready but keep it not discoverable - // and not connectable. This way the Bluetooth Module can be quickly - // switched on if needed - static final int TURN_HOT = 5; - - // Message(what) to report a event that the state machine need to respond to - // - // Event indicates sevice records have been loaded - static final int SERVICE_RECORD_LOADED = 51; - // Event indicates all the remote Bluetooth devices has been disconnected - static final int ALL_DEVICES_DISCONNECTED = 52; - // Event indicates the Bluetooth scan mode has changed - static final int SCAN_MODE_CHANGED = 53; - // Event indicates the powered state has changed - static final int POWER_STATE_CHANGED = 54; - // Event indicates airplane mode is turned on - static final int AIRPLANE_MODE_ON = 55; - // Event indicates airplane mode is turned off - static final int AIRPLANE_MODE_OFF = 56; - - // private internal messages - // - // USER_TURN_ON is changed to TURN_ON_CONTINUE after we broadcast the - // state change intent so that we will not broadcast the intent again in - // other state - private static final int TURN_ON_CONTINUE = 101; - // Unload firmware, turning off Bluetooth module power - private static final int TURN_COLD = 102; - // Device disconnecting timeout happens - private static final int DEVICES_DISCONNECT_TIMEOUT = 103; - // Prepare Bluetooth timeout happens - private static final int PREPARE_BLUETOOTH_TIMEOUT = 104; - // Bluetooth turn off wait timeout happens - private static final int TURN_OFF_TIMEOUT = 105; - // Bluetooth device power off wait timeout happens - private static final int POWER_DOWN_TIMEOUT = 106; - - private Context mContext; - private BluetoothService mBluetoothService; - private BluetoothEventLoop mEventLoop; - - private BluetoothOn mBluetoothOn; - private Switching mSwitching; - private HotOff mHotOff; - private WarmUp mWarmUp; - private PowerOff mPowerOff; - private PerProcessState mPerProcessState; - - // this is the BluetoothAdapter state that reported externally - private int mPublicState; - // When turning off, broadcast STATE_OFF in the last HotOff state - // This is because we do HotOff -> PowerOff -> HotOff for USER_TURN_OFF - private boolean mDelayBroadcastStateOff; - - // timeout value waiting for all the devices to be disconnected - private static final int DEVICES_DISCONNECT_TIMEOUT_TIME = 3000; - - private static final int PREPARE_BLUETOOTH_TIMEOUT_TIME = 10000; - - private static final int TURN_OFF_TIMEOUT_TIME = 5000; - private static final int POWER_DOWN_TIMEOUT_TIME = 20; - - BluetoothAdapterStateMachine(Context context, BluetoothService bluetoothService, - BluetoothAdapter bluetoothAdapter) { - super(TAG); - mContext = context; - mBluetoothService = bluetoothService; - mEventLoop = new BluetoothEventLoop(context, bluetoothAdapter, bluetoothService, this); - - mBluetoothOn = new BluetoothOn(); - mSwitching = new Switching(); - mHotOff = new HotOff(); - mWarmUp = new WarmUp(); - mPowerOff = new PowerOff(); - mPerProcessState = new PerProcessState(); - - addState(mBluetoothOn); - addState(mSwitching); - addState(mHotOff); - addState(mWarmUp); - addState(mPowerOff); - addState(mPerProcessState); - - setInitialState(mPowerOff); - mPublicState = BluetoothAdapter.STATE_OFF; - mDelayBroadcastStateOff = false; - } - - /** - * Bluetooth module's power is off, firmware is not loaded. - */ - private class PowerOff extends State { - @Override - public void enter() { - if (DBG) log("Enter PowerOff: " + getCurrentMessage().what); - } - @Override - public boolean processMessage(Message message) { - log("PowerOff process message: " + message.what); - - boolean retValue = HANDLED; - switch(message.what) { - case USER_TURN_ON: - // starts turning on BT module, broadcast this out - broadcastState(BluetoothAdapter.STATE_TURNING_ON); - transitionTo(mWarmUp); - if (prepareBluetooth()) { - // this is user request, save the setting - if ((Boolean) message.obj) { - persistSwitchSetting(true); - } - // We will continue turn the BT on all the way to the BluetoothOn state - deferMessage(obtainMessage(TURN_ON_CONTINUE)); - } else { - Log.e(TAG, "failed to prepare bluetooth, abort turning on"); - transitionTo(mPowerOff); - broadcastState(BluetoothAdapter.STATE_OFF); - } - break; - case TURN_HOT: - if (prepareBluetooth()) { - transitionTo(mWarmUp); - } - break; - case AIRPLANE_MODE_OFF: - if (getBluetoothPersistedSetting()) { - // starts turning on BT module, broadcast this out - broadcastState(BluetoothAdapter.STATE_TURNING_ON); - transitionTo(mWarmUp); - if (prepareBluetooth()) { - // We will continue turn the BT on all the way to the BluetoothOn state - deferMessage(obtainMessage(TURN_ON_CONTINUE)); - transitionTo(mWarmUp); - } else { - Log.e(TAG, "failed to prepare bluetooth, abort turning on"); - transitionTo(mPowerOff); - broadcastState(BluetoothAdapter.STATE_OFF); - } - } else if (mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - sendMessage(TURN_HOT); - } - break; - case PER_PROCESS_TURN_ON: - if (prepareBluetooth()) { - transitionTo(mWarmUp); - } - deferMessage(obtainMessage(PER_PROCESS_TURN_ON)); - break; - case PER_PROCESS_TURN_OFF: - perProcessCallback(false, (IBluetoothStateChangeCallback) message.obj); - break; - case USER_TURN_OFF: - Log.w(TAG, "PowerOff received: " + message.what); - case AIRPLANE_MODE_ON: // ignore - break; - default: - return NOT_HANDLED; - } - return retValue; - } - - /** - * Turn on Bluetooth Module, Load firmware, and do all the preparation - * needed to get the Bluetooth Module ready but keep it not discoverable - * and not connectable. - * The last step of this method sets up the local service record DB. - * There will be a event reporting the status of the SDP setup. - */ - private boolean prepareBluetooth() { - if (mBluetoothService.enableNative() != 0) { - return false; - } - - // try to start event loop, give 2 attempts - int retryCount = 2; - boolean eventLoopStarted = false; - while ((retryCount-- > 0) && !eventLoopStarted) { - mEventLoop.start(); - // it may take a moment for the other thread to do its - // thing. Check periodically for a while. - int pollCount = 5; - while ((pollCount-- > 0) && !eventLoopStarted) { - if (mEventLoop.isEventLoopRunning()) { - eventLoopStarted = true; - break; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - log("prepareBluetooth sleep interrupted: " + pollCount); - break; - } - } - } - - if (!eventLoopStarted) { - mBluetoothService.disableNative(); - return false; - } - - // get BluetoothService ready - if (!mBluetoothService.prepareBluetooth()) { - mEventLoop.stop(); - mBluetoothService.disableNative(); - return false; - } - - sendMessageDelayed(PREPARE_BLUETOOTH_TIMEOUT, PREPARE_BLUETOOTH_TIMEOUT_TIME); - return true; - } - } - - /** - * Turning on Bluetooth module's power, loading firmware, starting - * event loop thread to listen on Bluetooth module event changes. - */ - private class WarmUp extends State { - - @Override - public void enter() { - if (DBG) log("Enter WarmUp: " + getCurrentMessage().what); - } - - @Override - public boolean processMessage(Message message) { - log("WarmUp process message: " + message.what); - - boolean retValue = HANDLED; - switch(message.what) { - case SERVICE_RECORD_LOADED: - removeMessages(PREPARE_BLUETOOTH_TIMEOUT); - transitionTo(mHotOff); - if (mDelayBroadcastStateOff) { - broadcastState(BluetoothAdapter.STATE_OFF); - mDelayBroadcastStateOff = false; - } - break; - case PREPARE_BLUETOOTH_TIMEOUT: - Log.e(TAG, "Bluetooth adapter SDP failed to load"); - shutoffBluetooth(); - transitionTo(mPowerOff); - broadcastState(BluetoothAdapter.STATE_OFF); - break; - case USER_TURN_ON: // handle this at HotOff state - case TURN_ON_CONTINUE: // Once in HotOff state, continue turn bluetooth - // on to the BluetoothOn state - case AIRPLANE_MODE_ON: - case AIRPLANE_MODE_OFF: - case PER_PROCESS_TURN_ON: - case PER_PROCESS_TURN_OFF: - deferMessage(message); - break; - case USER_TURN_OFF: - Log.w(TAG, "WarmUp received: " + message.what); - break; - default: - return NOT_HANDLED; - } - return retValue; - } - - } - - /** - * Bluetooth Module has powered, firmware loaded, event loop started, - * SDP loaded, but the modules stays non-discoverable and - * non-connectable. - */ - private class HotOff extends State { - @Override - public void enter() { - if (DBG) log("Enter HotOff: " + getCurrentMessage().what); - } - - @Override - public boolean processMessage(Message message) { - log("HotOff process message: " + message.what); - - boolean retValue = HANDLED; - switch(message.what) { - case USER_TURN_ON: - broadcastState(BluetoothAdapter.STATE_TURNING_ON); - if ((Boolean) message.obj) { - persistSwitchSetting(true); - } - // let it fall to TURN_ON_CONTINUE: - //$FALL-THROUGH$ - case TURN_ON_CONTINUE: - mBluetoothService.switchConnectable(true); - transitionTo(mSwitching); - break; - case AIRPLANE_MODE_ON: - case TURN_COLD: - shutoffBluetooth(); - // we cannot go to power off state yet, we need wait for the Bluetooth - // device power off. Unfortunately the stack does not give a event back - // so we wait a little bit here - sendMessageDelayed(POWER_DOWN_TIMEOUT, - POWER_DOWN_TIMEOUT_TIME); - break; - case POWER_DOWN_TIMEOUT: - transitionTo(mPowerOff); - if (!mDelayBroadcastStateOff) { - broadcastState(BluetoothAdapter.STATE_OFF); - } - break; - case AIRPLANE_MODE_OFF: - if (getBluetoothPersistedSetting()) { - broadcastState(BluetoothAdapter.STATE_TURNING_ON); - transitionTo(mSwitching); - mBluetoothService.switchConnectable(true); - } - break; - case PER_PROCESS_TURN_ON: - transitionTo(mPerProcessState); - - // Resend the PER_PROCESS_TURN_ON message so that the callback - // can be sent through. - deferMessage(message); - - mBluetoothService.switchConnectable(true); - break; - case PER_PROCESS_TURN_OFF: - perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj); - break; - case USER_TURN_OFF: // ignore - break; - case POWER_STATE_CHANGED: - if ((Boolean) message.obj) { - recoverStateMachine(TURN_HOT, null); - } - break; - case TURN_HOT: - deferMessage(message); - break; - default: - return NOT_HANDLED; - } - return retValue; - } - - } - - private class Switching extends State { - - @Override - public void enter() { - if (DBG) log("Enter Switching: " + getCurrentMessage().what); - } - @Override - public boolean processMessage(Message message) { - log("Switching process message: " + message.what); - - boolean retValue = HANDLED; - switch(message.what) { - case SCAN_MODE_CHANGED: - // This event matches mBluetoothService.switchConnectable action - if (mPublicState == BluetoothAdapter.STATE_TURNING_ON) { - // set pairable if it's not - mBluetoothService.setPairable(); - mBluetoothService.initBluetoothAfterTurningOn(); - transitionTo(mBluetoothOn); - broadcastState(BluetoothAdapter.STATE_ON); - // run bluetooth now that it's turned on - // Note runBluetooth should be called only in adapter STATE_ON - mBluetoothService.runBluetooth(); - } - break; - case POWER_STATE_CHANGED: - removeMessages(TURN_OFF_TIMEOUT); - if (!((Boolean) message.obj)) { - if (mPublicState == BluetoothAdapter.STATE_TURNING_OFF) { - transitionTo(mHotOff); - mBluetoothService.finishDisable(); - mBluetoothService.cleanupAfterFinishDisable(); - deferMessage(obtainMessage(TURN_COLD)); - if (mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch) && - !mBluetoothService.isAirplaneModeOn()) { - deferMessage(obtainMessage(TURN_HOT)); - mDelayBroadcastStateOff = true; - } - } - } else { - if (mPublicState != BluetoothAdapter.STATE_TURNING_ON) { - if (mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - recoverStateMachine(TURN_HOT, null); - } else { - recoverStateMachine(TURN_COLD, null); - } - } - } - break; - case ALL_DEVICES_DISCONNECTED: - removeMessages(DEVICES_DISCONNECT_TIMEOUT); - mBluetoothService.switchConnectable(false); - sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); - break; - case DEVICES_DISCONNECT_TIMEOUT: - sendMessage(ALL_DEVICES_DISCONNECTED); - // reset the hardware for error recovery - Log.e(TAG, "Devices failed to disconnect, reseting..."); - deferMessage(obtainMessage(TURN_COLD)); - if (mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - deferMessage(obtainMessage(TURN_HOT)); - } - break; - case TURN_OFF_TIMEOUT: - transitionTo(mHotOff); - finishSwitchingOff(); - // reset the hardware for error recovery - Log.e(TAG, "Devices failed to power down, reseting..."); - deferMessage(obtainMessage(TURN_COLD)); - if (mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - deferMessage(obtainMessage(TURN_HOT)); - } - break; - case USER_TURN_ON: - case AIRPLANE_MODE_OFF: - case AIRPLANE_MODE_ON: - case PER_PROCESS_TURN_ON: - case PER_PROCESS_TURN_OFF: - case USER_TURN_OFF: - deferMessage(message); - break; - - default: - return NOT_HANDLED; - } - return retValue; - } - } - - private class BluetoothOn extends State { - - @Override - public void enter() { - if (DBG) log("Enter BluetoothOn: " + getCurrentMessage().what); - } - @Override - public boolean processMessage(Message message) { - log("BluetoothOn process message: " + message.what); - - boolean retValue = HANDLED; - switch(message.what) { - case USER_TURN_OFF: - if ((Boolean) message.obj) { - persistSwitchSetting(false); - } - - if (mBluetoothService.isDiscovering()) { - mBluetoothService.cancelDiscovery(); - } - if (!mBluetoothService.isApplicationStateChangeTrackerEmpty()) { - transitionTo(mPerProcessState); - deferMessage(obtainMessage(TURN_HOT)); - break; - } - //$FALL-THROUGH$ to AIRPLANE_MODE_ON - case AIRPLANE_MODE_ON: - broadcastState(BluetoothAdapter.STATE_TURNING_OFF); - transitionTo(mSwitching); - if (mBluetoothService.getAdapterConnectionState() != - BluetoothAdapter.STATE_DISCONNECTED) { - mBluetoothService.disconnectDevices(); - sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT, - DEVICES_DISCONNECT_TIMEOUT_TIME); - } else { - mBluetoothService.switchConnectable(false); - sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); - } - - if (message.what == AIRPLANE_MODE_ON || mBluetoothService.isAirplaneModeOn()) { - // We inform all the per process callbacks - allProcessesCallback(false); - } - break; - case AIRPLANE_MODE_OFF: - case USER_TURN_ON: - Log.w(TAG, "BluetoothOn received: " + message.what); - break; - case PER_PROCESS_TURN_ON: - perProcessCallback(true, (IBluetoothStateChangeCallback)message.obj); - break; - case PER_PROCESS_TURN_OFF: - perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj); - break; - case POWER_STATE_CHANGED: - if ((Boolean) message.obj) { - // reset the state machine and send it TURN_ON_CONTINUE message - recoverStateMachine(USER_TURN_ON, false); - } - break; - default: - return NOT_HANDLED; - } - return retValue; - } - - } - - - private class PerProcessState extends State { - IBluetoothStateChangeCallback mCallback = null; - boolean isTurningOn = false; - - @Override - public void enter() { - int what = getCurrentMessage().what; - if (DBG) log("Enter PerProcessState: " + what); - - if (what == PER_PROCESS_TURN_ON) { - isTurningOn = true; - } else if (what == USER_TURN_OFF) { - isTurningOn = false; - } else { - Log.e(TAG, "enter PerProcessState: wrong msg: " + what); - } - } - - @Override - public boolean processMessage(Message message) { - log("PerProcessState process message: " + message.what); - - boolean retValue = HANDLED; - switch (message.what) { - case PER_PROCESS_TURN_ON: - mCallback = (IBluetoothStateChangeCallback)getCurrentMessage().obj; - - // If this is not the first application call the callback. - if (mBluetoothService.getNumberOfApplicationStateChangeTrackers() > 1) { - perProcessCallback(true, mCallback); - } - break; - case SCAN_MODE_CHANGED: - if (isTurningOn) { - perProcessCallback(true, mCallback); - isTurningOn = false; - } - break; - case POWER_STATE_CHANGED: - removeMessages(TURN_OFF_TIMEOUT); - if (!((Boolean) message.obj)) { - transitionTo(mHotOff); - if (!mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - deferMessage(obtainMessage(TURN_COLD)); - } - } else { - if (!isTurningOn) { - recoverStateMachine(TURN_COLD, null); - for (IBluetoothStateChangeCallback c: - mBluetoothService.getApplicationStateChangeCallbacks()) { - perProcessCallback(false, c); - deferMessage(obtainMessage(PER_PROCESS_TURN_ON, c)); - } - } - } - break; - case TURN_OFF_TIMEOUT: - transitionTo(mHotOff); - Log.e(TAG, "Power-down timed out, resetting..."); - deferMessage(obtainMessage(TURN_COLD)); - if (mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - deferMessage(obtainMessage(TURN_HOT)); - } - break; - case USER_TURN_ON: - broadcastState(BluetoothAdapter.STATE_TURNING_ON); - persistSwitchSetting(true); - mBluetoothService.initBluetoothAfterTurningOn(); - transitionTo(mBluetoothOn); - broadcastState(BluetoothAdapter.STATE_ON); - // run bluetooth now that it's turned on - mBluetoothService.runBluetooth(); - break; - case TURN_HOT: - broadcastState(BluetoothAdapter.STATE_TURNING_OFF); - if (mBluetoothService.getAdapterConnectionState() != - BluetoothAdapter.STATE_DISCONNECTED) { - mBluetoothService.disconnectDevices(); - sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT, - DEVICES_DISCONNECT_TIMEOUT_TIME); - break; - } - //$FALL-THROUGH$ all devices are already disconnected - case ALL_DEVICES_DISCONNECTED: - removeMessages(DEVICES_DISCONNECT_TIMEOUT); - finishSwitchingOff(); - break; - case DEVICES_DISCONNECT_TIMEOUT: - finishSwitchingOff(); - Log.e(TAG, "Devices fail to disconnect, reseting..."); - transitionTo(mHotOff); - deferMessage(obtainMessage(TURN_COLD)); - for (IBluetoothStateChangeCallback c: - mBluetoothService.getApplicationStateChangeCallbacks()) { - perProcessCallback(false, c); - deferMessage(obtainMessage(PER_PROCESS_TURN_ON, c)); - } - break; - case PER_PROCESS_TURN_OFF: - perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj); - if (mBluetoothService.isApplicationStateChangeTrackerEmpty()) { - mBluetoothService.switchConnectable(false); - sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); - } - break; - case AIRPLANE_MODE_ON: - mBluetoothService.switchConnectable(false); - sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); - allProcessesCallback(false); - break; - case USER_TURN_OFF: - Log.w(TAG, "PerProcessState received: " + message.what); - break; - default: - return NOT_HANDLED; - } - return retValue; - } - } - - private void finishSwitchingOff() { - mBluetoothService.finishDisable(); - broadcastState(BluetoothAdapter.STATE_OFF); - mBluetoothService.cleanupAfterFinishDisable(); - } - - private void shutoffBluetooth() { - mBluetoothService.shutoffBluetooth(); - mEventLoop.stop(); - mBluetoothService.cleanNativeAfterShutoffBluetooth(); - } - - private void perProcessCallback(boolean on, IBluetoothStateChangeCallback c) { - if (c == null) return; - - try { - c.onBluetoothStateChange(on); - } catch (RemoteException e) {} - } - - private void allProcessesCallback(boolean on) { - for (IBluetoothStateChangeCallback c: - mBluetoothService.getApplicationStateChangeCallbacks()) { - perProcessCallback(on, c); - } - if (!on) { - mBluetoothService.clearApplicationStateChangeTracker(); - } - } - - /** - * Return the public BluetoothAdapter state - */ - int getBluetoothAdapterState() { - return mPublicState; - } - - BluetoothEventLoop getBluetoothEventLoop() { - return mEventLoop; - } - - private void persistSwitchSetting(boolean setOn) { - long origCallerIdentityToken = Binder.clearCallingIdentity(); - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.BLUETOOTH_ON, - setOn ? 1 : 0); - Binder.restoreCallingIdentity(origCallerIdentityToken); - } - - private boolean getBluetoothPersistedSetting() { - ContentResolver contentResolver = mContext.getContentResolver(); - return (Settings.Secure.getInt(contentResolver, - Settings.Secure.BLUETOOTH_ON, 0) > 0); - } - - private void broadcastState(int newState) { - - log("Bluetooth state " + mPublicState + " -> " + newState); - if (mPublicState == newState) { - return; - } - - Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mPublicState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mPublicState = newState; - - mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); - } - - /** - * bluetoothd has crashed and recovered, the adapter state machine has to - * reset itself and try to return to previous state - */ - private void recoverStateMachine(int what, Object obj) { - Log.e(TAG, "Get unexpected power on event, reset with: " + what); - transitionTo(mHotOff); - deferMessage(obtainMessage(TURN_COLD)); - deferMessage(obtainMessage(what, obj)); - } - - private void dump(PrintWriter pw) { - IState currentState = getCurrentState(); - if (currentState == mPowerOff) { - pw.println("Bluetooth OFF - power down\n"); - } else if (currentState == mWarmUp) { - pw.println("Bluetooth OFF - warm up\n"); - } else if (currentState == mHotOff) { - pw.println("Bluetooth OFF - hot but off\n"); - } else if (currentState == mSwitching) { - pw.println("Bluetooth Switching\n"); - } else if (currentState == mBluetoothOn) { - pw.println("Bluetooth ON\n"); - } else { - pw.println("ERROR: Bluetooth UNKNOWN STATE "); - } - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/server/BluetoothBondState.java b/core/java/android/server/BluetoothBondState.java deleted file mode 100644 index 0446f02..0000000 --- a/core/java/android/server/BluetoothBondState.java +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.server; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothHeadset; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.provider.Settings; -import android.util.Log; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.DataInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -/** - * Local cache of bonding state. - * We keep our own state to track the intermediate state BONDING, which - * bluez does not track. - * All addresses must be passed in upper case. - */ -class BluetoothBondState { - private static final String TAG = "BluetoothBondState"; - private static final boolean DBG = true; - - private final HashMap<String, Integer> mState = new HashMap<String, Integer>(); - private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>(); - - private static final String AUTO_PAIRING_BLACKLIST = - "/etc/bluetooth/auto_pairing.conf"; - private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST = - "/data/misc/bluetooth/dynamic_auto_pairing.conf"; - private ArrayList<String> mAutoPairingAddressBlacklist; - private ArrayList<String> mAutoPairingExactNameBlacklist; - private ArrayList<String> mAutoPairingPartialNameBlacklist; - private ArrayList<String> mAutoPairingFixedPinZerosKeyboardList; - // Addresses added to blacklist dynamically based on usage. - private ArrayList<String> mAutoPairingDynamicAddressBlacklist; - - // If this is an outgoing connection, store the address. - // There can be only 1 pending outgoing connection at a time, - private String mPendingOutgoingBonding; - - private final Context mContext; - private final BluetoothService mService; - private final BluetoothInputProfileHandler mBluetoothInputProfileHandler; - private BluetoothA2dp mA2dpProxy; - private BluetoothHeadset mHeadsetProxy; - - private ArrayList<String> mPairingRequestRcvd = new ArrayList<String>(); - - BluetoothBondState(Context context, BluetoothService service) { - mContext = context; - mService = service; - mBluetoothInputProfileHandler = - BluetoothInputProfileHandler.getInstance(mContext, mService); - - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); - mContext.registerReceiver(mReceiver, filter); - readAutoPairingData(); - } - - synchronized void setPendingOutgoingBonding(String address) { - mPendingOutgoingBonding = address; - } - - public synchronized String getPendingOutgoingBonding() { - return mPendingOutgoingBonding; - } - - public synchronized void initBondState() { - getProfileProxy(); - loadBondState(); - } - - private void loadBondState() { - if (mService.getBluetoothStateInternal() != - BluetoothAdapter.STATE_TURNING_ON) { - return; - } - String val = mService.getAdapterProperties().getProperty("Devices"); - if (val == null) { - return; - } - String[] bonds = val.split(","); - if (bonds == null) { - return; - } - mState.clear(); - if (DBG) Log.d(TAG, "found " + bonds.length + " bonded devices"); - for (String device : bonds) { - mState.put(mService.getAddressFromObjectPath(device).toUpperCase(), - BluetoothDevice.BOND_BONDED); - } - } - - public synchronized void setBondState(String address, int state) { - setBondState(address, state, 0); - } - - /** reason is ignored unless state == BOND_NOT_BONDED */ - public synchronized void setBondState(String address, int state, int reason) { - if (DBG) Log.d(TAG, "setBondState " + "address" + " " + state + "reason: " + reason); - - int oldState = getBondState(address); - if (oldState == state) { - return; - } - - // Check if this was a pending outgoing bonding. - // If yes, reset the state. - if (oldState == BluetoothDevice.BOND_BONDING) { - if (address.equals(mPendingOutgoingBonding)) { - mPendingOutgoingBonding = null; - } - } - - if (state == BluetoothDevice.BOND_BONDED) { - boolean setTrust = false; - if (mPairingRequestRcvd.contains(address)) setTrust = true; - - mService.addProfileState(address, setTrust); - mPairingRequestRcvd.remove(address); - - } else if (state == BluetoothDevice.BOND_BONDING) { - if (mA2dpProxy == null || mHeadsetProxy == null) { - getProfileProxy(); - } - } else if (state == BluetoothDevice.BOND_NONE) { - mPairingRequestRcvd.remove(address); - } - - setProfilePriorities(address, state); - - if (DBG) { - Log.d(TAG, address + " bond state " + oldState + " -> " + state - + " (" + reason + ")"); - } - Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mService.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state); - intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState); - if (state == BluetoothDevice.BOND_NONE) { - if (reason <= 0) { - Log.w(TAG, "setBondState() called to unbond device, but reason code is " + - "invalid. Overriding reason code with BOND_RESULT_REMOVED"); - reason = BluetoothDevice.UNBOND_REASON_REMOVED; - } - intent.putExtra(BluetoothDevice.EXTRA_REASON, reason); - mState.remove(address); - } else { - mState.put(address, state); - } - - mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); - } - - public boolean isAutoPairingBlacklisted(String address) { - if (mAutoPairingAddressBlacklist != null) { - for (String blacklistAddress : mAutoPairingAddressBlacklist) { - if (address.startsWith(blacklistAddress)) return true; - } - } - - if (mAutoPairingDynamicAddressBlacklist != null) { - for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) { - if (address.equals(blacklistAddress)) return true; - } - } - - String name = mService.getRemoteName(address); - if (name != null) { - if (mAutoPairingExactNameBlacklist != null) { - for (String blacklistName : mAutoPairingExactNameBlacklist) { - if (name.equals(blacklistName)) return true; - } - } - - if (mAutoPairingPartialNameBlacklist != null) { - for (String blacklistName : mAutoPairingPartialNameBlacklist) { - if (name.startsWith(blacklistName)) return true; - } - } - } - return false; - } - - public boolean isFixedPinZerosAutoPairKeyboard(String address) { - // Note: the meaning of blacklist is reversed in this case. - // If its in the list, we can go ahead and auto pair since - // by default keyboard should have a variable PIN that we don't - // auto pair using 0000. - if (mAutoPairingFixedPinZerosKeyboardList != null) { - for (String blacklistAddress : mAutoPairingFixedPinZerosKeyboardList) { - if (address.startsWith(blacklistAddress)) return true; - } - } - return false; - } - - public synchronized int getBondState(String address) { - Integer state = mState.get(address); - if (state == null) { - return BluetoothDevice.BOND_NONE; - } - return state.intValue(); - } - - /*package*/ synchronized String[] listInState(int state) { - ArrayList<String> result = new ArrayList<String>(mState.size()); - for (Map.Entry<String, Integer> e : mState.entrySet()) { - if (e.getValue().intValue() == state) { - result.add(e.getKey()); - } - } - return result.toArray(new String[result.size()]); - } - - public synchronized void addAutoPairingFailure(String address) { - if (mAutoPairingDynamicAddressBlacklist == null) { - mAutoPairingDynamicAddressBlacklist = new ArrayList<String>(); - } - - updateAutoPairingData(address); - mAutoPairingDynamicAddressBlacklist.add(address); - } - - public synchronized boolean isAutoPairingAttemptsInProgress(String address) { - return getAttempt(address) != 0; - } - - public synchronized void clearPinAttempts(String address) { - if (DBG) Log.d(TAG, "clearPinAttempts: " + address); - - mPinAttempt.remove(address); - } - - public synchronized boolean hasAutoPairingFailed(String address) { - if (mAutoPairingDynamicAddressBlacklist == null) return false; - - return mAutoPairingDynamicAddressBlacklist.contains(address); - } - - public synchronized int getAttempt(String address) { - Integer attempt = mPinAttempt.get(address); - if (attempt == null) { - return 0; - } - return attempt.intValue(); - } - - public synchronized void attempt(String address) { - Integer attempt = mPinAttempt.get(address); - int newAttempt; - if (attempt == null) { - newAttempt = 1; - } else { - newAttempt = attempt.intValue() + 1; - } - if (DBG) Log.d(TAG, "attemp newAttempt: " + newAttempt); - - mPinAttempt.put(address, new Integer(newAttempt)); - } - - private void getProfileProxy() { - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (mA2dpProxy == null) { - bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener, - BluetoothProfile.A2DP); - } - - if (mHeadsetProxy == null) { - bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener, - BluetoothProfile.HEADSET); - } - } - - private void closeProfileProxy() { - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (mA2dpProxy != null) { - bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dpProxy); - } - - if (mHeadsetProxy != null) { - bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetProxy); - } - } - - private BluetoothProfile.ServiceListener mProfileServiceListener = - new BluetoothProfile.ServiceListener() { - - public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (profile == BluetoothProfile.A2DP) { - mA2dpProxy = (BluetoothA2dp) proxy; - } else if (profile == BluetoothProfile.HEADSET) { - mHeadsetProxy = (BluetoothHeadset) proxy; - } - } - - public void onServiceDisconnected(int profile) { - if (profile == BluetoothProfile.A2DP) { - mA2dpProxy = null; - } else if (profile == BluetoothProfile.HEADSET) { - mHeadsetProxy = null; - } - } - }; - - private void copyAutoPairingData() { - FileInputStream in = null; - FileOutputStream out = null; - try { - File file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST); - if (file.exists()) return; - - in = new FileInputStream(AUTO_PAIRING_BLACKLIST); - out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST); - - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - } catch (FileNotFoundException e) { - Log.e(TAG, "FileNotFoundException: copyAutoPairingData " + e); - } catch (IOException e) { - Log.e(TAG, "IOException: copyAutoPairingData " + e); - } finally { - try { - if (in != null) in.close(); - if (out != null) out.close(); - } catch (IOException e) {} - } - } - - synchronized public void readAutoPairingData() { - if (mAutoPairingAddressBlacklist != null) return; - copyAutoPairingData(); - FileInputStream fstream = null; - try { - fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST); - DataInputStream in = new DataInputStream(fstream); - BufferedReader file = new BufferedReader(new InputStreamReader(in)); - String line; - while((line = file.readLine()) != null) { - line = line.trim(); - if (line.length() == 0 || line.startsWith("//")) continue; - String[] value = line.split("="); - if (value != null && value.length == 2) { - String[] val = value[1].split(","); - if (value[0].equalsIgnoreCase("AddressBlacklist")) { - mAutoPairingAddressBlacklist = - new ArrayList<String>(Arrays.asList(val)); - } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) { - mAutoPairingExactNameBlacklist = - new ArrayList<String>(Arrays.asList(val)); - } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) { - mAutoPairingPartialNameBlacklist = - new ArrayList<String>(Arrays.asList(val)); - } else if (value[0].equalsIgnoreCase("FixedPinZerosKeyboardBlacklist")) { - mAutoPairingFixedPinZerosKeyboardList = - new ArrayList<String>(Arrays.asList(val)); - } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) { - mAutoPairingDynamicAddressBlacklist = - new ArrayList<String>(Arrays.asList(val)); - } else { - Log.e(TAG, "Error parsing Auto pairing blacklist file"); - } - } - } - } catch (FileNotFoundException e) { - Log.e(TAG, "FileNotFoundException: readAutoPairingData " + e); - } catch (IOException e) { - Log.e(TAG, "IOException: readAutoPairingData " + e); - } finally { - if (fstream != null) { - try { - fstream.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - - // This function adds a bluetooth address to the auto pairing blacklist - // file. These addresses are added to DynamicAddressBlacklistSection - private void updateAutoPairingData(String address) { - BufferedWriter out = null; - try { - out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true)); - StringBuilder str = new StringBuilder(); - if (mAutoPairingDynamicAddressBlacklist.size() == 0) { - str.append("DynamicAddressBlacklist="); - } - str.append(address); - str.append(","); - out.write(str.toString()); - } catch (FileNotFoundException e) { - Log.e(TAG, "FileNotFoundException: updateAutoPairingData " + e); - } catch (IOException e) { - Log.e(TAG, "IOException: updateAutoPairingData " + e); - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - - // Set service priority of Hid, A2DP and Headset profiles depending on - // the bond state change - private void setProfilePriorities(String address, int state) { - BluetoothDevice remoteDevice = mService.getRemoteDevice(address); - // HID is handled by BluetoothService - mBluetoothInputProfileHandler.setInitialInputDevicePriority(remoteDevice, state); - - // Set service priority of A2DP and Headset - // We used to do the priority change in the 2 services after the broadcast - // intent reach them. But that left a small time gap that could reject - // incoming connection due to undefined priorities. - if (state == BluetoothDevice.BOND_BONDED) { - if (mA2dpProxy != null && - mA2dpProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) { - mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON); - } - - if (mHeadsetProxy != null && - mHeadsetProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) { - mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON); - } - } else if (state == BluetoothDevice.BOND_NONE) { - if (mA2dpProxy != null) { - mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED); - } - if (mHeadsetProxy != null) { - mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED); - } - } - - if (mA2dpProxy == null || mHeadsetProxy == null) { - Log.e(TAG, "Proxy is null:" + mA2dpProxy + ":" + mHeadsetProxy); - } - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent == null) return; - - String action = intent.getAction(); - if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { - BluetoothDevice dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - String address = dev.getAddress(); - mPairingRequestRcvd.add(address); - } - } - }; -} diff --git a/core/java/android/server/BluetoothDeviceProperties.java b/core/java/android/server/BluetoothDeviceProperties.java deleted file mode 100644 index fe3ef79..0000000 --- a/core/java/android/server/BluetoothDeviceProperties.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.server; - -import android.os.ParcelUuid; -import android.util.Log; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -class BluetoothDeviceProperties { - - private static final String TAG = "BluetoothDeviceProperties"; - - private final HashMap<String, Map<String, String>> mPropertiesMap; - private final BluetoothService mService; - - BluetoothDeviceProperties(BluetoothService service) { - mPropertiesMap = new HashMap<String, Map<String, String>>(); - mService = service; - } - - Map<String, String> addProperties(String address, String[] properties) { - /* - * We get a DeviceFound signal every time RSSI changes or name changes. - * Don't create a new Map object every time. - */ - Map<String, String> propertyValues; - synchronized(mPropertiesMap) { - propertyValues = mPropertiesMap.get(address); - if (propertyValues == null) { - propertyValues = new HashMap<String, String>(); - } - - for (int i = 0; i < properties.length; i++) { - String name = properties[i]; - String newValue = null; - int len; - if (name == null) { - Log.e(TAG, "Error: Remote Device Property at index " - + i + " is null"); - continue; - } - if (name.equals("UUIDs") || name.equals("Nodes")) { - StringBuilder str = new StringBuilder(); - len = Integer.valueOf(properties[++i]); - for (int j = 0; j < len; j++) { - str.append(properties[++i]); - str.append(","); - } - if (len > 0) { - newValue = str.toString(); - } - } else { - newValue = properties[++i]; - } - - propertyValues.put(name, newValue); - } - mPropertiesMap.put(address, propertyValues); - } - - // We have added a new remote device or updated its properties. - // Also update the serviceChannel cache. - mService.updateDeviceServiceChannelCache(address); - return propertyValues; - } - - void setProperty(String address, String name, String value) { - synchronized(mPropertiesMap) { - Map <String, String> propVal = mPropertiesMap.get(address); - if (propVal != null) { - propVal.put(name, value); - mPropertiesMap.put(address, propVal); - } else { - Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address); - } - } - } - - boolean isInCache(String address) { - synchronized (mPropertiesMap) { - return (mPropertiesMap.get(address) != null); - } - } - - boolean isEmpty() { - synchronized (mPropertiesMap) { - return mPropertiesMap.isEmpty(); - } - } - - Set<String> keySet() { - synchronized (mPropertiesMap) { - return mPropertiesMap.keySet(); - } - } - - String getProperty(String address, String property) { - synchronized(mPropertiesMap) { - Map<String, String> properties = mPropertiesMap.get(address); - if (properties != null) { - return properties.get(property); - } else { - // Query for remote device properties, again. - // We will need to reload the cache when we switch Bluetooth on / off - // or if we crash. - properties = updateCache(address); - if (properties != null) { - return properties.get(property); - } - } - } - Log.e(TAG, "getRemoteDeviceProperty: " + property + " not present: " + address); - return null; - } - - Map<String, String> updateCache(String address) { - String[] propValues = mService.getRemoteDeviceProperties(address); - if (propValues != null) { - return addProperties(address, propValues); - } - return null; - } -} diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java deleted file mode 100644 index b758e7f..0000000 --- a/core/java/android/server/BluetoothEventLoop.java +++ /dev/null @@ -1,1067 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.server; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHealth; -import android.bluetooth.BluetoothInputDevice; -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothUuid; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Message; -import android.os.ParcelUuid; -import android.os.PowerManager; -import android.util.Log; - -import java.util.HashMap; -import java.util.List; - - -/** - * @hide - */ -class BluetoothEventLoop { - private static final String TAG = "BluetoothEventLoop"; - private static final boolean DBG = false; - - private int mNativeData; - private Thread mThread; - private boolean mStarted; - private boolean mInterrupted; - - private final HashMap<String, Integer> mPasskeyAgentRequestData; - private final HashMap<String, Integer> mAuthorizationAgentRequestData; - private final BluetoothService mBluetoothService; - private final BluetoothAdapter mAdapter; - private final BluetoothAdapterStateMachine mBluetoothState; - private BluetoothA2dp mA2dp; - private final Context mContext; - // The WakeLock is used for bringing up the LCD during a pairing request - // from remote device when Android is in Suspend state. - private PowerManager.WakeLock mWakeLock; - - private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 1; - private static final int EVENT_AGENT_CANCEL = 2; - - private static final int CREATE_DEVICE_ALREADY_EXISTS = 1; - private static final int CREATE_DEVICE_SUCCESS = 0; - private static final int CREATE_DEVICE_FAILED = -1; - - private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; - private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - String address = null; - switch (msg.what) { - case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT: - address = (String)msg.obj; - if (address != null) { - mBluetoothService.setPairingConfirmation(address, true); - } - break; - case EVENT_AGENT_CANCEL: - // Set the Bond State to BOND_NONE. - // We always have only 1 device in BONDING state. - String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING); - if (devices.length == 0) { - break; - } else if (devices.length > 1) { - Log.e(TAG, " There is more than one device in the Bonding State"); - break; - } - address = devices[0]; - mBluetoothService.setBondState(address, - BluetoothDevice.BOND_NONE, - BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED); - break; - } - } - }; - - static { classInitNative(); } - private static native void classInitNative(); - - /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, - BluetoothService bluetoothService, - BluetoothAdapterStateMachine bluetoothState) { - mBluetoothService = bluetoothService; - mContext = context; - mBluetoothState = bluetoothState; - mPasskeyAgentRequestData = new HashMap<String, Integer>(); - mAuthorizationAgentRequestData = new HashMap<String, Integer>(); - mAdapter = adapter; - //WakeLock instantiation in BluetoothEventLoop class - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP - | PowerManager.ON_AFTER_RELEASE, TAG); - mWakeLock.setReferenceCounted(false); - initializeNativeDataNative(); - } - - /*package*/ void getProfileProxy() { - mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP); - mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.INPUT_DEVICE); - } - - private BluetoothProfile.ServiceListener mProfileServiceListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (profile == BluetoothProfile.A2DP) { - mA2dp = (BluetoothA2dp) proxy; - } - } - public void onServiceDisconnected(int profile) { - if (profile == BluetoothProfile.A2DP) { - mA2dp = null; - } - } - }; - - - protected void finalize() throws Throwable { - try { - cleanupNativeDataNative(); - } finally { - super.finalize(); - } - } - - /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() { - return mPasskeyAgentRequestData; - } - - /* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() { - return mAuthorizationAgentRequestData; - } - - /* package */ void start() { - - if (!isEventLoopRunningNative()) { - if (DBG) log("Starting Event Loop thread"); - startEventLoopNative(); - } - } - - public void stop() { - if (isEventLoopRunningNative()) { - if (DBG) log("Stopping Event Loop thread"); - stopEventLoopNative(); - } - } - - public boolean isEventLoopRunning() { - return isEventLoopRunningNative(); - } - - private void addDevice(String address, String[] properties) { - BluetoothDeviceProperties deviceProperties = - mBluetoothService.getDeviceProperties(); - deviceProperties.addProperties(address, properties); - String rssi = deviceProperties.getProperty(address, "RSSI"); - String classValue = deviceProperties.getProperty(address, "Class"); - String name = deviceProperties.getProperty(address, "Name"); - short rssiValue; - // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE. - // If we accept the pairing, we will automatically show it at the top of the list. - if (rssi != null) { - rssiValue = (short)Integer.valueOf(rssi).intValue(); - } else { - rssiValue = Short.MIN_VALUE; - } - if (classValue != null) { - Intent intent = new Intent(BluetoothDevice.ACTION_FOUND); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_CLASS, - new BluetoothClass(Integer.valueOf(classValue))); - intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue); - intent.putExtra(BluetoothDevice.EXTRA_NAME, name); - - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } else { - log ("ClassValue: " + classValue + " for remote device: " + address + " is null"); - } - } - - /** - * Called by native code on a DeviceFound signal from org.bluez.Adapter. - * - * @param address the MAC address of the new device - * @param properties an array of property keys and value strings - * - * @see BluetoothDeviceProperties#addProperties(String, String[]) - */ - private void onDeviceFound(String address, String[] properties) { - if (properties == null) { - Log.e(TAG, "ERROR: Remote device properties are null"); - return; - } - addDevice(address, properties); - } - - /** - * Called by native code on a DeviceDisappeared signal from - * org.bluez.Adapter. - * - * @param address the MAC address of the disappeared device - */ - private void onDeviceDisappeared(String address) { - Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } - - /** - * Called by native code on a DisconnectRequested signal from - * org.bluez.Device. - * - * @param deviceObjectPath the object path for the disconnecting device - */ - private void onDeviceDisconnectRequested(String deviceObjectPath) { - String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); - if (address == null) { - Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null"); - return; - } - Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } - - /** - * Called by native code for the async response to a CreatePairedDevice - * method call to org.bluez.Adapter. - * - * @param address the MAC address of the device to pair - * @param result success or error result for the pairing operation - */ - private void onCreatePairedDeviceResult(String address, int result) { - address = address.toUpperCase(); - mBluetoothService.onCreatePairedDeviceResult(address, result); - } - - /** - * Called by native code on a DeviceCreated signal from org.bluez.Adapter. - * - * @param deviceObjectPath the object path for the created device - */ - private void onDeviceCreated(String deviceObjectPath) { - String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); - if (address == null) { - Log.e(TAG, "onDeviceCreated: device address null!" + " deviceObjectPath: " + - deviceObjectPath); - return; - } - if (!mBluetoothService.isRemoteDeviceInCache(address)) { - // Incoming connection, we haven't seen this device, add to cache. - String[] properties = mBluetoothService.getRemoteDeviceProperties(address); - if (properties != null) { - addDevice(address, properties); - } - } - } - - /** - * Called by native code on a DeviceRemoved signal from org.bluez.Adapter. - * - * @param deviceObjectPath the object path for the removed device - */ - private void onDeviceRemoved(String deviceObjectPath) { - String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); - if (address != null) { - mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE, - BluetoothDevice.UNBOND_REASON_REMOVED); - mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null); - } - } - - /** - * Called by native code on a PropertyChanged signal from - * org.bluez.Adapter. This method is also called from - * {@link BluetoothAdapterStateMachine} to set the "Pairable" - * property when Bluetooth is enabled. - * - * @param propValues a string array containing the key and one or more - * values. - */ - /*package*/ void onPropertyChanged(String[] propValues) { - BluetoothAdapterProperties adapterProperties = - mBluetoothService.getAdapterProperties(); - - if (adapterProperties.isEmpty()) { - // We have got a property change before - // we filled up our cache. - adapterProperties.getAllProperties(); - } - log("Property Changed: " + propValues[0] + " : " + propValues[1]); - String name = propValues[0]; - if (name.equals("Name")) { - adapterProperties.setProperty(name, propValues[1]); - Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } else if (name.equals("Pairable") || name.equals("Discoverable")) { - adapterProperties.setProperty(name, propValues[1]); - - if (name.equals("Discoverable")) { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SCAN_MODE_CHANGED); - } - - String pairable = name.equals("Pairable") ? propValues[1] : - adapterProperties.getProperty("Pairable"); - String discoverable = name.equals("Discoverable") ? propValues[1] : - adapterProperties.getProperty("Discoverable"); - - // This shouldn't happen, unless Adapter Properties are null. - if (pairable == null || discoverable == null) - return; - - int mode = BluetoothService.bluezStringToScanMode( - pairable.equals("true"), - discoverable.equals("true")); - if (mode >= 0) { - Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } - } else if (name.equals("Discovering")) { - Intent intent; - adapterProperties.setProperty(name, propValues[1]); - if (propValues[1].equals("true")) { - intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED); - } else { - // Stop the discovery. - mBluetoothService.cancelDiscovery(); - intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); - } - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } else if (name.equals("Devices") || name.equals("UUIDs")) { - String value = null; - int len = Integer.valueOf(propValues[1]); - if (len > 0) { - StringBuilder str = new StringBuilder(); - for (int i = 2; i < propValues.length; i++) { - str.append(propValues[i]); - str.append(","); - } - value = str.toString(); - } - adapterProperties.setProperty(name, value); - if (name.equals("UUIDs")) { - mBluetoothService.updateBluetoothState(value); - } - } else if (name.equals("Powered")) { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED, - propValues[1].equals("true") ? new Boolean(true) : new Boolean(false)); - } else if (name.equals("DiscoverableTimeout")) { - adapterProperties.setProperty(name, propValues[1]); - } - } - - /** - * Called by native code on a PropertyChanged signal from - * org.bluez.Device. - * - * @param deviceObjectPath the object path for the changed device - * @param propValues a string array containing the key and one or more - * values. - */ - private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) { - String name = propValues[0]; - String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); - if (address == null) { - Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null"); - return; - } - log("Device property changed: " + address + " property: " - + name + " value: " + propValues[1]); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (name.equals("Name")) { - mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); - Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } else if (name.equals("Alias")) { - mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); - Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } else if (name.equals("Class")) { - mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); - Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothDevice.EXTRA_CLASS, - new BluetoothClass(Integer.valueOf(propValues[1]))); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } else if (name.equals("Connected")) { - mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); - Intent intent = null; - if (propValues[1].equals("true")) { - intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); - // Set the link timeout to 8000 slots (5 sec timeout) - // for bluetooth docks. - if (mBluetoothService.isBluetoothDock(address)) { - mBluetoothService.setLinkTimeout(address, 8000); - } - } else { - intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); - } - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } else if (name.equals("UUIDs")) { - String uuid = null; - int len = Integer.valueOf(propValues[1]); - if (len > 0) { - StringBuilder str = new StringBuilder(); - for (int i = 2; i < propValues.length; i++) { - str.append(propValues[i]); - str.append(","); - } - uuid = str.toString(); - } - mBluetoothService.setRemoteDeviceProperty(address, name, uuid); - - // UUIDs have changed, query remote service channel and update cache. - mBluetoothService.updateDeviceServiceChannelCache(address); - - mBluetoothService.sendUuidIntent(address); - } else if (name.equals("Paired")) { - if (propValues[1].equals("true")) { - // If locally initiated pairing, we will - // not go to BOND_BONDED state until we have received a - // successful return value in onCreatePairedDeviceResult - if (null == mBluetoothService.getPendingOutgoingBonding()) { - mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED); - } - } else { - mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE); - mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false"); - } - } else if (name.equals("Trusted")) { - if (DBG) - log("set trust state succeeded, value is: " + propValues[1]); - mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); - } - } - - /** - * Called by native code on a PropertyChanged signal from - * org.bluez.Input. - * - * @param path the object path for the changed input device - * @param propValues a string array containing the key and one or more - * values. - */ - private void onInputDevicePropertyChanged(String path, String[] propValues) { - String address = mBluetoothService.getAddressFromObjectPath(path); - if (address == null) { - Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device is null"); - return; - } - log("Input Device : Name of Property is: " + propValues[0]); - boolean state = false; - if (propValues[1].equals("true")) { - state = true; - } - mBluetoothService.handleInputDevicePropertyChange(address, state); - } - - /** - * Called by native code on a PropertyChanged signal from - * org.bluez.Network. - * - * @param deviceObjectPath the object path for the changed PAN device - * @param propValues a string array containing the key and one or more - * values. - */ - private void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) { - String name = propValues[0]; - String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); - if (address == null) { - Log.e(TAG, "onPanDevicePropertyChanged: Address of the remote device in null"); - return; - } - if (DBG) { - log("Pan Device property changed: " + address + " property: " - + name + " value: "+ propValues[1]); - } - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (name.equals("Connected")) { - if (propValues[1].equals("false")) { - mBluetoothService.handlePanDeviceStateChange(device, - BluetoothPan.STATE_DISCONNECTED, - BluetoothPan.LOCAL_PANU_ROLE); - } - } else if (name.equals("Interface")) { - String iface = propValues[1]; - if (!iface.equals("")) { - mBluetoothService.handlePanDeviceStateChange(device, iface, - BluetoothPan.STATE_CONNECTED, - BluetoothPan.LOCAL_PANU_ROLE); - } - } - } - - private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) { - String address = mBluetoothService.getAddressFromObjectPath(objectPath); - if (address == null) { - Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " + - "returning null"); - return null; - } - address = address.toUpperCase(); - mPasskeyAgentRequestData.put(address, new Integer(nativeData)); - - if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) { - // shutdown path - mBluetoothService.cancelPairingUserInput(address); - return null; - } - // Set state to BONDING. For incoming connections it will be set here. - // For outgoing connections, it gets set when we call createBond. - // Also set it only when the state is not already Bonded, we can sometimes - // get an authorization request from the remote end if it doesn't have the link key - // while we still have it. - if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED) - mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING); - return address; - } - - /** - * Called by native code on a RequestPairingConsent method call to - * org.bluez.Agent. - * - * @param objectPath the path of the device to request pairing consent for - * @param nativeData a native pointer to the original D-Bus message - */ - private void onRequestPairingConsent(String objectPath, int nativeData) { - String address = checkPairingRequestAndGetAddress(objectPath, nativeData); - if (address == null) return; - - /* The link key will not be stored if the incoming request has MITM - * protection switched on. Unfortunately, some devices have MITM - * switched on even though their capabilities are NoInputNoOutput, - * so we may get this request many times. Also if we respond immediately, - * the other end is unable to handle it. Delay sending the message. - */ - if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) { - Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT); - message.obj = address; - mHandler.sendMessageDelayed(message, 1500); - return; - } - // Acquire wakelock during PIN code request to bring up LCD display - mWakeLock.acquire(); - Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.PAIRING_VARIANT_CONSENT); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - // Release wakelock to allow the LCD to go off after the PIN popup notification. - mWakeLock.release(); - return; - } - - /** - * Called by native code on a RequestConfirmation method call to - * org.bluez.Agent. - * - * @param objectPath the path of the device to confirm the passkey for - * @param passkey an integer containing the 6-digit passkey to confirm - * @param nativeData a native pointer to the original D-Bus message - */ - private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) { - String address = checkPairingRequestAndGetAddress(objectPath, nativeData); - if (address == null) return; - // Acquire wakelock during PIN code request to bring up LCD display - mWakeLock.acquire(); - Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - // Release wakelock to allow the LCD to go off after the PIN popup notification. - mWakeLock.release(); - return; - } - - /** - * Called by native code on a RequestPasskey method call to - * org.bluez.Agent. - * - * @param objectPath the path of the device requesting a passkey - * @param nativeData a native pointer to the original D-Bus message - */ - private void onRequestPasskey(String objectPath, int nativeData) { - String address = checkPairingRequestAndGetAddress(objectPath, nativeData); - if (address == null) return; - // Acquire wakelock during PIN code request to bring up LCD display - mWakeLock.acquire(); - Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.PAIRING_VARIANT_PASSKEY); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - // Release wakelock to allow the LCD to go off after the PIN popup notification. - mWakeLock.release(); - return; - } - - /** - * Called by native code on a RequestPinCode method call to - * org.bluez.Agent. - * - * @param objectPath the path of the device requesting a PIN code - * @param nativeData a native pointer to the original D-Bus message - */ - private void onRequestPinCode(String objectPath, int nativeData) { - String address = checkPairingRequestAndGetAddress(objectPath, nativeData); - if (address == null) return; - - String pendingOutgoingAddress = - mBluetoothService.getPendingOutgoingBonding(); - BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address)); - int btDeviceClass = btClass.getDeviceClass(); - - if (address.equals(pendingOutgoingAddress)) { - // we initiated the bonding - - // Check if its a dock - if (mBluetoothService.isBluetoothDock(address)) { - String pin = mBluetoothService.getDockPin(); - mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin)); - return; - } - - // try 0000 once if the device looks dumb - switch (btDeviceClass) { - case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: - case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: - case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: - case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: - case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: - if (mBluetoothService.attemptAutoPair(address)) return; - } - } - - if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || - btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) { - // Its a keyboard. Follow the HID spec recommendation of creating the - // passkey and displaying it to the user. If the keyboard doesn't follow - // the spec recommendation, check if the keyboard has a fixed PIN zero - // and pair. - if (mBluetoothService.isFixedPinZerosAutoPairKeyboard(address)) { - mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); - return; - } - - // Generate a variable PIN. This is not truly random but good enough. - int pin = (int) Math.floor(Math.random() * 10000); - sendDisplayPinIntent(address, pin); - return; - } - // Acquire wakelock during PIN code request to bring up LCD display - mWakeLock.acquire(); - Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - // Release wakelock to allow the LCD to go off after the PIN popup notification. - mWakeLock.release(); - return; - } - - /** - * Called by native code on a DisplayPasskey method call to - * org.bluez.Agent. - * - * @param objectPath the path of the device to display the passkey for - * @param passkey an integer containing the 6-digit passkey - * @param nativeData a native pointer to the original D-Bus message - */ - private void onDisplayPasskey(String objectPath, int passkey, int nativeData) { - String address = checkPairingRequestAndGetAddress(objectPath, nativeData); - if (address == null) return; - - // Acquire wakelock during PIN code request to bring up LCD display - mWakeLock.acquire(); - Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - //Release wakelock to allow the LCD to go off after the PIN popup notification. - mWakeLock.release(); - } - - private void sendDisplayPinIntent(String address, int pin) { - // Acquire wakelock during PIN code request to bring up LCD display - mWakeLock.acquire(); - Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - //Release wakelock to allow the LCD to go off after the PIN popup notifcation. - mWakeLock.release(); - } - - /** - * Called by native code on a RequestOobData method call to - * org.bluez.Agent. - * - * @param objectPath the path of the device requesting OOB data - * @param nativeData a native pointer to the original D-Bus message - */ - private void onRequestOobData(String objectPath, int nativeData) { - String address = checkPairingRequestAndGetAddress(objectPath, nativeData); - if (address == null) return; - - Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - } - - /** - * Called by native code on an Authorize method call to org.bluez.Agent. - * - * @param objectPath the path of the device requesting to be authorized - * @param deviceUuid the UUID of the requesting device - * @param nativeData reference for native data - */ - private void onAgentAuthorize(String objectPath, String deviceUuid, int nativeData) { - if (!mBluetoothService.isEnabled()) return; - - String address = mBluetoothService.getAddressFromObjectPath(objectPath); - if (address == null) { - Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize"); - return; - } - - boolean authorized = false; - ParcelUuid uuid = ParcelUuid.fromString(deviceUuid); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - mAuthorizationAgentRequestData.put(address, new Integer(nativeData)); - - // Bluez sends the UUID of the local service being accessed, _not_ the - // remote service - if (mA2dp != null && - (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) - || BluetoothUuid.isAdvAudioDist(uuid)) && - !isOtherSinkInNonDisconnectedState(address)) { - authorized = mA2dp.getPriority(device) > BluetoothProfile.PRIORITY_OFF; - if (authorized && !BluetoothUuid.isAvrcpTarget(uuid)) { - Log.i(TAG, "First check pass for incoming A2DP / AVRCP connection from " + address); - // Some headsets try to connect AVCTP before AVDTP - against the recommendation - // If AVCTP connection fails, we get stuck in IncomingA2DP state in the state - // machine. We don't handle AVCTP signals currently. We only send - // intents for AVDTP state changes. We need to handle both of them in - // some cases. For now, just don't move to incoming state in this case. - mBluetoothService.notifyIncomingA2dpConnection(address, false); - } else { - Log.i(TAG, "" + authorized + - "Incoming A2DP / AVRCP connection from " + address); - mA2dp.allowIncomingConnect(device, authorized); - mBluetoothService.notifyIncomingA2dpConnection(address, true); - } - } else if (BluetoothUuid.isInputDevice(uuid)) { - // We can have more than 1 input device connected. - authorized = mBluetoothService.getInputDevicePriority(device) > - BluetoothInputDevice.PRIORITY_OFF; - if (authorized) { - Log.i(TAG, "First check pass for incoming HID connection from " + address); - // notify profile state change - mBluetoothService.notifyIncomingHidConnection(address); - } else { - Log.i(TAG, "Rejecting incoming HID connection from " + address); - mBluetoothService.allowIncomingProfileConnect(device, authorized); - } - } else if (BluetoothUuid.isBnep(uuid)) { - // PAN doesn't go to the state machine, accept or reject from here - authorized = mBluetoothService.allowIncomingTethering(); - mBluetoothService.allowIncomingProfileConnect(device, authorized); - } else { - Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address); - mBluetoothService.allowIncomingProfileConnect(device, authorized); - } - log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized); - } - - private boolean onAgentOutOfBandDataAvailable(String objectPath) { - if (!mBluetoothService.isEnabled()) return false; - - String address = mBluetoothService.getAddressFromObjectPath(objectPath); - if (address == null) return false; - - if (mBluetoothService.getDeviceOutOfBandData( - mAdapter.getRemoteDevice(address)) != null) { - return true; - } - return false; - } - - private boolean isOtherSinkInNonDisconnectedState(String address) { - List<BluetoothDevice> devices = - mA2dp.getDevicesMatchingConnectionStates(new int[] {BluetoothA2dp.STATE_CONNECTED, - BluetoothA2dp.STATE_CONNECTING, - BluetoothA2dp.STATE_DISCONNECTING}); - - if (devices.size() == 0) return false; - for (BluetoothDevice dev: devices) { - if (!dev.getAddress().equals(address)) return true; - } - return false; - } - - /** - * Called by native code on a Cancel method call to org.bluez.Agent. - */ - private void onAgentCancel() { - Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - - mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL), - 1500); - - return; - } - - /** - * Called by native code for the async response to a DiscoverServices - * method call to org.bluez.Adapter. - * - * @param deviceObjectPath the path for the specified device - * @param result true for success; false on error - */ - private void onDiscoverServicesResult(String deviceObjectPath, boolean result) { - String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); - if (address == null) return; - - // We don't parse the xml here, instead just query Bluez for the properties. - if (result) { - mBluetoothService.updateRemoteDevicePropertiesCache(address); - } - mBluetoothService.sendUuidIntent(address); - mBluetoothService.makeServiceChannelCallbacks(address); - } - - /** - * Called by native code for the async response to a CreateDevice - * method call to org.bluez.Adapter. - * - * @param address the MAC address of the device to create - * @param result {@link #CREATE_DEVICE_SUCCESS}, - * {@link #CREATE_DEVICE_ALREADY_EXISTS} or {@link #CREATE_DEVICE_FAILED}} - */ - private void onCreateDeviceResult(String address, int result) { - if (DBG) log("Result of onCreateDeviceResult:" + result); - - switch (result) { - case CREATE_DEVICE_ALREADY_EXISTS: - String path = mBluetoothService.getObjectPathFromAddress(address); - if (path != null) { - mBluetoothService.discoverServicesNative(path, ""); - break; - } - Log.w(TAG, "Device exists, but we don't have the bluez path, failing"); - // fall-through - case CREATE_DEVICE_FAILED: - mBluetoothService.sendUuidIntent(address); - mBluetoothService.makeServiceChannelCallbacks(address); - break; - case CREATE_DEVICE_SUCCESS: - // nothing to do, UUID intent's will be sent via property changed - } - } - - /** - * Called by native code for the async response to a Connect - * method call to org.bluez.Input. - * - * @param path the path of the specified input device - * @param result Result code of the operation. - */ - private void onInputDeviceConnectionResult(String path, int result) { - // Success case gets handled by Property Change signal - if (result != BluetoothInputDevice.INPUT_OPERATION_SUCCESS) { - String address = mBluetoothService.getAddressFromObjectPath(path); - if (address == null) return; - - boolean connected = false; - BluetoothDevice device = mAdapter.getRemoteDevice(address); - int state = mBluetoothService.getInputDeviceConnectionState(device); - if (state == BluetoothInputDevice.STATE_CONNECTING) { - if (result == BluetoothInputDevice.INPUT_CONNECT_FAILED_ALREADY_CONNECTED) { - connected = true; - } else { - connected = false; - } - } else if (state == BluetoothInputDevice.STATE_DISCONNECTING) { - if (result == BluetoothInputDevice.INPUT_DISCONNECT_FAILED_NOT_CONNECTED) { - connected = false; - } else { - // There is no better way to handle this, this shouldn't happen - connected = true; - } - } else { - Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state); - } - mBluetoothService.handleInputDevicePropertyChange(address, connected); - } - } - - /** - * Called by native code for the async response to a Connect - * method call to org.bluez.Network. - * - * @param path the path of the specified PAN device - * @param result Result code of the operation. - */ - private void onPanDeviceConnectionResult(String path, int result) { - log ("onPanDeviceConnectionResult " + path + " " + result); - // Success case gets handled by Property Change signal - if (result != BluetoothPan.PAN_OPERATION_SUCCESS) { - String address = mBluetoothService.getAddressFromObjectPath(path); - if (address == null) return; - - boolean connected = false; - BluetoothDevice device = mAdapter.getRemoteDevice(address); - int state = mBluetoothService.getPanDeviceConnectionState(device); - if (state == BluetoothPan.STATE_CONNECTING) { - if (result == BluetoothPan.PAN_CONNECT_FAILED_ALREADY_CONNECTED) { - connected = true; - } else { - connected = false; - } - } else if (state == BluetoothPan.STATE_DISCONNECTING) { - if (result == BluetoothPan.PAN_DISCONNECT_FAILED_NOT_CONNECTED) { - connected = false; - } else { - // There is no better way to handle this, this shouldn't happen - connected = true; - } - } else { - Log.e(TAG, "Error onPanDeviceConnectionResult. State is: " - + state + " result: "+ result); - } - int newState = connected? BluetoothPan.STATE_CONNECTED : - BluetoothPan.STATE_DISCONNECTED; - mBluetoothService.handlePanDeviceStateChange(device, newState, - BluetoothPan.LOCAL_PANU_ROLE); - } - } - - /** - * Called by native code for the async response to a Connect - * method call to org.bluez.Health - * - * @param chanCode The internal id of the channel - * @param result Result code of the operation. - */ - private void onHealthDeviceConnectionResult(int chanCode, int result) { - log ("onHealthDeviceConnectionResult " + chanCode + " " + result); - // Success case gets handled by Property Change signal - if (result != BluetoothHealth.HEALTH_OPERATION_SUCCESS) { - mBluetoothService.onHealthDeviceChannelConnectionError(chanCode, - BluetoothHealth.STATE_CHANNEL_DISCONNECTED); - } - } - - /** - * Called by native code on a DeviceDisconnected signal from - * org.bluez.NetworkServer. - * - * @param address the MAC address of the disconnected device - */ - private void onNetworkDeviceDisconnected(String address) { - BluetoothDevice device = mAdapter.getRemoteDevice(address); - mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED, - BluetoothPan.LOCAL_NAP_ROLE); - } - - /** - * Called by native code on a DeviceConnected signal from - * org.bluez.NetworkServer. - * - * @param address the MAC address of the connected device - * @param iface interface of remote network - * @param destUuid unused UUID parameter - */ - private void onNetworkDeviceConnected(String address, String iface, int destUuid) { - BluetoothDevice device = mAdapter.getRemoteDevice(address); - mBluetoothService.handlePanDeviceStateChange(device, iface, BluetoothPan.STATE_CONNECTED, - BluetoothPan.LOCAL_NAP_ROLE); - } - - /** - * Called by native code on a PropertyChanged signal from - * org.bluez.HealthDevice. - * - * @param devicePath the object path of the remote device - * @param propValues Properties (Name-Value) of the Health Device. - */ - private void onHealthDevicePropertyChanged(String devicePath, String[] propValues) { - log("Health Device : Name of Property is: " + propValues[0] + " Value:" + propValues[1]); - mBluetoothService.onHealthDevicePropertyChanged(devicePath, propValues[1]); - } - - /** - * Called by native code on a ChannelCreated/Deleted signal from - * org.bluez.HealthDevice. - * - * @param devicePath the object path of the remote device - * @param channelPath the path of the health channel. - * @param exists Boolean to indicate if the channel was created or deleted. - */ - private void onHealthDeviceChannelChanged(String devicePath, String channelPath, - boolean exists) { - log("Health Device : devicePath: " + devicePath + ":channelPath:" + channelPath + - ":exists" + exists); - mBluetoothService.onHealthDeviceChannelChanged(devicePath, channelPath, exists); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private native void initializeNativeDataNative(); - private native void startEventLoopNative(); - private native void stopEventLoopNative(); - private native boolean isEventLoopRunningNative(); - private native void cleanupNativeDataNative(); -} diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java deleted file mode 100644 index 5e93b81..0000000 --- a/core/java/android/server/BluetoothHealthProfileHandler.java +++ /dev/null @@ -1,671 +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.server; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHealth; -import android.bluetooth.BluetoothHealthAppConfiguration; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.IBluetoothHealthCallback; -import android.content.Context; -import android.os.Handler; -import android.os.Message; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.util.Log; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * This handles all the operations on the Bluetooth Health profile. - * All functions are called by BluetoothService, as Bluetooth Service - * is the Service handler for the HDP profile. - * - * @hide - */ -final class BluetoothHealthProfileHandler { - private static final String TAG = "BluetoothHealthProfileHandler"; - private static final boolean DBG = false; - - private static BluetoothHealthProfileHandler sInstance; - private BluetoothService mBluetoothService; - private ArrayList<HealthChannel> mHealthChannels; - private HashMap <BluetoothHealthAppConfiguration, String> mHealthAppConfigs; - private HashMap <BluetoothDevice, Integer> mHealthDevices; - private HashMap <BluetoothHealthAppConfiguration, IBluetoothHealthCallback> mCallbacks; - - private static final int MESSAGE_REGISTER_APPLICATION = 0; - private static final int MESSAGE_UNREGISTER_APPLICATION = 1; - private static final int MESSAGE_CONNECT_CHANNEL = 2; - private static final AtomicInteger sChannelId = new AtomicInteger(); - - class HealthChannel { - private ParcelFileDescriptor mChannelFd; - private boolean mMainChannel; - private String mChannelPath; - private BluetoothDevice mDevice; - private BluetoothHealthAppConfiguration mConfig; - private int mState; - private int mChannelType; - private int mId; - - HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, - ParcelFileDescriptor fd, boolean mainChannel, String channelPath) { - mChannelFd = fd; - mMainChannel = mainChannel; - mChannelPath = channelPath; - mDevice = device; - mConfig = config; - mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; - mId = getChannelId(); - } - } - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_REGISTER_APPLICATION: - BluetoothHealthAppConfiguration registerApp = - (BluetoothHealthAppConfiguration) msg.obj; - int role = registerApp.getRole(); - String path = null; - - if (role == BluetoothHealth.SINK_ROLE) { - path = mBluetoothService.registerHealthApplicationNative( - registerApp.getDataType(), getStringRole(role), registerApp.getName()); - } else { - path = mBluetoothService.registerHealthApplicationNative( - registerApp.getDataType(), getStringRole(role), registerApp.getName(), - getStringChannelType(registerApp.getChannelType())); - } - - if (path == null) { - callHealthApplicationStatusCallback(registerApp, - BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE); - mCallbacks.remove(registerApp); - } else { - mHealthAppConfigs.put(registerApp, path); - callHealthApplicationStatusCallback(registerApp, - BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS); - } - - break; - case MESSAGE_UNREGISTER_APPLICATION: - BluetoothHealthAppConfiguration unregisterApp = - (BluetoothHealthAppConfiguration) msg.obj; - - // Disconnect all the channels - for (HealthChannel chan : mHealthChannels) { - if (chan.mConfig.equals(unregisterApp) && - chan.mState != BluetoothHealth.STATE_CHANNEL_DISCONNECTED) { - disconnectChannel(chan.mDevice, unregisterApp, chan.mId); - } - } - - boolean result = mBluetoothService.unregisterHealthApplicationNative( - mHealthAppConfigs.get(unregisterApp)); - if (result) { - callHealthApplicationStatusCallback(unregisterApp, - BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS); - mCallbacks.remove(unregisterApp); - mHealthAppConfigs.remove(unregisterApp); - } else { - callHealthApplicationStatusCallback(unregisterApp, - BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE); - } - break; - case MESSAGE_CONNECT_CHANNEL: - HealthChannel chan = (HealthChannel)msg.obj; - String deviceObjectPath = - mBluetoothService.getObjectPathFromAddress(chan.mDevice.getAddress()); - String configPath = mHealthAppConfigs.get(chan.mConfig); - String channelType = getStringChannelType(chan.mChannelType); - - if (!mBluetoothService.createChannelNative(deviceObjectPath, configPath, - channelType, chan.mId)) { - int prevState = chan.mState; - int state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; - callHealthChannelCallback(chan.mConfig, chan.mDevice, prevState, state, null, - chan.mId); - mHealthChannels.remove(chan); - } - } - } - }; - - private BluetoothHealthProfileHandler(Context context, BluetoothService service) { - mBluetoothService = service; - mHealthAppConfigs = new HashMap<BluetoothHealthAppConfiguration, String>(); - mHealthChannels = new ArrayList<HealthChannel>(); - mHealthDevices = new HashMap<BluetoothDevice, Integer>(); - mCallbacks = new HashMap<BluetoothHealthAppConfiguration, IBluetoothHealthCallback>(); - } - - static synchronized BluetoothHealthProfileHandler getInstance(Context context, - BluetoothService service) { - if (sInstance == null) sInstance = new BluetoothHealthProfileHandler(context, service); - return sInstance; - } - - boolean registerAppConfiguration(BluetoothHealthAppConfiguration config, - IBluetoothHealthCallback callback) { - Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION); - msg.obj = config; - mHandler.sendMessage(msg); - mCallbacks.put(config, callback); - return true; - } - - boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { - String path = mHealthAppConfigs.get(config); - if (path == null) return false; - - Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION); - msg.obj = config; - mHandler.sendMessage(msg); - return true; - } - - boolean connectChannelToSource(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY); - } - - private HealthChannel getMainChannel(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - for (HealthChannel chan: mHealthChannels) { - if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) { - if (chan.mMainChannel) return chan; - } - } - return null; - } - - boolean connectChannel(BluetoothDevice device, - BluetoothHealthAppConfiguration config, int channelType) { - String deviceObjectPath = - mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (deviceObjectPath == null) return false; - - String configPath = mHealthAppConfigs.get(config); - if (configPath == null) return false; - - HealthChannel chan = new HealthChannel(device, config, null, false, null); - chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTING; - chan.mChannelType = channelType; - mHealthChannels.add(chan); - - int prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; - int state = BluetoothHealth.STATE_CHANNEL_CONNECTING; - callHealthChannelCallback(config, device, prevState, state, null, chan.mId); - - Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL); - msg.obj = chan; - mHandler.sendMessage(msg); - - return true; - } - - private String getStringChannelType(int type) { - if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) { - return "Reliable"; - } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) { - return "Streaming"; - } else { - return "Any"; - } - } - - private String getStringRole(int role) { - if (role == BluetoothHealth.SINK_ROLE) { - return "Sink"; - } else if (role == BluetoothHealth.SOURCE_ROLE) { - return "Streaming"; - } else { - return null; - } - } - - private int getChannelId() { - // The function doesn't need to be synchronized, as the health profile handler - // will only allow one health channel object creation at a time. - // In the worst case the while loop will have to break out at some point of - // time, because only a limited number of L2CAP channels are possible. - int id; - boolean found; - do { - id = sChannelId.incrementAndGet(); - found = false; - for (HealthChannel chan: mHealthChannels) { - if (chan.mId == id) found = true; - } - } while (found); - return id; - } - - boolean disconnectChannel(BluetoothDevice device, - BluetoothHealthAppConfiguration config, int id) { - HealthChannel chan = findChannelById(id); - if (chan == null) { - return false; - } - - String deviceObjectPath = - mBluetoothService.getObjectPathFromAddress(device.getAddress()); - - mBluetoothService.releaseChannelFdNative(chan.mChannelPath); - - int prevState = chan.mState; - chan.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTING; - callHealthChannelCallback(config, device, prevState, chan.mState, - null, chan.mId); - - if (!mBluetoothService.destroyChannelNative(deviceObjectPath, chan.mChannelPath, - chan.mId)) { - prevState = chan.mState; - chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTED; - callHealthChannelCallback(config, device, prevState, chan.mState, - chan.mChannelFd, chan.mId); - return false; - } else { - return true; - } - } - - private HealthChannel findChannelById(int id) { - for (HealthChannel chan : mHealthChannels) { - if (chan.mId == id) return chan; - } - return null; - } - - private HealthChannel findChannelByPath(BluetoothDevice device, String path) { - for (HealthChannel chan : mHealthChannels) { - if (path.equals(chan.mChannelPath) && device.equals(chan.mDevice)) return chan; - } - return null; - } - - private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) { - List<HealthChannel> channels = new ArrayList<HealthChannel>(); - for (HealthChannel chan: mHealthChannels) { - if (chan.mDevice.equals(device)) { - for (int state : states) { - if (chan.mState == state) { - channels.add(chan); - } - } - } - } - return channels; - } - - private HealthChannel findConnectingChannel(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - for (HealthChannel chan : mHealthChannels) { - if (chan.mDevice.equals(device) && chan.mConfig.equals(config) && - chan.mState == BluetoothHealth.STATE_CHANNEL_CONNECTING) return chan; - } - return null; - } - - ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - HealthChannel chan = getMainChannel(device, config); - if (chan != null) { - ParcelFileDescriptor pfd = null; - try { - pfd = chan.mChannelFd.dup(); - return pfd; - } catch (IOException e) { - return null; - } - } - - String objectPath = - mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (objectPath == null) return null; - - String mainChannelPath = mBluetoothService.getMainChannelNative(objectPath); - if (mainChannelPath == null) return null; - - // We had no record of the main channel but querying Bluez we got a - // main channel. We might not have received the PropertyChanged yet for - // the main channel creation so update our data structure here. - chan = findChannelByPath(device, mainChannelPath); - if (chan == null) { - errorLog("Main Channel present but we don't have any account of it:" + - device +":" + config); - return null; - } - chan.mMainChannel = true; - try { - return chan.mChannelFd.dup(); - } catch (IOException e) { - return null; - } - } - - /*package*/ void onHealthDevicePropertyChanged(String devicePath, - String channelPath) { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - String address = mBluetoothService.getAddressFromObjectPath(devicePath); - if (address == null) return; - - //TODO: Fix this in Bluez - if (channelPath.equals("/")) { - // This means that the main channel is being destroyed. - return; - } - - BluetoothDevice device = adapter.getRemoteDevice(address); - BluetoothHealthAppConfiguration config = findHealthApplication(device, - channelPath); - if (config != null) { - HealthChannel chan = findChannelByPath(device, channelPath); - if (chan == null) { - errorLog("Health Channel is not present:" + channelPath); - } else { - chan.mMainChannel = true; - } - } - } - - /*package*/ void onHealthDeviceChannelConnectionError(int chanCode, - int state) { - HealthChannel channel = findChannelById(chanCode); - if (channel == null) errorLog("No record of this channel:" + chanCode); - - callHealthChannelCallback(channel.mConfig, channel.mDevice, channel.mState, state, null, - chanCode); - } - - private BluetoothHealthAppConfiguration findHealthApplication( - BluetoothDevice device, String channelPath) { - BluetoothHealthAppConfiguration config = null; - HealthChannel chan = findChannelByPath(device, channelPath); - - if (chan != null) { - config = chan.mConfig; - } else { - String configPath = mBluetoothService.getChannelApplicationNative(channelPath); - if (configPath == null) { - errorLog("Config path is null for application"); - } else { - for (Entry<BluetoothHealthAppConfiguration, String> e : - mHealthAppConfigs.entrySet()) { - if (e.getValue().equals(configPath)) { - config = e.getKey(); - } - } - if (config == null) errorLog("No associated application for path:" + configPath); - } - } - return config; - } - - /*package*/ void onHealthDeviceChannelChanged(String devicePath, - String channelPath, boolean exists) { - debugLog("onHealthDeviceChannelChanged: devicePath: " + devicePath + - "ChannelPath: " + channelPath + "Exists: " + exists); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - String address = mBluetoothService.getAddressFromObjectPath(devicePath); - if (address == null) return; - - BluetoothDevice device = adapter.getRemoteDevice(address); - BluetoothHealthAppConfiguration config; - int state, prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; - ParcelFileDescriptor fd; - HealthChannel channel; - config = findHealthApplication(device, channelPath); - - if (exists) { - channel = findConnectingChannel(device, config); - if (channel == null) { - channel = new HealthChannel(device, config, null, false, - channelPath); - channel.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; - mHealthChannels.add(channel); - } - channel.mChannelPath = channelPath; - - fd = mBluetoothService.getChannelFdNative(channelPath); - if (fd == null) { - errorLog("Error obtaining fd for channel:" + channelPath); - disconnectChannel(device, config, channel.mId); - return; - } - boolean mainChannel = - getMainChannel(device, config) == null ? false : true; - if (!mainChannel) { - String mainChannelPath = - mBluetoothService.getMainChannelNative(devicePath); - if (mainChannelPath == null) { - errorLog("Main Channel Path is null for devicePath:" + devicePath); - return; - } - if (mainChannelPath.equals(channelPath)) mainChannel = true; - } - - channel.mChannelFd = fd; - channel.mMainChannel = mainChannel; - prevState = channel.mState; - state = BluetoothHealth.STATE_CHANNEL_CONNECTED; - } else { - channel = findChannelByPath(device, channelPath); - if (channel == null) { - errorLog("Channel not found:" + config + ":" + channelPath); - return; - } - mHealthChannels.remove(channel); - - channel.mChannelFd = null; - prevState = channel.mState; - state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; - } - channel.mState = state; - callHealthChannelCallback(config, device, prevState, state, channel.mChannelFd, - channel.mId); - } - - private void callHealthChannelCallback(BluetoothHealthAppConfiguration config, - BluetoothDevice device, int prevState, int state, ParcelFileDescriptor fd, int id) { - broadcastHealthDeviceStateChange(device, prevState, state); - - debugLog("Health Device Callback: " + device + " State Change: " - + prevState + "->" + state); - - ParcelFileDescriptor dupedFd = null; - if (fd != null) { - try { - dupedFd = fd.dup(); - } catch (IOException e) { - dupedFd = null; - errorLog("Exception while duping: " + e); - } - } - - IBluetoothHealthCallback callback = mCallbacks.get(config); - if (callback != null) { - try { - callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id); - } catch (RemoteException e) { - errorLog("Remote Exception:" + e); - } - } - } - - private void callHealthApplicationStatusCallback( - BluetoothHealthAppConfiguration config, int status) { - debugLog("Health Device Application: " + config + " State Change: status:" - + status); - IBluetoothHealthCallback callback = mCallbacks.get(config); - if (callback != null) { - try { - callback.onHealthAppConfigurationStatusChange(config, status); - } catch (RemoteException e) { - errorLog("Remote Exception:" + e); - } - } - } - - int getHealthDeviceConnectionState(BluetoothDevice device) { - if (mHealthDevices.get(device) == null) { - return BluetoothHealth.STATE_DISCONNECTED; - } - return mHealthDevices.get(device); - } - - List<BluetoothDevice> getConnectedHealthDevices() { - List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates( - new int[] {BluetoothHealth.STATE_CONNECTED}); - return devices; - } - - List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) { - List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states); - return devices; - } - - List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) { - List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>(); - - for (BluetoothDevice device: mHealthDevices.keySet()) { - int healthDeviceState = getHealthDeviceConnectionState(device); - for (int state : states) { - if (state == healthDeviceState) { - healthDevices.add(device); - break; - } - } - } - return healthDevices; - } - - /** - * This function sends the intent for the updates on the connection status to the remote device. - * Note that multiple channels can be connected to the remote device by multiple applications. - * This sends an intent for the update to the device connection status and not the channel - * connection status. Only the following state transitions are possible: - * - * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING} - * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED} - * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING} - * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED} - * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED} - * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED} - * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED} - * - * @param device - * @param prevChannelState - * @param newChannelState - * @hide - */ - private void broadcastHealthDeviceStateChange(BluetoothDevice device, int prevChannelState, - int newChannelState) { - if (mHealthDevices.get(device) == null) { - mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED); - } - - int currDeviceState = mHealthDevices.get(device); - int newDeviceState = convertState(newChannelState); - - if (currDeviceState != newDeviceState) { - List<HealthChannel> chan; - switch (currDeviceState) { - case BluetoothHealth.STATE_DISCONNECTED: - updateAndSendIntent(device, currDeviceState, newDeviceState); - break; - case BluetoothHealth.STATE_CONNECTING: - // Channel got connected. - if (newDeviceState == BluetoothHealth.STATE_CONNECTED) { - updateAndSendIntent(device, currDeviceState, newDeviceState); - } else { - // Channel got disconnected - chan = findChannelByStates(device, new int [] { - BluetoothHealth.STATE_CHANNEL_CONNECTING, - BluetoothHealth.STATE_CHANNEL_DISCONNECTING}); - if (chan.isEmpty()) { - updateAndSendIntent(device, currDeviceState, newDeviceState); - } - } - break; - case BluetoothHealth.STATE_CONNECTED: - // Channel got disconnected or is in disconnecting state. - chan = findChannelByStates(device, new int [] { - BluetoothHealth.STATE_CHANNEL_CONNECTING, - BluetoothHealth.STATE_CHANNEL_CONNECTED}); - if (chan.isEmpty()) { - updateAndSendIntent(device, currDeviceState, newDeviceState); - } - break; - case BluetoothHealth.STATE_DISCONNECTING: - // Channel got disconnected. - chan = findChannelByStates(device, new int [] { - BluetoothHealth.STATE_CHANNEL_CONNECTING, - BluetoothHealth.STATE_CHANNEL_DISCONNECTING}); - if (chan.isEmpty()) { - updateAndSendIntent(device, currDeviceState, newDeviceState); - } - break; - } - } - } - - private void updateAndSendIntent(BluetoothDevice device, int prevDeviceState, - int newDeviceState) { - mHealthDevices.put(device, newDeviceState); - mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.HEALTH, - newDeviceState, prevDeviceState); - } - - /** - * This function converts the channel connection state to device connection state. - * - * @param state - * @return - */ - private int convertState(int state) { - switch (state) { - case BluetoothHealth.STATE_CHANNEL_CONNECTED: - return BluetoothHealth.STATE_CONNECTED; - case BluetoothHealth.STATE_CHANNEL_CONNECTING: - return BluetoothHealth.STATE_CONNECTING; - case BluetoothHealth.STATE_CHANNEL_DISCONNECTING: - return BluetoothHealth.STATE_DISCONNECTING; - case BluetoothHealth.STATE_CHANNEL_DISCONNECTED: - return BluetoothHealth.STATE_DISCONNECTED; - } - errorLog("Mismatch in Channel and Health Device State"); - return -1; - } - - private static void debugLog(String msg) { - if (DBG) Log.d(TAG, msg); - } - - private static void errorLog(String msg) { - Log.e(TAG, msg); - } -} diff --git a/core/java/android/server/BluetoothInputProfileHandler.java b/core/java/android/server/BluetoothInputProfileHandler.java deleted file mode 100644 index 31764b0..0000000 --- a/core/java/android/server/BluetoothInputProfileHandler.java +++ /dev/null @@ -1,227 +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.server; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothDeviceProfileState; -import android.bluetooth.BluetoothInputDevice; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProfileState; -import android.content.Context; -import android.content.Intent; -import android.os.Message; -import android.provider.Settings; -import android.util.Log; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * This handles all the operations on the HID profile. - * All functions are called by BluetoothService, as Bluetooth Service - * is the Service handler for the HID profile. - */ -final class BluetoothInputProfileHandler { - private static final String TAG = "BluetoothInputProfileHandler"; - private static final boolean DBG = true; - - public static BluetoothInputProfileHandler sInstance; - private Context mContext; - private BluetoothService mBluetoothService; - private final HashMap<BluetoothDevice, Integer> mInputDevices; - private final BluetoothProfileState mHidProfileState; - - private BluetoothInputProfileHandler(Context context, BluetoothService service) { - mContext = context; - mBluetoothService = service; - mInputDevices = new HashMap<BluetoothDevice, Integer>(); - mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID); - mHidProfileState.start(); - } - - static synchronized BluetoothInputProfileHandler getInstance(Context context, - BluetoothService service) { - if (sInstance == null) sInstance = new BluetoothInputProfileHandler(context, service); - return sInstance; - } - - boolean connectInputDevice(BluetoothDevice device, - BluetoothDeviceProfileState state) { - String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (objectPath == null || - getInputDeviceConnectionState(device) != BluetoothInputDevice.STATE_DISCONNECTED || - getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_OFF) { - return false; - } - if (state != null) { - Message msg = new Message(); - msg.arg1 = BluetoothDeviceProfileState.CONNECT_HID_OUTGOING; - msg.obj = state; - mHidProfileState.sendMessage(msg); - return true; - } - return false; - } - - boolean connectInputDeviceInternal(BluetoothDevice device) { - String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING); - if (!mBluetoothService.connectInputDeviceNative(objectPath)) { - handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTED); - return false; - } - return true; - } - - boolean disconnectInputDevice(BluetoothDevice device, - BluetoothDeviceProfileState state) { - String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (objectPath == null || - getInputDeviceConnectionState(device) == BluetoothInputDevice.STATE_DISCONNECTED) { - return false; - } - if (state != null) { - Message msg = new Message(); - msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HID_OUTGOING; - msg.obj = state; - mHidProfileState.sendMessage(msg); - return true; - } - return false; - } - - boolean disconnectInputDeviceInternal(BluetoothDevice device) { - String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING); - if (!mBluetoothService.disconnectInputDeviceNative(objectPath)) { - handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTED); - return false; - } - return true; - } - - int getInputDeviceConnectionState(BluetoothDevice device) { - if (mInputDevices.get(device) == null) { - return BluetoothInputDevice.STATE_DISCONNECTED; - } - return mInputDevices.get(device); - } - - List<BluetoothDevice> getConnectedInputDevices() { - List<BluetoothDevice> devices = lookupInputDevicesMatchingStates( - new int[] {BluetoothInputDevice.STATE_CONNECTED}); - return devices; - } - - List<BluetoothDevice> getInputDevicesMatchingConnectionStates(int[] states) { - List<BluetoothDevice> devices = lookupInputDevicesMatchingStates(states); - return devices; - } - - int getInputDevicePriority(BluetoothDevice device) { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()), - BluetoothInputDevice.PRIORITY_UNDEFINED); - } - - boolean setInputDevicePriority(BluetoothDevice device, int priority) { - if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) { - return false; - } - return Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()), - priority); - } - - List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) { - List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>(); - - for (BluetoothDevice device: mInputDevices.keySet()) { - int inputDeviceState = getInputDeviceConnectionState(device); - for (int state : states) { - if (state == inputDeviceState) { - inputDevices.add(device); - break; - } - } - } - return inputDevices; - } - - private void handleInputDeviceStateChange(BluetoothDevice device, int state) { - int prevState; - if (mInputDevices.get(device) == null) { - prevState = BluetoothInputDevice.STATE_DISCONNECTED; - } else { - prevState = mInputDevices.get(device); - } - if (prevState == state) return; - - mInputDevices.put(device, state); - - if (getInputDevicePriority(device) > - BluetoothInputDevice.PRIORITY_OFF && - state == BluetoothInputDevice.STATE_CONNECTING || - state == BluetoothInputDevice.STATE_CONNECTED) { - // We have connected or attempting to connect. - // Bump priority - setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_AUTO_CONNECT); - } - - Intent intent = new Intent(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothInputDevice.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothInputDevice.EXTRA_STATE, state); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); - - debugLog("InputDevice state : device: " + device + " State:" + prevState + "->" + state); - mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.INPUT_DEVICE, state, - prevState); - } - - void handleInputDevicePropertyChange(String address, boolean connected) { - int state = connected ? BluetoothInputDevice.STATE_CONNECTED : - BluetoothInputDevice.STATE_DISCONNECTED; - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(address); - handleInputDeviceStateChange(device, state); - } - - void setInitialInputDevicePriority(BluetoothDevice device, int state) { - switch (state) { - case BluetoothDevice.BOND_BONDED: - if (getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_UNDEFINED) { - setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_ON); - } - break; - case BluetoothDevice.BOND_NONE: - setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_UNDEFINED); - break; - } - } - - private static void debugLog(String msg) { - if (DBG) Log.d(TAG, msg); - } - - private static void errorLog(String msg) { - Log.e(TAG, msg); - } -} diff --git a/core/java/android/server/BluetoothPanProfileHandler.java b/core/java/android/server/BluetoothPanProfileHandler.java deleted file mode 100644 index 41bb87f..0000000 --- a/core/java/android/server/BluetoothPanProfileHandler.java +++ /dev/null @@ -1,409 +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.server; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothTetheringDataTracker; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources.NotFoundException; -import android.net.ConnectivityManager; -import android.net.InterfaceConfiguration; -import android.net.LinkAddress; -import android.net.NetworkUtils; -import android.os.IBinder; -import android.os.INetworkManagementService; -import android.os.ServiceManager; -import android.util.Log; - -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * This handles the PAN profile. All calls into this are made - * from Bluetooth Service. - */ -final class BluetoothPanProfileHandler { - private static final String TAG = "BluetoothPanProfileHandler"; - private static final boolean DBG = true; - - private ArrayList<String> mBluetoothIfaceAddresses; - private int mMaxPanDevices; - - private static final String BLUETOOTH_IFACE_ADDR_START= "192.168.44.1"; - private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5; - private static final int BLUETOOTH_PREFIX_LENGTH = 24; - public static BluetoothPanProfileHandler sInstance; - private final HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices; - private boolean mTetheringOn; - private Context mContext; - private BluetoothService mBluetoothService; - - static final String NAP_ROLE = "nap"; - static final String NAP_BRIDGE = "pan1"; - - private BluetoothPanProfileHandler(Context context, BluetoothService service) { - mContext = context; - mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>(); - mBluetoothService = service; - mTetheringOn = false; - mBluetoothIfaceAddresses = new ArrayList<String>(); - try { - mMaxPanDevices = context.getResources().getInteger( - com.android.internal.R.integer.config_max_pan_devices); - } catch (NotFoundException e) { - mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS; - } - } - - static BluetoothPanProfileHandler getInstance(Context context, - BluetoothService service) { - if (sInstance == null) sInstance = new BluetoothPanProfileHandler(context, service); - return sInstance; - } - - boolean isTetheringOn() { - return mTetheringOn; - } - - boolean allowIncomingTethering() { - if (isTetheringOn() && getConnectedPanDevices().size() < mMaxPanDevices) - return true; - return false; - } - - private BroadcastReceiver mTetheringReceiver = null; - - void setBluetoothTethering(boolean value) { - if (!value) { - disconnectPanServerDevices(); - } - - if (mBluetoothService.getBluetoothState() != BluetoothAdapter.STATE_ON && value) { - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - mTetheringReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF) - == BluetoothAdapter.STATE_ON) { - mTetheringOn = true; - mContext.unregisterReceiver(mTetheringReceiver); - } - } - }; - mContext.registerReceiver(mTetheringReceiver, filter); - } else { - mTetheringOn = value; - } - } - - int getPanDeviceConnectionState(BluetoothDevice device) { - BluetoothPanDevice panDevice = mPanDevices.get(device); - if (panDevice == null) { - return BluetoothPan.STATE_DISCONNECTED; - } - return panDevice.mState; - } - - boolean connectPanDevice(BluetoothDevice device) { - String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (DBG) Log.d(TAG, "connect PAN(" + objectPath + ")"); - if (getPanDeviceConnectionState(device) != BluetoothPan.STATE_DISCONNECTED) { - errorLog(device + " already connected to PAN"); - } - - int connectedCount = 0; - for (BluetoothDevice panDevice: mPanDevices.keySet()) { - if (getPanDeviceConnectionState(panDevice) == BluetoothPan.STATE_CONNECTED) { - connectedCount ++; - } - } - if (connectedCount > 8) { - debugLog(device + " could not connect to PAN because 8 other devices are" - + "already connected"); - return false; - } - - // Send interface as null as it is not known - handlePanDeviceStateChange(device, null, BluetoothPan.STATE_CONNECTING, - BluetoothPan.LOCAL_PANU_ROLE); - if (mBluetoothService.connectPanDeviceNative(objectPath, "nap")) { - debugLog("connecting to PAN"); - return true; - } else { - handlePanDeviceStateChange(device, null, BluetoothPan.STATE_DISCONNECTED, - BluetoothPan.LOCAL_PANU_ROLE); - errorLog("could not connect to PAN"); - return false; - } - } - - private boolean disconnectPanServerDevices() { - debugLog("disconnect all PAN devices"); - - for (BluetoothDevice device: mPanDevices.keySet()) { - BluetoothPanDevice panDevice = mPanDevices.get(device); - int state = panDevice.mState; - if (state == BluetoothPan.STATE_CONNECTED && - panDevice.mLocalRole == BluetoothPan.LOCAL_NAP_ROLE) { - String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - - handlePanDeviceStateChange(device, panDevice.mIface, - BluetoothPan.STATE_DISCONNECTING, panDevice.mLocalRole); - - if (!mBluetoothService.disconnectPanServerDeviceNative(objectPath, - device.getAddress(), - panDevice.mIface)) { - errorLog("could not disconnect Pan Server Device "+device.getAddress()); - - // Restore prev state - handlePanDeviceStateChange(device, panDevice.mIface, state, - panDevice.mLocalRole); - - return false; - } - } - } - return true; - } - - List<BluetoothDevice> getConnectedPanDevices() { - List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); - - for (BluetoothDevice device: mPanDevices.keySet()) { - if (getPanDeviceConnectionState(device) == BluetoothPan.STATE_CONNECTED) { - devices.add(device); - } - } - return devices; - } - - List<BluetoothDevice> getPanDevicesMatchingConnectionStates(int[] states) { - List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); - - for (BluetoothDevice device: mPanDevices.keySet()) { - int panDeviceState = getPanDeviceConnectionState(device); - for (int state : states) { - if (state == panDeviceState) { - devices.add(device); - break; - } - } - } - return devices; - } - - boolean disconnectPanDevice(BluetoothDevice device) { - String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - debugLog("disconnect PAN(" + objectPath + ")"); - - int state = getPanDeviceConnectionState(device); - if (state != BluetoothPan.STATE_CONNECTED) { - debugLog(device + " already disconnected from PAN"); - return false; - } - - BluetoothPanDevice panDevice = mPanDevices.get(device); - - if (panDevice == null) { - errorLog("No record for this Pan device:" + device); - return false; - } - - handlePanDeviceStateChange(device, panDevice.mIface, BluetoothPan.STATE_DISCONNECTING, - panDevice.mLocalRole); - if (panDevice.mLocalRole == BluetoothPan.LOCAL_NAP_ROLE) { - if (!mBluetoothService.disconnectPanServerDeviceNative(objectPath, device.getAddress(), - panDevice.mIface)) { - // Restore prev state, this shouldn't happen - handlePanDeviceStateChange(device, panDevice.mIface, state, panDevice.mLocalRole); - return false; - } - } else { - if (!mBluetoothService.disconnectPanDeviceNative(objectPath)) { - // Restore prev state, this shouldn't happen - handlePanDeviceStateChange(device, panDevice.mIface, state, panDevice.mLocalRole); - return false; - } - } - return true; - } - - void handlePanDeviceStateChange(BluetoothDevice device, - String iface, int state, int role) { - int prevState; - String ifaceAddr = null; - BluetoothPanDevice panDevice = mPanDevices.get(device); - - if (panDevice == null) { - prevState = BluetoothPan.STATE_DISCONNECTED; - } else { - prevState = panDevice.mState; - ifaceAddr = panDevice.mIfaceAddr; - } - if (prevState == state) return; - - if (role == BluetoothPan.LOCAL_NAP_ROLE) { - if (state == BluetoothPan.STATE_CONNECTED) { - ifaceAddr = enableTethering(iface); - if (ifaceAddr == null) Log.e(TAG, "Error seting up tether interface"); - } else if (state == BluetoothPan.STATE_DISCONNECTED) { - if (ifaceAddr != null) { - mBluetoothIfaceAddresses.remove(ifaceAddr); - ifaceAddr = null; - } - } - } else { - // PANU Role = reverse Tether - if (state == BluetoothPan.STATE_CONNECTED) { - BluetoothTetheringDataTracker.getInstance().startReverseTether(iface, device); - } else if (state == BluetoothPan.STATE_DISCONNECTED && - (prevState == BluetoothPan.STATE_CONNECTED || - prevState == BluetoothPan.STATE_DISCONNECTING)) { - BluetoothTetheringDataTracker.getInstance().stopReverseTether(panDevice.mIface); - } - } - - if (panDevice == null) { - panDevice = new BluetoothPanDevice(state, ifaceAddr, iface, role); - mPanDevices.put(device, panDevice); - } else { - panDevice.mState = state; - panDevice.mIfaceAddr = ifaceAddr; - panDevice.mLocalRole = role; - panDevice.mIface = iface; - } - - Intent intent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothPan.EXTRA_STATE, state); - intent.putExtra(BluetoothPan.EXTRA_LOCAL_ROLE, role); - mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); - - debugLog("Pan Device state : device: " + device + " State:" + prevState + "->" + state); - mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.PAN, state, - prevState); - } - - private class BluetoothPanDevice { - private int mState; - private String mIfaceAddr; - private String mIface; - private int mLocalRole; // Which local role is this PAN device bound to - - BluetoothPanDevice(int state, String ifaceAddr, String iface, int localRole) { - mState = state; - mIfaceAddr = ifaceAddr; - mIface = iface; - mLocalRole = localRole; - } - } - - private String createNewTetheringAddressLocked() { - if (getConnectedPanDevices().size() == mMaxPanDevices) { - debugLog ("Max PAN device connections reached"); - return null; - } - String address = BLUETOOTH_IFACE_ADDR_START; - while (true) { - if (mBluetoothIfaceAddresses.contains(address)) { - String[] addr = address.split("\\."); - Integer newIp = Integer.parseInt(addr[2]) + 1; - address = address.replace(addr[2], newIp.toString()); - } else { - break; - } - } - mBluetoothIfaceAddresses.add(address); - return address; - } - - // configured when we start tethering - private String enableTethering(String iface) { - debugLog("updateTetherState:" + iface); - - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); - ConnectivityManager cm = - (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs(); - - // bring toggle the interfaces - String[] currentIfaces = new String[0]; - try { - currentIfaces = service.listInterfaces(); - } catch (Exception e) { - Log.e(TAG, "Error listing Interfaces :" + e); - return null; - } - - boolean found = false; - for (String currIface: currentIfaces) { - if (currIface.equals(iface)) { - found = true; - break; - } - } - - if (!found) return null; - - String address = createNewTetheringAddressLocked(); - if (address == null) return null; - - InterfaceConfiguration ifcg = null; - try { - ifcg = service.getInterfaceConfig(iface); - if (ifcg != null) { - final LinkAddress linkAddr = ifcg.getLinkAddress(); - InetAddress addr = null; - if (linkAddr == null || (addr = linkAddr.getAddress()) == null || - addr.equals(NetworkUtils.numericToInetAddress("0.0.0.0")) || - addr.equals(NetworkUtils.numericToInetAddress("::0"))) { - addr = NetworkUtils.numericToInetAddress(address); - } - ifcg.setInterfaceUp(); - ifcg.clearFlag("running"); - ifcg.setLinkAddress(new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH)); - service.setInterfaceConfig(iface, ifcg); - if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { - Log.e(TAG, "Error tethering "+iface); - } - } - } catch (Exception e) { - Log.e(TAG, "Error configuring interface " + iface + ", :" + e); - return null; - } - return address; - } - - private static void debugLog(String msg) { - if (DBG) Log.d(TAG, msg); - } - - private static void errorLog(String msg) { - Log.e(TAG, msg); - } -} diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java deleted file mode 100755 index 6296b11..0000000 --- a/core/java/android/server/BluetoothService.java +++ /dev/null @@ -1,2924 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * TODO: Move this to - * java/services/com/android/server/BluetoothService.java - * and make the contructor package private again. - * - * @hide - */ - -package android.server; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothDeviceProfileState; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothHealthAppConfiguration; -import android.bluetooth.BluetoothInputDevice; -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProfileState; -import android.bluetooth.BluetoothSocket; -import android.bluetooth.BluetoothUuid; -import android.bluetooth.IBluetooth; -import android.bluetooth.IBluetoothCallback; -import android.bluetooth.IBluetoothHealthCallback; -import android.bluetooth.IBluetoothStateChangeCallback; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.ParcelFileDescriptor; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.provider.Settings; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.app.IBatteryStats; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.DataInputStream; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.RandomAccessFile; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -public class BluetoothService extends IBluetooth.Stub { - private static final String TAG = "BluetoothService"; - private static final boolean DBG = true; - - private int mNativeData; - private BluetoothEventLoop mEventLoop; - private BluetoothHeadset mHeadsetProxy; - private BluetoothInputDevice mInputDevice; - private BluetoothPan mPan; - private boolean mIsAirplaneSensitive; - private boolean mIsAirplaneToggleable; - private BluetoothAdapterStateMachine mBluetoothState; - private int[] mAdapterSdpHandles; - private ParcelUuid[] mAdapterUuids; - - private BluetoothAdapter mAdapter; // constant after init() - private final BluetoothBondState mBondState; // local cache of bondings - private final IBatteryStats mBatteryStats; - private final Context mContext; - private Map<Integer, IBluetoothStateChangeCallback> mStateChangeTracker = - Collections.synchronizedMap(new HashMap<Integer, IBluetoothStateChangeCallback>()); - - private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; - static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; - - private static final String DOCK_ADDRESS_PATH = "/sys/class/switch/dock/bt_addr"; - private static final String DOCK_PIN_PATH = "/sys/class/switch/dock/bt_pin"; - - private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address"; - private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings"; - - private static final int MESSAGE_UUID_INTENT = 1; - private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 2; - private static final int MESSAGE_REMOVE_SERVICE_RECORD = 3; - - private static final int RFCOMM_RECORD_REAPER = 10; - private static final int STATE_CHANGE_REAPER = 11; - - // The time (in millisecs) to delay the pairing attempt after the first - // auto pairing attempt fails. We use an exponential delay with - // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and - // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. - private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; - private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; - - // The timeout used to sent the UUIDs Intent - // This timeout should be greater than the page timeout - private static final int UUID_INTENT_DELAY = 6000; - - /** Always retrieve RFCOMM channel for these SDP UUIDs */ - private static final ParcelUuid[] RFCOMM_UUIDS = { - BluetoothUuid.Handsfree, - BluetoothUuid.HSP, - BluetoothUuid.ObexObjectPush }; - - private final BluetoothAdapterProperties mAdapterProperties; - private final BluetoothDeviceProperties mDeviceProperties; - - private final HashMap<String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache; - private final ArrayList<String> mUuidIntentTracker; - private final HashMap<RemoteService, IBluetoothCallback> mUuidCallbackTracker; - - private static class ServiceRecordClient { - int pid; - IBinder binder; - IBinder.DeathRecipient death; - } - private final HashMap<Integer, ServiceRecordClient> mServiceRecordToPid; - - private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState; - private final BluetoothProfileState mA2dpProfileState; - private final BluetoothProfileState mHfpProfileState; - - private BluetoothA2dpService mA2dpService; - private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData; - - private int mProfilesConnected = 0, mProfilesConnecting = 0, mProfilesDisconnecting = 0; - - private static String mDockAddress; - private String mDockPin; - - private boolean mAllowConnect = true; - - private int mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED; - private BluetoothPanProfileHandler mBluetoothPanProfileHandler; - private BluetoothInputProfileHandler mBluetoothInputProfileHandler; - private BluetoothHealthProfileHandler mBluetoothHealthProfileHandler; - private static final String INCOMING_CONNECTION_FILE = - "/data/misc/bluetooth/incoming_connection.conf"; - private HashMap<String, Pair<Integer, String>> mIncomingConnections; - private HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState; - - private static class RemoteService { - public String address; - public ParcelUuid uuid; - public RemoteService(String address, ParcelUuid uuid) { - this.address = address; - this.uuid = uuid; - } - @Override - public boolean equals(Object o) { - if (o instanceof RemoteService) { - RemoteService service = (RemoteService)o; - return address.equals(service.address) && uuid.equals(service.uuid); - } - return false; - } - - @Override - public int hashCode() { - int hash = 1; - hash = hash * 31 + (address == null ? 0 : address.hashCode()); - hash = hash * 31 + (uuid == null ? 0 : uuid.hashCode()); - return hash; - } - } - - static { - classInitNative(); - } - - public BluetoothService(Context context) { - mContext = context; - - // Need to do this in place of: - // mBatteryStats = BatteryStatsService.getService(); - // Since we can not import BatteryStatsService from here. This class really needs to be - // moved to java/services/com/android/server/ - mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); - - initializeNativeDataNative(); - - if (isEnabledNative() == 1) { - Log.w(TAG, "Bluetooth daemons already running - runtime restart? "); - disableNative(); - } - - mBondState = new BluetoothBondState(context, this); - mAdapterProperties = new BluetoothAdapterProperties(context, this); - mDeviceProperties = new BluetoothDeviceProperties(this); - - mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>(); - mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>(); - mUuidIntentTracker = new ArrayList<String>(); - mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>(); - mServiceRecordToPid = new HashMap<Integer, ServiceRecordClient>(); - mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>(); - mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP); - mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP); - - mHfpProfileState.start(); - mA2dpProfileState.start(); - - IntentFilter filter = new IntentFilter(); - registerForAirplaneMode(filter); - - filter.addAction(Intent.ACTION_DOCK_EVENT); - mContext.registerReceiver(mReceiver, filter); - mBluetoothInputProfileHandler = BluetoothInputProfileHandler.getInstance(mContext, this); - mBluetoothPanProfileHandler = BluetoothPanProfileHandler.getInstance(mContext, this); - mBluetoothHealthProfileHandler = BluetoothHealthProfileHandler.getInstance(mContext, this); - mIncomingConnections = new HashMap<String, Pair<Integer, String>>(); - mProfileConnectionState = new HashMap<Integer, Pair<Integer, Integer>>(); - } - - public static synchronized String readDockBluetoothAddress() { - if (mDockAddress != null) return mDockAddress; - - BufferedInputStream file = null; - String dockAddress; - try { - file = new BufferedInputStream(new FileInputStream(DOCK_ADDRESS_PATH)); - byte[] address = new byte[17]; - file.read(address); - dockAddress = new String(address); - dockAddress = dockAddress.toUpperCase(); - if (BluetoothAdapter.checkBluetoothAddress(dockAddress)) { - mDockAddress = dockAddress; - return mDockAddress; - } else { - Log.e(TAG, "CheckBluetoothAddress failed for car dock address: " - + dockAddress); - } - } catch (FileNotFoundException e) { - Log.e(TAG, "FileNotFoundException while trying to read dock address"); - } catch (IOException e) { - Log.e(TAG, "IOException while trying to read dock address"); - } finally { - if (file != null) { - try { - file.close(); - } catch (IOException e) { - // Ignore - } - } - } - mDockAddress = null; - return null; - } - - private synchronized boolean writeDockPin() { - BufferedWriter out = null; - try { - out = new BufferedWriter(new FileWriter(DOCK_PIN_PATH)); - - // Generate a random 4 digit pin between 0000 and 9999 - // This is not truly random but good enough for our purposes. - int pin = (int) Math.floor(Math.random() * 10000); - - mDockPin = String.format("%04d", pin); - out.write(mDockPin); - return true; - } catch (FileNotFoundException e) { - Log.e(TAG, "FileNotFoundException while trying to write dock pairing pin"); - } catch (IOException e) { - Log.e(TAG, "IOException while while trying to write dock pairing pin"); - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - // Ignore - } - } - } - mDockPin = null; - return false; - } - - /*package*/ synchronized String getDockPin() { - return mDockPin; - } - - public synchronized void initAfterRegistration() { - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mBluetoothState = new BluetoothAdapterStateMachine(mContext, this, mAdapter); - mBluetoothState.start(); - if (mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.TURN_HOT); - } - mEventLoop = mBluetoothState.getBluetoothEventLoop(); - } - - public synchronized void initAfterA2dpRegistration() { - mEventLoop.getProfileProxy(); - } - - @Override - protected void finalize() throws Throwable { - mContext.unregisterReceiver(mReceiver); - try { - cleanupNativeDataNative(); - } finally { - super.finalize(); - } - } - - public boolean isEnabled() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return isEnabledInternal(); - } - - private boolean isEnabledInternal() { - return (getBluetoothStateInternal() == BluetoothAdapter.STATE_ON); - } - - public int getBluetoothState() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return getBluetoothStateInternal(); - } - - int getBluetoothStateInternal() { - return mBluetoothState.getBluetoothAdapterState(); - } - - /** - * Bring down bluetooth and disable BT in settings. Returns true on success. - */ - public boolean disable() { - return disable(true); - } - - /** - * Bring down bluetooth. Returns true on success. - * - * @param saveSetting If true, persist the new setting - */ - public synchronized boolean disable(boolean saveSetting) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); - - int adapterState = getBluetoothStateInternal(); - - switch (adapterState) { - case BluetoothAdapter.STATE_OFF: - return true; - case BluetoothAdapter.STATE_ON: - break; - default: - return false; - } - - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_OFF, saveSetting); - return true; - } - - synchronized void disconnectDevices() { - // Disconnect devices handled by BluetoothService. - for (BluetoothDevice device: getConnectedInputDevices()) { - disconnectInputDevice(device); - } - - for (BluetoothDevice device: getConnectedPanDevices()) { - disconnectPanDevice(device); - } - } - - /** - * The Bluetooth has been turned off, but hot. Do bonding, profile cleanup - */ - synchronized void finishDisable() { - // mark in progress bondings as cancelled - for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) { - mBondState.setBondState(address, BluetoothDevice.BOND_NONE, - BluetoothDevice.UNBOND_REASON_AUTH_CANCELED); - } - - // Stop the profile state machine for bonded devices. - for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDED)) { - removeProfileState(address); - } - - // update mode - Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.SCAN_MODE_NONE); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } - - /** - * Local clean up after broadcasting STATE_OFF intent - */ - synchronized void cleanupAfterFinishDisable() { - mAdapterProperties.clear(); - - for (Integer srHandle : mServiceRecordToPid.keySet()) { - removeServiceRecordNative(srHandle); - } - mServiceRecordToPid.clear(); - - mProfilesConnected = 0; - mProfilesConnecting = 0; - mProfilesDisconnecting = 0; - mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED; - mAdapterUuids = null; - mAdapterSdpHandles = null; - - // Log bluetooth off to battery stats. - long ident = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteBluetoothOff(); - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - /** - * power off Bluetooth - */ - synchronized void shutoffBluetooth() { - if (mAdapterSdpHandles != null) removeReservedServiceRecordsNative(mAdapterSdpHandles); - setBluetoothTetheringNative(false, BluetoothPanProfileHandler.NAP_ROLE, - BluetoothPanProfileHandler.NAP_BRIDGE); - tearDownNativeDataNative(); - } - - /** - * Data clean up after Bluetooth shutoff - */ - synchronized void cleanNativeAfterShutoffBluetooth() { - // Ths method is called after shutdown of event loop in the Bluetooth shut down - // procedure - - // the adapter property could be changed before event loop is stoped, clear it again - mAdapterProperties.clear(); - disableNative(); - } - - /** Bring up BT and persist BT on in settings */ - public boolean enable() { - return enable(true, true); - } - - /** - * Enable this Bluetooth device, asynchronously. - * This turns on/off the underlying hardware. - * - * @param saveSetting If true, persist the new state of BT in settings - * @param allowConnect If true, auto-connects device when BT is turned on - * and allows incoming A2DP/HSP connections - * @return True on success (so far) - */ - public synchronized boolean enable(boolean saveSetting, boolean allowConnect) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - - // Airplane mode can prevent Bluetooth radio from being turned on. - if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) { - return false; - } - mAllowConnect = allowConnect; - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_ON, saveSetting); - return true; - } - - /** - * Enable this Bluetooth device, asynchronously, but does not - * auto-connect devices. In this state the Bluetooth adapter - * also does not allow incoming A2DP/HSP connections (that - * must go through this service), but does allow communication - * on RFCOMM sockets implemented outside of this service (ie BTOPP). - * This method is used to temporarily enable Bluetooth - * for data transfer, without changing - * - * This turns on/off the underlying hardware. - * - * @return True on success (so far) - */ - public boolean enableNoAutoConnect() { - return enable(false, false); - } - - /** - * Turn on Bluetooth Module, Load firmware, and do all the preparation - * needed to get the Bluetooth Module ready but keep it not discoverable - * and not connectable. - */ - /* package */ synchronized boolean prepareBluetooth() { - if (!setupNativeDataNative()) { - return false; - } - switchConnectable(false); - - // Bluetooth stack needs a small delay here before adding - // SDP records, otherwise dbus stalls for over 30 seconds 1 out of 50 runs - try { - Thread.sleep(50); - } catch (InterruptedException e) {} - updateSdpRecords(); - return true; - } - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_UUID_INTENT: - String address = (String)msg.obj; - if (address != null) { - sendUuidIntent(address); - makeServiceChannelCallbacks(address); - } - break; - case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: - address = (String)msg.obj; - if (address == null) return; - int attempt = mBondState.getAttempt(address); - - // Try only if attemps are in progress and cap it 2 attempts - // The 2 attempts cap is a fail safe if the stack returns - // an incorrect error code for bonding failures and if the pin - // is entered wrongly twice we should abort. - if (attempt > 0 && attempt <= 2) { - mBondState.attempt(address); - createBond(address); - return; - } - if (attempt > 0) mBondState.clearPinAttempts(address); - break; - case MESSAGE_REMOVE_SERVICE_RECORD: - Pair<Integer, Integer> pair = (Pair<Integer, Integer>) msg.obj; - checkAndRemoveRecord(pair.first, pair.second); - break; - } - } - }; - - private synchronized void addReservedSdpRecords(final ArrayList<ParcelUuid> uuids) { - //Register SDP records. - int[] svcIdentifiers = new int[uuids.size()]; - for (int i = 0; i < uuids.size(); i++) { - svcIdentifiers[i] = BluetoothUuid.getServiceIdentifierFromParcelUuid(uuids.get(i)); - } - mAdapterSdpHandles = addReservedServiceRecordsNative(svcIdentifiers); - } - - private synchronized void updateSdpRecords() { - ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); - - Resources R = mContext.getResources(); - - // Add the default records - if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) { - uuids.add(BluetoothUuid.HSP_AG); - uuids.add(BluetoothUuid.ObexObjectPush); - } - - if (R.getBoolean(com.android.internal.R.bool.config_voice_capable)) { - uuids.add(BluetoothUuid.Handsfree_AG); - uuids.add(BluetoothUuid.PBAP_PSE); - } - - // Add SDP records for profiles maintained by Android userspace - addReservedSdpRecords(uuids); - - // Bluetooth stack need some a small delay here before adding more - // SDP records, otherwise dbus stalls for over 30 seconds 1 out of 50 runs - try { - Thread.sleep(50); - } catch (InterruptedException e) {} - - if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) { - // Enable profiles maintained by Bluez userspace. - setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE, - BluetoothPanProfileHandler.NAP_BRIDGE); - - // Add SDP records for profiles maintained by Bluez userspace - uuids.add(BluetoothUuid.AudioSource); - uuids.add(BluetoothUuid.AvrcpTarget); - uuids.add(BluetoothUuid.NAP); - } - - // Cannot cast uuids.toArray directly since ParcelUuid is parcelable - mAdapterUuids = new ParcelUuid[uuids.size()]; - for (int i = 0; i < uuids.size(); i++) { - mAdapterUuids[i] = uuids.get(i); - } - } - - /** - * This function is called from Bluetooth Event Loop when onPropertyChanged - * for adapter comes in with UUID property. - * @param uuidsThe uuids of adapter as reported by Bluez. - */ - /*package*/ synchronized void updateBluetoothState(String uuids) { - ParcelUuid[] adapterUuids = convertStringToParcelUuid(uuids); - - if (mAdapterUuids != null && - BluetoothUuid.containsAllUuids(adapterUuids, mAdapterUuids)) { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SERVICE_RECORD_LOADED); - } - } - - /** - * This method is called immediately before Bluetooth module is turned on after - * the adapter became pariable. - * It inits bond state and profile state before STATE_ON intent is broadcasted. - */ - /*package*/ void initBluetoothAfterTurningOn() { - String discoverable = getProperty("Discoverable", false); - String timeout = getProperty("DiscoverableTimeout", false); - if (timeout == null) { - Log.w(TAG, "Null DiscoverableTimeout property"); - // assign a number, anything not 0 - timeout = "1"; - } - if (discoverable.equals("true") && Integer.valueOf(timeout) != 0) { - setAdapterPropertyBooleanNative("Discoverable", 0); - } - mBondState.initBondState(); - initProfileState(); - getProfileProxy(); - } - - /** - * This method is called immediately after Bluetooth module is turned on. - * It starts auto-connection and places bluetooth on sign onto the battery - * stats - */ - /*package*/ void runBluetooth() { - autoConnect(); - - // Log bluetooth on to battery stats. - long ident = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteBluetoothOn(); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - /*package*/ synchronized boolean attemptAutoPair(String address) { - if (!mBondState.hasAutoPairingFailed(address) && - !mBondState.isAutoPairingBlacklisted(address)) { - mBondState.attempt(address); - setPin(address, BluetoothDevice.convertPinToBytes("0000")); - return true; - } - return false; - } - - /*package*/ synchronized boolean isFixedPinZerosAutoPairKeyboard(String address) { - // Check for keyboards which have fixed PIN 0000 as the pairing pin - return mBondState.isFixedPinZerosAutoPairKeyboard(address); - } - - /*package*/ synchronized void onCreatePairedDeviceResult(String address, int result) { - if (result == BluetoothDevice.BOND_SUCCESS) { - setBondState(address, BluetoothDevice.BOND_BONDED); - if (mBondState.isAutoPairingAttemptsInProgress(address)) { - mBondState.clearPinAttempts(address); - } - } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && - mBondState.getAttempt(address) == 1) { - mBondState.addAutoPairingFailure(address); - pairingAttempt(address, result); - } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && - mBondState.isAutoPairingAttemptsInProgress(address)) { - pairingAttempt(address, result); - } else { - setBondState(address, BluetoothDevice.BOND_NONE, result); - if (mBondState.isAutoPairingAttemptsInProgress(address)) { - mBondState.clearPinAttempts(address); - } - } - } - - /*package*/ synchronized String getPendingOutgoingBonding() { - return mBondState.getPendingOutgoingBonding(); - } - - private void pairingAttempt(String address, int result) { - // This happens when our initial guess of "0000" as the pass key - // fails. Try to create the bond again and display the pin dialog - // to the user. Use back-off while posting the delayed - // message. The initial value is - // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is - // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is - // reached, display an error to the user. - int attempt = mBondState.getAttempt(address); - if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > - MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { - mBondState.clearPinAttempts(address); - setBondState(address, BluetoothDevice.BOND_NONE, result); - return; - } - - Message message = mHandler.obtainMessage(MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); - message.obj = address; - boolean postResult = mHandler.sendMessageDelayed(message, - attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); - if (!postResult) { - mBondState.clearPinAttempts(address); - setBondState(address, - BluetoothDevice.BOND_NONE, result); - return; - } - } - - /*package*/ BluetoothDevice getRemoteDevice(String address) { - return mAdapter.getRemoteDevice(address); - } - - private static String toBondStateString(int bondState) { - switch (bondState) { - case BluetoothDevice.BOND_NONE: - return "not bonded"; - case BluetoothDevice.BOND_BONDING: - return "bonding"; - case BluetoothDevice.BOND_BONDED: - return "bonded"; - default: - return "??????"; - } - } - - public synchronized boolean setName(String name) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (name == null) { - return false; - } - return setPropertyString("Name", name); - } - - //TODO(): setPropertyString, setPropertyInteger, setPropertyBoolean - // Either have a single property function with Object as the parameter - // or have a function for each property and then obfuscate in the JNI layer. - // The following looks dirty. - private boolean setPropertyString(String key, String value) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!isEnabledInternal()) return false; - return setAdapterPropertyStringNative(key, value); - } - - private boolean setPropertyInteger(String key, int value) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!isEnabledInternal()) return false; - return setAdapterPropertyIntegerNative(key, value); - } - - private boolean setPropertyBoolean(String key, boolean value) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!isEnabledInternal()) return false; - return setAdapterPropertyBooleanNative(key, value ? 1 : 0); - } - - /** - * Set the discoverability window for the device. A timeout of zero - * makes the device permanently discoverable (if the device is - * discoverable). Setting the timeout to a nonzero value does not make - * a device discoverable; you need to call setMode() to make the device - * explicitly discoverable. - * - * @param timeout The discoverable timeout in seconds. - */ - public synchronized boolean setDiscoverableTimeout(int timeout) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - return setPropertyInteger("DiscoverableTimeout", timeout); - } - - public synchronized boolean setScanMode(int mode, int duration) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, - "Need WRITE_SECURE_SETTINGS permission"); - boolean pairable; - boolean discoverable; - - switch (mode) { - case BluetoothAdapter.SCAN_MODE_NONE: - pairable = false; - discoverable = false; - break; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE: - pairable = true; - discoverable = false; - break; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: - pairable = true; - discoverable = true; - if (DBG) Log.d(TAG, "BT Discoverable for " + duration + " seconds"); - break; - default: - Log.w(TAG, "Requested invalid scan mode " + mode); - return false; - } - - setPropertyBoolean("Discoverable", discoverable); - setPropertyBoolean("Pairable", pairable); - return true; - } - - /** - * @param on true set the local Bluetooth module to be connectable - * The dicoverability is recovered to what it was before - * switchConnectable(false) call - * false set the local Bluetooth module to be not connectable - * and not dicoverable - */ - /*package*/ synchronized void switchConnectable(boolean on) { - setAdapterPropertyBooleanNative("Powered", on ? 1 : 0); - } - - /*package*/ synchronized void setPairable() { - String pairableString = getProperty("Pairable", false); - if (pairableString == null) { - Log.e(TAG, "null pairableString"); - return; - } - if (pairableString.equals("false")) { - setAdapterPropertyBooleanNative("Pairable", 1); - } - } - - /*package*/ String getProperty(String name, boolean checkState) { - // If checkState is false, check if the event loop is running. - // before making the call to Bluez - if (checkState) { - if (!isEnabledInternal()) return null; - } else if (!mEventLoop.isEventLoopRunning()) { - return null; - } - - return mAdapterProperties.getProperty(name); - } - - BluetoothAdapterProperties getAdapterProperties() { - return mAdapterProperties; - } - - BluetoothDeviceProperties getDeviceProperties() { - return mDeviceProperties; - } - - boolean isRemoteDeviceInCache(String address) { - return mDeviceProperties.isInCache(address); - } - - void setRemoteDeviceProperty(String address, String name, String value) { - mDeviceProperties.setProperty(address, name, value); - } - - void updateRemoteDevicePropertiesCache(String address) { - mDeviceProperties.updateCache(address); - } - - public synchronized String getAddress() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - // Don't check state since we want to provide address, even if BT is off - return getProperty("Address", false); - } - - public synchronized String getName() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - // Don't check state since we want to provide name, even if BT is off - return getProperty("Name", false); - } - - public ParcelUuid[] getUuids() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - String value = getProperty("UUIDs", true); - if (value == null) return null; - return convertStringToParcelUuid(value); - } - - private ParcelUuid[] convertStringToParcelUuid(String value) { - String[] uuidStrings = null; - // The UUIDs are stored as a "," separated string. - uuidStrings = value.split(","); - ParcelUuid[] uuids = new ParcelUuid[uuidStrings.length]; - - for (int i = 0; i < uuidStrings.length; i++) { - uuids[i] = ParcelUuid.fromString(uuidStrings[i]); - } - return uuids; - } - - /** - * Returns the user-friendly name of a remote device. This value is - * returned from our local cache, which is updated when onPropertyChange - * event is received. - * Do not expect to retrieve the updated remote name immediately after - * changing the name on the remote device. - * - * @param address Bluetooth address of remote device. - * - * @return The user-friendly name of the specified remote device. - */ - public synchronized String getRemoteName(String address) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return null; - } - return mDeviceProperties.getProperty(address, "Name"); - } - - /** - * Returns alias of a remote device. This value is returned from our - * local cache, which is updated when onPropertyChange event is received. - * - * @param address Bluetooth address of remote device. - * - * @return The alias of the specified remote device. - */ - public synchronized String getRemoteAlias(String address) { - - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return null; - } - return mDeviceProperties.getProperty(address, "Alias"); - } - - /** - * Set the alias of a remote device. - * - * @param address Bluetooth address of remote device. - * @param alias new alias to change to - * @return true on success, false on error - */ - public synchronized boolean setRemoteAlias(String address, String alias) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - - return setDevicePropertyStringNative(getObjectPathFromAddress(address), - "Alias", alias); - } - - /** - * Get the discoverability window for the device. A timeout of zero - * means that the device is permanently discoverable (if the device is - * in the discoverable mode). - * - * @return The discoverability window of the device, in seconds. A negative - * value indicates an error. - */ - public int getDiscoverableTimeout() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - String timeout = getProperty("DiscoverableTimeout", true); - if (timeout != null) - return Integer.valueOf(timeout); - else - return -1; - } - - public int getScanMode() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!isEnabledInternal()) - return BluetoothAdapter.SCAN_MODE_NONE; - - boolean pairable = getProperty("Pairable", true).equals("true"); - boolean discoverable = getProperty("Discoverable", true).equals("true"); - return bluezStringToScanMode (pairable, discoverable); - } - - public synchronized boolean startDiscovery() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - return startDiscoveryNative(); - } - - public synchronized boolean cancelDiscovery() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - return stopDiscoveryNative(); - } - - public boolean isDiscovering() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - - String discoveringProperty = getProperty("Discovering", false); - if (discoveringProperty == null) { - return false; - } - - return discoveringProperty.equals("true"); - } - - private boolean isBondingFeasible(String address) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - address = address.toUpperCase(); - - if (mBondState.getPendingOutgoingBonding() != null) { - Log.d(TAG, "Ignoring createBond(): another device is bonding"); - // a different device is currently bonding, fail - return false; - } - - // Check for bond state only if we are not performing auto - // pairing exponential back-off attempts. - if (!mBondState.isAutoPairingAttemptsInProgress(address) && - mBondState.getBondState(address) != BluetoothDevice.BOND_NONE) { - Log.d(TAG, "Ignoring createBond(): this device is already bonding or bonded"); - return false; - } - - if (address.equals(mDockAddress)) { - if (!writeDockPin()) { - Log.e(TAG, "Error while writing Pin for the dock"); - return false; - } - } - return true; - } - - public synchronized boolean createBond(String address) { - if (!isBondingFeasible(address)) return false; - - if (!createPairedDeviceNative(address, 60000 /*1 minute*/ )) { - return false; - } - - mBondState.setPendingOutgoingBonding(address); - mBondState.setBondState(address, BluetoothDevice.BOND_BONDING); - - return true; - } - - public synchronized boolean createBondOutOfBand(String address, byte[] hash, - byte[] randomizer) { - if (!isBondingFeasible(address)) return false; - - if (!createPairedDeviceOutOfBandNative(address, 60000 /* 1 minute */)) { - return false; - } - - setDeviceOutOfBandData(address, hash, randomizer); - mBondState.setPendingOutgoingBonding(address); - mBondState.setBondState(address, BluetoothDevice.BOND_BONDING); - - return true; - } - - public synchronized boolean setDeviceOutOfBandData(String address, byte[] hash, - byte[] randomizer) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - Pair <byte[], byte[]> value = new Pair<byte[], byte[]>(hash, randomizer); - - if (DBG) { - Log.d(TAG, "Setting out of band data for: " + address + ":" + - Arrays.toString(hash) + ":" + Arrays.toString(randomizer)); - } - - mDeviceOobData.put(address, value); - return true; - } - - Pair<byte[], byte[]> getDeviceOutOfBandData(BluetoothDevice device) { - return mDeviceOobData.get(device.getAddress()); - } - - - public synchronized byte[] readOutOfBandData() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - if (!isEnabledInternal()) return null; - - return readAdapterOutOfBandDataNative(); - } - - public synchronized boolean cancelBondProcess(String address) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - address = address.toUpperCase(); - if (mBondState.getBondState(address) != BluetoothDevice.BOND_BONDING) { - return false; - } - - mBondState.setBondState(address, BluetoothDevice.BOND_NONE, - BluetoothDevice.UNBOND_REASON_AUTH_CANCELED); - cancelDeviceCreationNative(address); - return true; - } - - public synchronized boolean removeBond(String address) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state != null) { - state.sendMessage(BluetoothDeviceProfileState.UNPAIR); - return true; - } else { - return false; - } - } - - public synchronized boolean removeBondInternal(String address) { - // Unset the trusted device state and then unpair - setTrust(address, false); - return removeDeviceNative(getObjectPathFromAddress(address)); - } - - public synchronized String[] listBonds() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return mBondState.listInState(BluetoothDevice.BOND_BONDED); - } - - /*package*/ synchronized String[] listInState(int state) { - return mBondState.listInState(state); - } - - public synchronized int getBondState(String address) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return BluetoothDevice.ERROR; - } - return mBondState.getBondState(address.toUpperCase()); - } - - /*package*/ synchronized boolean setBondState(String address, int state) { - return setBondState(address, state, 0); - } - - /*package*/ synchronized boolean setBondState(String address, int state, int reason) { - mBondState.setBondState(address.toUpperCase(), state, reason); - return true; - } - - public synchronized boolean isBluetoothDock(String address) { - SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE); - - return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address); - } - - /*package*/ String[] getRemoteDeviceProperties(String address) { - if (!isEnabledInternal()) return null; - - String objectPath = getObjectPathFromAddress(address); - return (String [])getDevicePropertiesNative(objectPath); - } - - /** - * Sets the remote device trust state. - * - * @return boolean to indicate operation success or fail - */ - public synchronized boolean setTrust(String address, boolean value) { - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - return false; - } - - if (!isEnabledInternal()) return false; - - return setDevicePropertyBooleanNative( - getObjectPathFromAddress(address), "Trusted", value ? 1 : 0); - } - - /** - * Gets the remote device trust state as boolean. - * Note: this value may be - * retrieved from cache if we retrieved the data before * - * - * @return boolean to indicate trusted or untrusted state - */ - public synchronized boolean getTrustState(String address) { - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return false; - } - - String val = mDeviceProperties.getProperty(address, "Trusted"); - if (val == null) { - return false; - } else { - return val.equals("true"); - } - } - - /** - * Gets the remote major, minor classes encoded as a 32-bit - * integer. - * - * Note: this value is retrieved from cache, because we get it during - * remote-device discovery. - * - * @return 32-bit integer encoding the remote major, minor, and service - * classes. - */ - public synchronized int getRemoteClass(String address) { - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return BluetoothClass.ERROR; - } - String val = mDeviceProperties.getProperty(address, "Class"); - if (val == null) - return BluetoothClass.ERROR; - else { - return Integer.valueOf(val); - } - } - - - /** - * Gets the UUIDs supported by the remote device - * - * @return array of 128bit ParcelUuids - */ - public synchronized ParcelUuid[] getRemoteUuids(String address) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return null; - } - return getUuidFromCache(address); - } - - ParcelUuid[] getUuidFromCache(String address) { - String value = mDeviceProperties.getProperty(address, "UUIDs"); - if (value == null) return null; - - String[] uuidStrings = null; - // The UUIDs are stored as a "," separated string. - uuidStrings = value.split(","); - ParcelUuid[] uuids = new ParcelUuid[uuidStrings.length]; - - for (int i = 0; i < uuidStrings.length; i++) { - uuids[i] = ParcelUuid.fromString(uuidStrings[i]); - } - return uuids; - } - - /** - * Connect and fetch new UUID's using SDP. - * The UUID's found are broadcast as intents. - * Optionally takes a uuid and callback to fetch the RFCOMM channel for the - * a given uuid. - * TODO: Don't wait UUID_INTENT_DELAY to broadcast UUID intents on success - * TODO: Don't wait UUID_INTENT_DELAY to handle the failure case for - * callback and broadcast intents. - */ - public synchronized boolean fetchRemoteUuids(String address, ParcelUuid uuid, - IBluetoothCallback callback) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!isEnabledInternal()) return false; - - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - - RemoteService service = new RemoteService(address, uuid); - if (uuid != null && mUuidCallbackTracker.get(service) != null) { - // An SDP query for this address & uuid is already in progress - // Do not add this callback for the uuid - return false; - } - - if (mUuidIntentTracker.contains(address)) { - // An SDP query for this address is already in progress - // Add this uuid onto the in-progress SDP query - if (uuid != null) { - mUuidCallbackTracker.put(new RemoteService(address, uuid), callback); - } - return true; - } - - // If the device is already created, we will - // do the SDP on the callback of createDeviceNative. - boolean ret= createDeviceNative(address); - - mUuidIntentTracker.add(address); - if (uuid != null) { - mUuidCallbackTracker.put(new RemoteService(address, uuid), callback); - } - - Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); - message.obj = address; - mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY); - return ret; - } - - /** - * Gets the rfcomm channel associated with the UUID. - * Pulls records from the cache only. - * - * @param address Address of the remote device - * @param uuid ParcelUuid of the service attribute - * - * @return rfcomm channel associated with the service attribute - * -1 on error - */ - public int getRemoteServiceChannel(String address, ParcelUuid uuid) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!isEnabledInternal()) return -1; - - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return BluetoothDevice.ERROR; - } - // Check if we are recovering from a crash. - if (mDeviceProperties.isEmpty()) { - if (mDeviceProperties.updateCache(address) == null) - return -1; - } - - Map<ParcelUuid, Integer> value = mDeviceServiceChannelCache.get(address); - if (value != null && value.containsKey(uuid)) - return value.get(uuid); - return -1; - } - - public synchronized boolean setPin(String address, byte[] pin) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - if (pin == null || pin.length <= 0 || pin.length > 16 || - !BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - address = address.toUpperCase(); - Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); - if (data == null) { - Log.w(TAG, "setPin(" + address + ") called but no native data available, " + - "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" + - " or by bluez.\n"); - return false; - } - // bluez API wants pin as a string - String pinString; - try { - pinString = new String(pin, "UTF8"); - } catch (UnsupportedEncodingException uee) { - Log.e(TAG, "UTF8 not supported?!?"); - return false; - } - return setPinNative(address, pinString, data.intValue()); - } - - public synchronized boolean setPasskey(String address, int passkey) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - if (passkey < 0 || passkey > 999999 || !BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - address = address.toUpperCase(); - Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); - if (data == null) { - Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " + - "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" + - " or by bluez.\n"); - return false; - } - return setPasskeyNative(address, passkey, data.intValue()); - } - - public synchronized boolean setPairingConfirmation(String address, boolean confirm) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - address = address.toUpperCase(); - Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); - if (data == null) { - Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " + - "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" + - " or by bluez.\n"); - return false; - } - return setPairingConfirmationNative(address, confirm, data.intValue()); - } - - public synchronized boolean setRemoteOutOfBandData(String address) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - address = address.toUpperCase(); - Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); - if (data == null) { - Log.w(TAG, "setRemoteOobData(" + address + ") called but no native data available, " + - "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" + - " or by bluez.\n"); - return false; - } - - Pair<byte[], byte[]> val = mDeviceOobData.get(address); - byte[] hash, randomizer; - if (val == null) { - // TODO: check what should be passed in this case. - hash = new byte[16]; - randomizer = new byte[16]; - } else { - hash = val.first; - randomizer = val.second; - } - return setRemoteOutOfBandDataNative(address, hash, randomizer, data.intValue()); - } - - public synchronized boolean cancelPairingUserInput(String address) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (!isEnabledInternal()) return false; - - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - mBondState.setBondState(address, BluetoothDevice.BOND_NONE, - BluetoothDevice.UNBOND_REASON_AUTH_CANCELED); - address = address.toUpperCase(); - Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); - if (data == null) { - Log.w(TAG, "cancelUserInputNative(" + address + ") called but no native data " + - "available, ignoring. Maybe the PasskeyAgent Request was already cancelled " + - "by the remote or by bluez.\n"); - return false; - } - return cancelPairingUserInputNative(address, data.intValue()); - } - - /*package*/ void updateDeviceServiceChannelCache(String address) { - if (DBG) Log.d(TAG, "updateDeviceServiceChannelCache(" + address + ")"); - - // We are storing the rfcomm channel numbers only for the uuids - // we are interested in. - ParcelUuid[] deviceUuids = getRemoteUuids(address); - - ArrayList<ParcelUuid> applicationUuids = new ArrayList<ParcelUuid>(); - - synchronized (this) { - for (RemoteService service : mUuidCallbackTracker.keySet()) { - if (service.address.equals(address)) { - applicationUuids.add(service.uuid); - } - } - } - - Map <ParcelUuid, Integer> uuidToChannelMap = new HashMap<ParcelUuid, Integer>(); - - // Retrieve RFCOMM channel for default uuids - for (ParcelUuid uuid : RFCOMM_UUIDS) { - if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) { - int channel = getDeviceServiceChannelForUuid(address, uuid); - uuidToChannelMap.put(uuid, channel); - if (DBG) Log.d(TAG, "\tuuid(system): " + uuid + " " + channel); - } - } - // Retrieve RFCOMM channel for application requested uuids - for (ParcelUuid uuid : applicationUuids) { - if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) { - int channel = getDeviceServiceChannelForUuid(address, uuid); - uuidToChannelMap.put(uuid, channel); - if (DBG) Log.d(TAG, "\tuuid(application): " + uuid + " " + channel); - } - } - - synchronized (this) { - // Make application callbacks - for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator(); - iter.hasNext();) { - RemoteService service = iter.next(); - if (service.address.equals(address)) { - if (uuidToChannelMap.containsKey(service.uuid)) { - int channel = uuidToChannelMap.get(service.uuid); - - if (DBG) Log.d(TAG, "Making callback for " + service.uuid + - " with result " + channel); - IBluetoothCallback callback = mUuidCallbackTracker.get(service); - if (callback != null) { - try { - callback.onRfcommChannelFound(channel); - } catch (RemoteException e) {Log.e(TAG, "", e);} - } - - iter.remove(); - } - } - } - - // Update cache - mDeviceServiceChannelCache.put(address, uuidToChannelMap); - } - } - - private int getDeviceServiceChannelForUuid(String address, - ParcelUuid uuid) { - return getDeviceServiceChannelNative(getObjectPathFromAddress(address), - uuid.toString(), 0x0004); - } - - /** - * b is a handle to a Binder instance, so that this service can be notified - * for Applications that terminate unexpectedly, to clean there service - * records - */ - public synchronized int addRfcommServiceRecord(String serviceName, ParcelUuid uuid, - int channel, IBinder b) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (!isEnabledInternal()) return -1; - - if (serviceName == null || uuid == null || channel < 1 || - channel > BluetoothSocket.MAX_RFCOMM_CHANNEL) { - return -1; - } - if (BluetoothUuid.isUuidPresent(BluetoothUuid.RESERVED_UUIDS, uuid)) { - Log.w(TAG, "Attempted to register a reserved UUID: " + uuid); - return -1; - } - int handle = addRfcommServiceRecordNative(serviceName, - uuid.getUuid().getMostSignificantBits(), uuid.getUuid().getLeastSignificantBits(), - (short)channel); - if (DBG) Log.d(TAG, "new handle " + Integer.toHexString(handle)); - if (handle == -1) { - return -1; - } - - ServiceRecordClient client = new ServiceRecordClient(); - client.pid = Binder.getCallingPid(); - client.binder = b; - client.death = new Reaper(handle, client.pid, RFCOMM_RECORD_REAPER); - mServiceRecordToPid.put(new Integer(handle), client); - try { - b.linkToDeath(client.death, 0); - } catch (RemoteException e) { - Log.e(TAG, "", e); - client.death = null; - } - return handle; - } - - public void removeServiceRecord(int handle) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - // Since this is a binder call check if Bluetooth is off - if (getBluetoothStateInternal() == BluetoothAdapter.STATE_OFF) return; - Message message = mHandler.obtainMessage(MESSAGE_REMOVE_SERVICE_RECORD); - message.obj = new Pair<Integer, Integer>(handle, Binder.getCallingPid()); - mHandler.sendMessage(message); - } - - private synchronized void checkAndRemoveRecord(int handle, int pid) { - ServiceRecordClient client = mServiceRecordToPid.get(handle); - if (client != null && pid == client.pid) { - if (DBG) Log.d(TAG, "Removing service record " + - Integer.toHexString(handle) + " for pid " + pid); - - if (client.death != null) { - client.binder.unlinkToDeath(client.death, 0); - } - - mServiceRecordToPid.remove(handle); - removeServiceRecordNative(handle); - } - } - - private class Reaper implements IBinder.DeathRecipient { - int mPid; - int mHandle; - int mType; - - Reaper(int handle, int pid, int type) { - mPid = pid; - mHandle = handle; - mType = type; - } - - Reaper(int pid, int type) { - mPid = pid; - mType = type; - } - - @Override - public void binderDied() { - synchronized (BluetoothService.this) { - if (DBG) Log.d(TAG, "Tracked app " + mPid + " died" + "Type:" + mType); - if (mType == RFCOMM_RECORD_REAPER) { - checkAndRemoveRecord(mHandle, mPid); - } else if (mType == STATE_CHANGE_REAPER) { - mStateChangeTracker.remove(mPid); - } - } - } - } - - - @Override - public boolean changeApplicationBluetoothState(boolean on, - IBluetoothStateChangeCallback callback, IBinder binder) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - - int pid = Binder.getCallingPid(); - //mStateChangeTracker is a synchronized map - if (!mStateChangeTracker.containsKey(pid)) { - if (on) { - mStateChangeTracker.put(pid, callback); - } else { - return false; - } - } else if (!on) { - mStateChangeTracker.remove(pid); - } - - if (binder != null) { - try { - binder.linkToDeath(new Reaper(pid, STATE_CHANGE_REAPER), 0); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - } - - int type; - if (on) { - type = BluetoothAdapterStateMachine.PER_PROCESS_TURN_ON; - } else { - type = BluetoothAdapterStateMachine.PER_PROCESS_TURN_OFF; - } - - mBluetoothState.sendMessage(type, callback); - return true; - } - - boolean isApplicationStateChangeTrackerEmpty() { - return mStateChangeTracker.isEmpty(); - } - - void clearApplicationStateChangeTracker() { - mStateChangeTracker.clear(); - } - - Collection<IBluetoothStateChangeCallback> getApplicationStateChangeCallbacks() { - return mStateChangeTracker.values(); - } - - int getNumberOfApplicationStateChangeTrackers() { - return mStateChangeTracker.size(); - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent == null) return; - - String action = intent.getAction(); - if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { - ContentResolver resolver = context.getContentResolver(); - // Query the airplane mode from Settings.System just to make sure that - // some random app is not sending this intent and disabling bluetooth - if (isAirplaneModeOn()) { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.AIRPLANE_MODE_ON); - } else { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.AIRPLANE_MODE_OFF); - } - } else if (Intent.ACTION_DOCK_EVENT.equals(action)) { - int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (DBG) Log.v(TAG, "Received ACTION_DOCK_EVENT with State:" + state); - if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - mDockAddress = null; - mDockPin = null; - } else { - SharedPreferences.Editor editor = - mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, - mContext.MODE_PRIVATE).edit(); - editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true); - editor.apply(); - } - } - } - }; - - private void registerForAirplaneMode(IntentFilter filter) { - final ContentResolver resolver = mContext.getContentResolver(); - final String airplaneModeRadios = Settings.System.getString(resolver, - Settings.System.AIRPLANE_MODE_RADIOS); - final String toggleableRadios = Settings.System.getString(resolver, - Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); - - mIsAirplaneSensitive = airplaneModeRadios == null ? true : - airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH); - mIsAirplaneToggleable = toggleableRadios == null ? false : - toggleableRadios.contains(Settings.System.RADIO_BLUETOOTH); - - if (mIsAirplaneSensitive) { - filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); - } - } - - /* Returns true if airplane mode is currently on */ - /*package*/ final boolean isAirplaneModeOn() { - return Settings.System.getInt(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_ON, 0) == 1; - } - - /* Broadcast the Uuid intent */ - /*package*/ synchronized void sendUuidIntent(String address) { - ParcelUuid[] uuid = getUuidFromCache(address); - Intent intent = new Intent(BluetoothDevice.ACTION_UUID); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); - intent.putExtra(BluetoothDevice.EXTRA_UUID, uuid); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - mUuidIntentTracker.remove(address); - } - - /*package*/ synchronized void makeServiceChannelCallbacks(String address) { - for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator(); - iter.hasNext();) { - RemoteService service = iter.next(); - if (service.address.equals(address)) { - if (DBG) Log.d(TAG, "Cleaning up failed UUID channel lookup: " - + service.address + " " + service.uuid); - IBluetoothCallback callback = mUuidCallbackTracker.get(service); - if (callback != null) { - try { - callback.onRfcommChannelFound(-1); - } catch (RemoteException e) {Log.e(TAG, "", e);} - } - - iter.remove(); - } - } - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); - - if (getBluetoothStateInternal() != BluetoothAdapter.STATE_ON) { - return; - } - - pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive); - pw.println("mIsAirplaneToggleable = " + mIsAirplaneToggleable); - - pw.println("Local address = " + getAddress()); - pw.println("Local name = " + getName()); - pw.println("isDiscovering() = " + isDiscovering()); - - mAdapter.getProfileProxy(mContext, - mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); - mAdapter.getProfileProxy(mContext, - mBluetoothProfileServiceListener, BluetoothProfile.INPUT_DEVICE); - mAdapter.getProfileProxy(mContext, - mBluetoothProfileServiceListener, BluetoothProfile.PAN); - - dumpKnownDevices(pw); - dumpAclConnectedDevices(pw); - dumpHeadsetService(pw); - dumpInputDeviceProfile(pw); - dumpPanProfile(pw); - dumpApplicationServiceRecords(pw); - dumpProfileState(pw); - } - - private void dumpProfileState(PrintWriter pw) { - pw.println("\n--Profile State dump--"); - pw.println("\n Headset profile state:" + - mAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)); - pw.println("\n A2dp profile state:" + - mAdapter.getProfileConnectionState(BluetoothProfile.A2DP)); - pw.println("\n HID profile state:" + - mAdapter.getProfileConnectionState(BluetoothProfile.INPUT_DEVICE)); - pw.println("\n PAN profile state:" + - mAdapter.getProfileConnectionState(BluetoothProfile.PAN)); - } - - private void dumpHeadsetService(PrintWriter pw) { - pw.println("\n--Headset Service--"); - if (mHeadsetProxy != null) { - List<BluetoothDevice> deviceList = mHeadsetProxy.getConnectedDevices(); - if (deviceList.size() == 0) { - pw.println("No headsets connected"); - } else { - BluetoothDevice device = deviceList.get(0); - pw.println("\ngetConnectedDevices[0] = " + device); - dumpHeadsetConnectionState(pw, device); - pw.println("getBatteryUsageHint() = " + - mHeadsetProxy.getBatteryUsageHint(device)); - } - - deviceList.clear(); - deviceList = mHeadsetProxy.getDevicesMatchingConnectionStates(new int[] { - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED}); - pw.println("--Connected and Disconnected Headsets"); - for (BluetoothDevice device: deviceList) { - pw.println(device); - if (mHeadsetProxy.isAudioConnected(device)) { - pw.println("SCO audio connected to device:" + device); - } - } - } - } - - private void dumpInputDeviceProfile(PrintWriter pw) { - pw.println("\n--Bluetooth Service- Input Device Profile"); - if (mInputDevice != null) { - List<BluetoothDevice> deviceList = mInputDevice.getConnectedDevices(); - if (deviceList.size() == 0) { - pw.println("No input devices connected"); - } else { - pw.println("Number of connected devices:" + deviceList.size()); - BluetoothDevice device = deviceList.get(0); - pw.println("getConnectedDevices[0] = " + device); - pw.println("Priority of Connected device = " + mInputDevice.getPriority(device)); - - switch (mInputDevice.getConnectionState(device)) { - case BluetoothInputDevice.STATE_CONNECTING: - pw.println("getConnectionState() = STATE_CONNECTING"); - break; - case BluetoothInputDevice.STATE_CONNECTED: - pw.println("getConnectionState() = STATE_CONNECTED"); - break; - case BluetoothInputDevice.STATE_DISCONNECTING: - pw.println("getConnectionState() = STATE_DISCONNECTING"); - break; - } - } - deviceList.clear(); - deviceList = mInputDevice.getDevicesMatchingConnectionStates(new int[] { - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED}); - pw.println("--Connected and Disconnected input devices"); - for (BluetoothDevice device: deviceList) { - pw.println(device); - } - } - } - - private void dumpPanProfile(PrintWriter pw) { - pw.println("\n--Bluetooth Service- Pan Profile"); - if (mPan != null) { - List<BluetoothDevice> deviceList = mPan.getConnectedDevices(); - if (deviceList.size() == 0) { - pw.println("No Pan devices connected"); - } else { - pw.println("Number of connected devices:" + deviceList.size()); - BluetoothDevice device = deviceList.get(0); - pw.println("getConnectedDevices[0] = " + device); - - switch (mPan.getConnectionState(device)) { - case BluetoothInputDevice.STATE_CONNECTING: - pw.println("getConnectionState() = STATE_CONNECTING"); - break; - case BluetoothInputDevice.STATE_CONNECTED: - pw.println("getConnectionState() = STATE_CONNECTED"); - break; - case BluetoothInputDevice.STATE_DISCONNECTING: - pw.println("getConnectionState() = STATE_DISCONNECTING"); - break; - } - } - deviceList.clear(); - deviceList = mPan.getDevicesMatchingConnectionStates(new int[] { - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED}); - pw.println("--Connected and Disconnected Pan devices"); - for (BluetoothDevice device: deviceList) { - pw.println(device); - } - } - } - - private void dumpHeadsetConnectionState(PrintWriter pw, - BluetoothDevice device) { - switch (mHeadsetProxy.getConnectionState(device)) { - case BluetoothHeadset.STATE_CONNECTING: - pw.println("getConnectionState() = STATE_CONNECTING"); - break; - case BluetoothHeadset.STATE_CONNECTED: - pw.println("getConnectionState() = STATE_CONNECTED"); - break; - case BluetoothHeadset.STATE_DISCONNECTING: - pw.println("getConnectionState() = STATE_DISCONNECTING"); - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - pw.println("getConnectionState() = STATE_AUDIO_CONNECTED"); - break; - } - } - - private void dumpApplicationServiceRecords(PrintWriter pw) { - pw.println("\n--Application Service Records--"); - for (Integer handle : mServiceRecordToPid.keySet()) { - Integer pid = mServiceRecordToPid.get(handle).pid; - pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle)); - } - } - - private void dumpAclConnectedDevices(PrintWriter pw) { - String[] devicesObjectPath = getKnownDevices(); - pw.println("\n--ACL connected devices--"); - if (devicesObjectPath != null) { - for (String device : devicesObjectPath) { - pw.println(getAddressFromObjectPath(device)); - } - } - } - - private void dumpKnownDevices(PrintWriter pw) { - pw.println("\n--Known devices--"); - for (String address : mDeviceProperties.keySet()) { - int bondState = mBondState.getBondState(address); - pw.printf("%s %10s (%d) %s\n", address, - toBondStateString(bondState), - mBondState.getAttempt(address), - getRemoteName(address)); - - Map<ParcelUuid, Integer> uuidChannels = mDeviceServiceChannelCache.get(address); - if (uuidChannels == null) { - pw.println("\tuuids = null"); - } else { - for (ParcelUuid uuid : uuidChannels.keySet()) { - Integer channel = uuidChannels.get(uuid); - if (channel == null) { - pw.println("\t" + uuid); - } else { - pw.println("\t" + uuid + " RFCOMM channel = " + channel); - } - } - } - for (RemoteService service : mUuidCallbackTracker.keySet()) { - if (service.address.equals(address)) { - pw.println("\tPENDING CALLBACK: " + service.uuid); - } - } - } - } - - private void getProfileProxy() { - mAdapter.getProfileProxy(mContext, - mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); - } - - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (profile == BluetoothProfile.HEADSET) { - mHeadsetProxy = (BluetoothHeadset) proxy; - } else if (profile == BluetoothProfile.INPUT_DEVICE) { - mInputDevice = (BluetoothInputDevice) proxy; - } else if (profile == BluetoothProfile.PAN) { - mPan = (BluetoothPan) proxy; - } - } - public void onServiceDisconnected(int profile) { - if (profile == BluetoothProfile.HEADSET) { - mHeadsetProxy = null; - } else if (profile == BluetoothProfile.INPUT_DEVICE) { - mInputDevice = null; - } else if (profile == BluetoothProfile.PAN) { - mPan = null; - } - } - }; - - /* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) { - if (pairable && discoverable) - return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; - else if (pairable && !discoverable) - return BluetoothAdapter.SCAN_MODE_CONNECTABLE; - else - return BluetoothAdapter.SCAN_MODE_NONE; - } - - /* package */ static String scanModeToBluezString(int mode) { - switch (mode) { - case BluetoothAdapter.SCAN_MODE_NONE: - return "off"; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE: - return "connectable"; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: - return "discoverable"; - } - return null; - } - - /*package*/ String getAddressFromObjectPath(String objectPath) { - String adapterObjectPath = mAdapterProperties.getObjectPath(); - if (adapterObjectPath == null || objectPath == null) { - Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath + - " or deviceObjectPath:" + objectPath + " is null"); - return null; - } - if (!objectPath.startsWith(adapterObjectPath)) { - Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath + - " is not a prefix of deviceObjectPath:" + objectPath + - "bluetoothd crashed ?"); - return null; - } - String address = objectPath.substring(adapterObjectPath.length()); - if (address != null) return address.replace('_', ':'); - - Log.e(TAG, "getAddressFromObjectPath: Address being returned is null"); - return null; - } - - /*package*/ String getObjectPathFromAddress(String address) { - String path = mAdapterProperties.getObjectPath(); - if (path == null) { - Log.e(TAG, "Error: Object Path is null"); - return null; - } - path = path + address.replace(":", "_"); - return path; - } - - /*package */ void setLinkTimeout(String address, int num_slots) { - String path = getObjectPathFromAddress(address); - boolean result = setLinkTimeoutNative(path, num_slots); - - if (!result) Log.d(TAG, "Set Link Timeout to " + num_slots + " slots failed"); - } - - /**** Handlers for PAN Profile ****/ - // TODO: This needs to be converted to a state machine. - - public boolean isTetheringOn() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothPanProfileHandler) { - return mBluetoothPanProfileHandler.isTetheringOn(); - } - } - - /*package*/boolean allowIncomingTethering() { - synchronized (mBluetoothPanProfileHandler) { - return mBluetoothPanProfileHandler.allowIncomingTethering(); - } - } - - public void setBluetoothTethering(boolean value) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothPanProfileHandler) { - mBluetoothPanProfileHandler.setBluetoothTethering(value); - } - } - - public int getPanDeviceConnectionState(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothPanProfileHandler) { - return mBluetoothPanProfileHandler.getPanDeviceConnectionState(device); - } - } - - public boolean connectPanDevice(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - synchronized (mBluetoothPanProfileHandler) { - return mBluetoothPanProfileHandler.connectPanDevice(device); - } - } - - public List<BluetoothDevice> getConnectedPanDevices() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothPanProfileHandler) { - return mBluetoothPanProfileHandler.getConnectedPanDevices(); - } - } - - public List<BluetoothDevice> getPanDevicesMatchingConnectionStates( - int[] states) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothPanProfileHandler) { - return mBluetoothPanProfileHandler.getPanDevicesMatchingConnectionStates(states); - } - } - - public boolean disconnectPanDevice(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - synchronized (mBluetoothPanProfileHandler) { - return mBluetoothPanProfileHandler.disconnectPanDevice(device); - } - } - - /*package*/void handlePanDeviceStateChange(BluetoothDevice device, - String iface, - int state, - int role) { - synchronized (mBluetoothPanProfileHandler) { - mBluetoothPanProfileHandler.handlePanDeviceStateChange(device, iface, state, role); - } - } - - /*package*/void handlePanDeviceStateChange(BluetoothDevice device, - int state, int role) { - synchronized (mBluetoothPanProfileHandler) { - mBluetoothPanProfileHandler.handlePanDeviceStateChange(device, null, state, role); - } - } - - /**** Handlers for Input Device Profile ****/ - // This needs to be converted to state machine - - public boolean connectInputDevice(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress()); - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.connectInputDevice(device, state); - } - } - - public boolean connectInputDeviceInternal(BluetoothDevice device) { - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.connectInputDeviceInternal(device); - } - } - - public boolean disconnectInputDevice(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress()); - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.disconnectInputDevice(device, state); - } - } - - public boolean disconnectInputDeviceInternal(BluetoothDevice device) { - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.disconnectInputDeviceInternal(device); - } - } - - public int getInputDeviceConnectionState(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.getInputDeviceConnectionState(device); - } - } - - public List<BluetoothDevice> getConnectedInputDevices() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.getConnectedInputDevices(); - } - } - - public List<BluetoothDevice> getInputDevicesMatchingConnectionStates( - int[] states) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.getInputDevicesMatchingConnectionStates(states); - } - } - - - public int getInputDevicePriority(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.getInputDevicePriority(device); - } - } - - public boolean setInputDevicePriority(BluetoothDevice device, int priority) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.setInputDevicePriority(device, priority); - } - } - - /** - * Handle incoming profile acceptance for profiles handled by Bluetooth Service, - * currently PAN and HID. This also is the catch all for all rejections for profiles - * that is not supported. - * - * @param device - Bluetooth Device - * @param allow - true / false - * @return - */ - public boolean allowIncomingProfileConnect(BluetoothDevice device, boolean allow) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - String address = device.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return false; - } - - Integer data = getAuthorizationAgentRequestData(address); - if (data == null) { - Log.w(TAG, "allowIncomingProfileConnect(" + device + - ") called but no native data available"); - return false; - } - if (DBG) log("allowIncomingProfileConnect: " + device + " : " + allow + " : " + data); - return setAuthorizationNative(address, allow, data.intValue()); - } - - /*package*/List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) { - synchronized (mBluetoothInputProfileHandler) { - return mBluetoothInputProfileHandler.lookupInputDevicesMatchingStates(states); - } - } - - /*package*/void handleInputDevicePropertyChange(String address, boolean connected) { - synchronized (mBluetoothInputProfileHandler) { - mBluetoothInputProfileHandler.handleInputDevicePropertyChange(address, connected); - } - } - - /**** Handlers for Health Device Profile ****/ - // TODO: All these need to be converted to a state machine. - - public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config, - IBluetoothHealthCallback callback) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler.registerAppConfiguration(config, callback); - } - } - - public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler.unregisterAppConfiguration(config); - } - } - - - public boolean connectChannelToSource(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler.connectChannelToSource(device, - config); - } - } - - public boolean connectChannelToSink(BluetoothDevice device, - BluetoothHealthAppConfiguration config, int channelType) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler.connectChannel(device, config, - channelType); - } - } - - public boolean disconnectChannel(BluetoothDevice device, - BluetoothHealthAppConfiguration config, int id) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler.disconnectChannel(device, config, id); - } - } - - public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler.getMainChannelFd(device, config); - } - } - - /*package*/ void onHealthDevicePropertyChanged(String devicePath, - String channelPath) { - synchronized (mBluetoothHealthProfileHandler) { - mBluetoothHealthProfileHandler.onHealthDevicePropertyChanged(devicePath, - channelPath); - } - } - - /*package*/ void onHealthDeviceChannelChanged(String devicePath, - String channelPath, boolean exists) { - synchronized(mBluetoothHealthProfileHandler) { - mBluetoothHealthProfileHandler.onHealthDeviceChannelChanged(devicePath, - channelPath, exists); - } - } - - /*package*/ void onHealthDeviceChannelConnectionError(int channelCode, - int newState) { - synchronized(mBluetoothHealthProfileHandler) { - mBluetoothHealthProfileHandler.onHealthDeviceChannelConnectionError(channelCode, - newState); - } - } - - public int getHealthDeviceConnectionState(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler.getHealthDeviceConnectionState(device); - } - } - - public List<BluetoothDevice> getConnectedHealthDevices() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler.getConnectedHealthDevices(); - } - } - - public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates( - int[] states) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, - "Need BLUETOOTH permission"); - synchronized (mBluetoothHealthProfileHandler) { - return mBluetoothHealthProfileHandler. - getHealthDevicesMatchingConnectionStates(states); - } - } - - /*package*/boolean notifyIncomingHidConnection(String address) { - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state == null) { - return false; - } - Message msg = new Message(); - msg.what = BluetoothDeviceProfileState.CONNECT_HID_INCOMING; - state.sendMessage(msg); - return true; - } - - public boolean connectHeadset(String address) { - if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false; - - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state != null) { - Message msg = new Message(); - msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING; - msg.obj = state; - mHfpProfileState.sendMessage(msg); - return true; - } - return false; - } - - public boolean disconnectHeadset(String address) { - if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false; - - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state != null) { - Message msg = new Message(); - msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HFP_OUTGOING; - msg.obj = state; - mHfpProfileState.sendMessage(msg); - return true; - } - return false; - } - - public boolean connectSink(String address) { - if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false; - - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state != null) { - Message msg = new Message(); - msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING; - msg.obj = state; - mA2dpProfileState.sendMessage(msg); - return true; - } - return false; - } - - public boolean disconnectSink(String address) { - if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false; - - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state != null) { - Message msg = new Message(); - msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_A2DP_OUTGOING; - msg.obj = state; - mA2dpProfileState.sendMessage(msg); - return true; - } - return false; - } - - BluetoothDeviceProfileState addProfileState(String address, boolean setTrust) { - BluetoothDeviceProfileState state = - new BluetoothDeviceProfileState(mContext, address, this, mA2dpService, setTrust); - mDeviceProfileState.put(address, state); - state.start(); - return state; - } - - void removeProfileState(String address) { - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state == null) return; - - state.doQuit(); - mDeviceProfileState.remove(address); - } - - String[] getKnownDevices() { - String[] bonds = null; - String val = getProperty("Devices", true); - if (val != null) { - bonds = val.split(","); - } - return bonds; - } - - private void initProfileState() { - String[] bonds = null; - String val = getProperty("Devices", false); - if (val != null) { - bonds = val.split(","); - } - if (bonds == null) { - return; - } - for (String path : bonds) { - String address = getAddressFromObjectPath(path); - BluetoothDeviceProfileState state = addProfileState(address, false); - } - } - - private void autoConnect() { - synchronized (this) { - if (!mAllowConnect) { - Log.d(TAG, "Not auto-connecting devices because of temporary BT on state."); - return; - } - } - - String[] bonds = getKnownDevices(); - if (bonds == null) { - return; - } - for (String path : bonds) { - String address = getAddressFromObjectPath(path); - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state != null) { - Message msg = new Message(); - msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES; - state.sendMessage(msg); - } - } - } - - public boolean notifyIncomingConnection(String address, boolean rejected) { - synchronized (this) { - if (!mAllowConnect) { - Log.d(TAG, "Not allowing incoming connection because of temporary BT on state."); - return false; - } - } - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state != null) { - Message msg = new Message(); - if (rejected) { - if (mA2dpService.getPriority(getRemoteDevice(address)) >= - BluetoothProfile.PRIORITY_ON) { - msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES; - msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING; - state.sendMessageDelayed(msg, - BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES_DELAY); - } - } else { - msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING; - state.sendMessage(msg); - } - return true; - } - return false; - } - - /*package*/ boolean notifyIncomingA2dpConnection(String address, boolean rejected) { - synchronized (this) { - if (!mAllowConnect) { - Log.d(TAG, "Not allowing a2dp connection because of temporary BT on state."); - return false; - } - } - - BluetoothDeviceProfileState state = mDeviceProfileState.get(address); - if (state != null) { - Message msg = new Message(); - if (rejected) { - if (mHeadsetProxy.getPriority(getRemoteDevice(address)) >= - BluetoothProfile.PRIORITY_ON) { - msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES; - msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING; - state.sendMessageDelayed(msg, - BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES_DELAY); - } - } else { - msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING; - state.sendMessage(msg); - } - return true; - } - return false; - } - - /*package*/ void setA2dpService(BluetoothA2dpService a2dpService) { - mA2dpService = a2dpService; - } - - /*package*/ Integer getAuthorizationAgentRequestData(String address) { - Integer data = mEventLoop.getAuthorizationAgentRequestData().remove(address); - return data; - } - - public void sendProfileStateMessage(int profile, int cmd) { - Message msg = new Message(); - msg.what = cmd; - if (profile == BluetoothProfileState.HFP) { - mHfpProfileState.sendMessage(msg); - } else if (profile == BluetoothProfileState.A2DP) { - mA2dpProfileState.sendMessage(msg); - } - } - - public int getAdapterConnectionState() { - return mAdapterConnectionState; - } - - public int getProfileConnectionState(int profile) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - - Pair<Integer, Integer> state = mProfileConnectionState.get(profile); - if (state == null) return BluetoothProfile.STATE_DISCONNECTED; - - return state.first; - } - - private void updateProfileConnectionState(int profile, int newState, int oldState) { - // mProfileConnectionState is a hashmap - - // <Integer, Pair<Integer, Integer>> - // The key is the profile, the value is a pair. first element - // is the state and the second element is the number of devices - // in that state. - int numDev = 1; - int newHashState = newState; - boolean update = true; - - // The following conditions are considered in this function: - // 1. If there is no record of profile and state - update - // 2. If a new device's state is current hash state - increment - // number of devices in the state. - // 3. If a state change has happened to Connected or Connecting - // (if current state is not connected), update. - // 4. If numDevices is 1 and that device state is being updated, update - // 5. If numDevices is > 1 and one of the devices is changing state, - // decrement numDevices but maintain oldState if it is Connected or - // Connecting - Pair<Integer, Integer> stateNumDev = mProfileConnectionState.get(profile); - if (stateNumDev != null) { - int currHashState = stateNumDev.first; - numDev = stateNumDev.second; - - if (newState == currHashState) { - numDev ++; - } else if (newState == BluetoothProfile.STATE_CONNECTED || - (newState == BluetoothProfile.STATE_CONNECTING && - currHashState != BluetoothProfile.STATE_CONNECTED)) { - numDev = 1; - } else if (numDev == 1 && oldState == currHashState) { - update = true; - } else if (numDev > 1 && oldState == currHashState) { - numDev --; - - if (currHashState == BluetoothProfile.STATE_CONNECTED || - currHashState == BluetoothProfile.STATE_CONNECTING) { - newHashState = currHashState; - } - } else { - update = false; - } - } - - if (update) { - mProfileConnectionState.put(profile, new Pair<Integer, Integer>(newHashState, - numDev)); - } - } - - public synchronized void sendConnectionStateChange(BluetoothDevice - device, int profile, int state, int prevState) { - // Since this is a binder call check if Bluetooth is on still - if (getBluetoothStateInternal() == BluetoothAdapter.STATE_OFF) return; - - if (!validateProfileConnectionState(state) || - !validateProfileConnectionState(prevState)) { - // Previously, an invalid state was broadcast anyway, - // with the invalid state converted to -1 in the intent. - // Better to log an error and not send an intent with - // invalid contents or set mAdapterConnectionState to -1. - Log.e(TAG, "Error in sendConnectionStateChange: " - + "prevState " + prevState + " state " + state); - return; - } - - updateProfileConnectionState(profile, state, prevState); - - if (updateCountersAndCheckForConnectionStateChange(state, prevState)) { - mAdapterConnectionState = state; - - if (state == BluetoothProfile.STATE_DISCONNECTED) { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.ALL_DEVICES_DISCONNECTED); - } - - Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, - convertToAdapterState(state)); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, - convertToAdapterState(prevState)); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - Log.d(TAG, "CONNECTION_STATE_CHANGE: " + device + ": " - + prevState + " -> " + state); - } - } - - private boolean validateProfileConnectionState(int state) { - return (state == BluetoothProfile.STATE_DISCONNECTED || - state == BluetoothProfile.STATE_CONNECTING || - state == BluetoothProfile.STATE_CONNECTED || - state == BluetoothProfile.STATE_DISCONNECTING); - } - - private int convertToAdapterState(int state) { - switch (state) { - case BluetoothProfile.STATE_DISCONNECTED: - return BluetoothAdapter.STATE_DISCONNECTED; - case BluetoothProfile.STATE_DISCONNECTING: - return BluetoothAdapter.STATE_DISCONNECTING; - case BluetoothProfile.STATE_CONNECTED: - return BluetoothAdapter.STATE_CONNECTED; - case BluetoothProfile.STATE_CONNECTING: - return BluetoothAdapter.STATE_CONNECTING; - } - Log.e(TAG, "Error in convertToAdapterState"); - return -1; - } - - private boolean updateCountersAndCheckForConnectionStateChange(int state, int prevState) { - switch (prevState) { - case BluetoothProfile.STATE_CONNECTING: - mProfilesConnecting--; - break; - - case BluetoothProfile.STATE_CONNECTED: - mProfilesConnected--; - break; - - case BluetoothProfile.STATE_DISCONNECTING: - mProfilesDisconnecting--; - break; - } - - switch (state) { - case BluetoothProfile.STATE_CONNECTING: - mProfilesConnecting++; - return (mProfilesConnected == 0 && mProfilesConnecting == 1); - - case BluetoothProfile.STATE_CONNECTED: - mProfilesConnected++; - return (mProfilesConnected == 1); - - case BluetoothProfile.STATE_DISCONNECTING: - mProfilesDisconnecting++; - return (mProfilesConnected == 0 && mProfilesDisconnecting == 1); - - case BluetoothProfile.STATE_DISCONNECTED: - return (mProfilesConnected == 0 && mProfilesConnecting == 0); - - default: - return true; - } - } - - private void createIncomingConnectionStateFile() { - File f = new File(INCOMING_CONNECTION_FILE); - if (!f.exists()) { - try { - f.createNewFile(); - } catch (IOException e) { - Log.e(TAG, "IOException: cannot create file"); - } - } - } - - /** @hide */ - public Pair<Integer, String> getIncomingState(String address) { - if (mIncomingConnections.isEmpty()) { - createIncomingConnectionStateFile(); - readIncomingConnectionState(); - } - return mIncomingConnections.get(address); - } - - private void readIncomingConnectionState() { - synchronized(mIncomingConnections) { - FileInputStream fstream = null; - try { - fstream = new FileInputStream(INCOMING_CONNECTION_FILE); - DataInputStream in = new DataInputStream(fstream); - BufferedReader file = new BufferedReader(new InputStreamReader(in)); - String line; - while((line = file.readLine()) != null) { - line = line.trim(); - if (line.length() == 0) continue; - String[] value = line.split(","); - if (value != null && value.length == 3) { - Integer val1 = Integer.parseInt(value[1]); - Pair<Integer, String> val = new Pair(val1, value[2]); - mIncomingConnections.put(value[0], val); - } - } - } catch (FileNotFoundException e) { - log("FileNotFoundException: readIncomingConnectionState" + e.toString()); - } catch (IOException e) { - log("IOException: readIncomingConnectionState" + e.toString()); - } finally { - if (fstream != null) { - try { - fstream.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - } - - private void truncateIncomingConnectionFile() { - RandomAccessFile r = null; - try { - r = new RandomAccessFile(INCOMING_CONNECTION_FILE, "rw"); - r.setLength(0); - } catch (FileNotFoundException e) { - log("FileNotFoundException: truncateIncomingConnectionState" + e.toString()); - } catch (IOException e) { - log("IOException: truncateIncomingConnectionState" + e.toString()); - } finally { - if (r != null) { - try { - r.close(); - } catch (IOException e) { - // ignore - } - } - } - } - - /** @hide */ - public void writeIncomingConnectionState(String address, Pair<Integer, String> data) { - synchronized(mIncomingConnections) { - mIncomingConnections.put(address, data); - - truncateIncomingConnectionFile(); - BufferedWriter out = null; - StringBuilder value = new StringBuilder(); - try { - out = new BufferedWriter(new FileWriter(INCOMING_CONNECTION_FILE, true)); - for (String devAddress: mIncomingConnections.keySet()) { - Pair<Integer, String> val = mIncomingConnections.get(devAddress); - value.append(devAddress); - value.append(","); - value.append(val.first.toString()); - value.append(","); - value.append(val.second); - value.append("\n"); - } - out.write(value.toString()); - } catch (FileNotFoundException e) { - log("FileNotFoundException: writeIncomingConnectionState" + e.toString()); - } catch (IOException e) { - log("IOException: writeIncomingConnectionState" + e.toString()); - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private native static void classInitNative(); - private native void initializeNativeDataNative(); - private native boolean setupNativeDataNative(); - private native boolean tearDownNativeDataNative(); - private native void cleanupNativeDataNative(); - /*package*/ native String getAdapterPathNative(); - - private native int isEnabledNative(); - /*package*/ native int enableNative(); - /*package*/ native int disableNative(); - - /*package*/ native Object[] getAdapterPropertiesNative(); - private native Object[] getDevicePropertiesNative(String objectPath); - private native boolean setAdapterPropertyStringNative(String key, String value); - private native boolean setAdapterPropertyIntegerNative(String key, int value); - private native boolean setAdapterPropertyBooleanNative(String key, int value); - - private native boolean startDiscoveryNative(); - private native boolean stopDiscoveryNative(); - - private native boolean createPairedDeviceNative(String address, int timeout_ms); - private native boolean createPairedDeviceOutOfBandNative(String address, int timeout_ms); - private native byte[] readAdapterOutOfBandDataNative(); - - private native boolean cancelDeviceCreationNative(String address); - private native boolean removeDeviceNative(String objectPath); - private native int getDeviceServiceChannelNative(String objectPath, String uuid, - int attributeId); - - private native boolean cancelPairingUserInputNative(String address, int nativeData); - private native boolean setPinNative(String address, String pin, int nativeData); - private native boolean setPasskeyNative(String address, int passkey, int nativeData); - private native boolean setPairingConfirmationNative(String address, boolean confirm, - int nativeData); - private native boolean setRemoteOutOfBandDataNative(String address, byte[] hash, - byte[] randomizer, int nativeData); - - private native boolean setDevicePropertyBooleanNative(String objectPath, String key, - int value); - private native boolean setDevicePropertyStringNative(String objectPath, String key, - String value); - private native boolean createDeviceNative(String address); - /*package*/ native boolean discoverServicesNative(String objectPath, String pattern); - - private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb, - short channel); - private native boolean removeServiceRecordNative(int handle); - private native boolean setLinkTimeoutNative(String path, int num_slots); - - native boolean connectInputDeviceNative(String path); - native boolean disconnectInputDeviceNative(String path); - - native boolean setBluetoothTetheringNative(boolean value, String nap, String bridge); - native boolean connectPanDeviceNative(String path, String dstRole); - native boolean disconnectPanDeviceNative(String path); - native boolean disconnectPanServerDeviceNative(String path, - String address, String iface); - - private native int[] addReservedServiceRecordsNative(int[] uuuids); - private native boolean removeReservedServiceRecordsNative(int[] handles); - - // Health API - native String registerHealthApplicationNative(int dataType, String role, String name, - String channelType); - native String registerHealthApplicationNative(int dataType, String role, String name); - native boolean unregisterHealthApplicationNative(String path); - native boolean createChannelNative(String devicePath, String appPath, String channelType, - int code); - native boolean destroyChannelNative(String devicePath, String channelpath, int code); - native String getMainChannelNative(String path); - native String getChannelApplicationNative(String channelPath); - native ParcelFileDescriptor getChannelFdNative(String channelPath); - native boolean releaseChannelFdNative(String channelPath); - native boolean setAuthorizationNative(String address, boolean value, int data); -} diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index c783e6a..bc3efdd 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -18,6 +18,9 @@ package android.server.search; import com.android.internal.content.PackageMonitor; +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.AppGlobals; import android.app.ISearchManager; import android.app.SearchManager; import android.app.SearchableInfo; @@ -27,11 +30,19 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; +import android.os.Binder; import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; import java.util.List; @@ -48,7 +59,7 @@ public class SearchManagerService extends ISearchManager.Stub { private final Context mContext; // This field is initialized lazily in getSearchables(), and then never modified. - private Searchables mSearchables; + private SparseArray<Searchables> mSearchables; private ContentObserver mGlobalSearchObserver; @@ -66,14 +77,25 @@ public class SearchManagerService extends ISearchManager.Stub { mContext.getContentResolver()); } - private synchronized Searchables getSearchables() { + private synchronized Searchables getSearchables(int userId) { if (mSearchables == null) { - Log.i(TAG, "Building list of searchable activities"); new MyPackageMonitor().register(mContext, null, true); - mSearchables = new Searchables(mContext); - mSearchables.buildSearchableList(); + mSearchables = new SparseArray<Searchables>(); } - return mSearchables; + Searchables searchables = mSearchables.get(userId); + + long origId = Binder.clearCallingIdentity(); + boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)) + .getUserInfo(userId) != null; + Binder.restoreCallingIdentity(origId); + + if (searchables == null && userExists) { + Log.i(TAG, "Building list of searchable activities for userId=" + userId); + searchables = new Searchables(mContext, userId); + searchables.buildSearchableList(); + mSearchables.append(userId, searchables); + } + return searchables; } /** @@ -87,7 +109,7 @@ public class SearchManagerService extends ISearchManager.Stub { public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); mContext.unregisterReceiver(BootCompletedReceiver.this); - getSearchables(); + getSearchables(0); } }.start(); } @@ -109,12 +131,16 @@ public class SearchManagerService extends ISearchManager.Stub { } private void updateSearchables() { - // Update list of searchable activities - getSearchables().buildSearchableList(); + synchronized (SearchManagerService.this) { + // Update list of searchable activities + for (int i = 0; i < mSearchables.size(); i++) { + getSearchables(mSearchables.keyAt(i)).buildSearchableList(); + } + } // Inform all listeners that the list of searchables has been updated. Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } } @@ -132,10 +158,14 @@ public class SearchManagerService extends ISearchManager.Stub { @Override public void onChange(boolean selfChange) { - getSearchables().buildSearchableList(); + synchronized (SearchManagerService.this) { + for (int i = 0; i < mSearchables.size(); i++) { + getSearchables(mSearchables.keyAt(i)).buildSearchableList(); + } + } Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } } @@ -156,32 +186,76 @@ public class SearchManagerService extends ISearchManager.Stub { Log.e(TAG, "getSearchableInfo(), activity == null"); return null; } - return getSearchables().getSearchableInfo(launchActivity); + return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity); } /** * Returns a list of the searchable activities that can be included in global search. */ public List<SearchableInfo> getSearchablesInGlobalSearch() { - return getSearchables().getSearchablesInGlobalSearchList(); + return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList(); } public List<ResolveInfo> getGlobalSearchActivities() { - return getSearchables().getGlobalSearchActivities(); + return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities(); } /** * Gets the name of the global search activity. */ public ComponentName getGlobalSearchActivity() { - return getSearchables().getGlobalSearchActivity(); + return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity(); } /** * Gets the name of the web search activity. */ public ComponentName getWebSearchActivity() { - return getSearchables().getWebSearchActivity(); + return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity(); } + @Override + public ComponentName getAssistIntent(int userHandle) { + try { + if (userHandle != UserHandle.getCallingUserId()) { + // Requesting a different user, make sure that they have the permission + if (ActivityManager.checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Binder.getCallingUid(), -1, true) + == PackageManager.PERMISSION_GRANTED) { + // Translate to the current user id, if caller wasn't aware + if (userHandle == UserHandle.USER_CURRENT) { + long identity = Binder.clearCallingIdentity(); + userHandle = ActivityManagerNative.getDefault().getCurrentUser().id; + Binder.restoreCallingIdentity(identity); + } + } else { + String msg = "Permission Denial: " + + "Request to getAssistIntent for " + userHandle + + " but is calling from user " + UserHandle.getCallingUserId() + + "; this requires " + + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + return null; + } + } + IPackageManager pm = AppGlobals.getPackageManager(); + Intent assistIntent = new Intent(Intent.ACTION_ASSIST); + ResolveInfo info = + pm.resolveIntent(assistIntent, + assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.MATCH_DEFAULT_ONLY, userHandle); + if (info != null) { + return new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + } + } catch (RemoteException re) { + // Local call + Log.e(TAG, "RemoteException in getAssistIntent: " + re); + } catch (Exception e) { + Log.e(TAG, "Exception in getAssistIntent: " + e); + } + return null; + } } diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java index f24d52f..30ca340 100644 --- a/core/java/android/server/search/Searchables.java +++ b/core/java/android/server/search/Searchables.java @@ -16,6 +16,7 @@ package android.server.search; +import android.app.AppGlobals; import android.app.SearchManager; import android.app.SearchableInfo; import android.content.ComponentName; @@ -23,9 +24,12 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.Binder; import android.os.Bundle; +import android.os.RemoteException; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -38,6 +42,7 @@ import java.util.List; /** * This class maintains the information about all searchable activities. + * This is a hidden class. */ public class Searchables { @@ -65,12 +70,19 @@ public class Searchables { public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME = "com.google.android.providers.enhancedgooglesearch/.Launcher"; + // Cache the package manager instance + final private IPackageManager mPm; + // User for which this Searchables caches information + private int mUserId; + /** * * @param context Context to use for looking up activities etc. */ - public Searchables (Context context) { + public Searchables (Context context, int userId) { mContext = context; + mUserId = userId; + mPm = AppGlobals.getPackageManager(); } /** @@ -115,50 +127,50 @@ public class Searchables { ActivityInfo ai = null; try { - ai = mContext.getPackageManager(). - getActivityInfo(activity, PackageManager.GET_META_DATA ); - String refActivityName = null; + ai = mPm.getActivityInfo(activity, PackageManager.GET_META_DATA, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error getting activity info " + re); + return null; + } + String refActivityName = null; - // First look for activity-specific reference - Bundle md = ai.metaData; + // First look for activity-specific reference + Bundle md = ai.metaData; + if (md != null) { + refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE); + } + // If not found, try for app-wide reference + if (refActivityName == null) { + md = ai.applicationInfo.metaData; if (md != null) { refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE); } - // If not found, try for app-wide reference - if (refActivityName == null) { - md = ai.applicationInfo.metaData; - if (md != null) { - refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE); - } - } + } - // Irrespective of source, if a reference was found, follow it. - if (refActivityName != null) - { - // This value is deprecated, return null - if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) { - return null; - } - String pkg = activity.getPackageName(); - ComponentName referredActivity; - if (refActivityName.charAt(0) == '.') { - referredActivity = new ComponentName(pkg, pkg + refActivityName); - } else { - referredActivity = new ComponentName(pkg, refActivityName); - } + // Irrespective of source, if a reference was found, follow it. + if (refActivityName != null) + { + // This value is deprecated, return null + if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) { + return null; + } + String pkg = activity.getPackageName(); + ComponentName referredActivity; + if (refActivityName.charAt(0) == '.') { + referredActivity = new ComponentName(pkg, pkg + refActivityName); + } else { + referredActivity = new ComponentName(pkg, refActivityName); + } - // Now try the referred activity, and if found, cache - // it against the original name so we can skip the check - synchronized (this) { - result = mSearchablesMap.get(referredActivity); - if (result != null) { - mSearchablesMap.put(activity, result); - return result; - } + // Now try the referred activity, and if found, cache + // it against the original name so we can skip the check + synchronized (this) { + result = mSearchablesMap.get(referredActivity); + if (result != null) { + mSearchablesMap.put(activity, result); + return result; } } - } catch (PackageManager.NameNotFoundException e) { - // case 3: no metadata } // Step 3. None found. Return null. @@ -195,22 +207,22 @@ public class Searchables { ArrayList<SearchableInfo> newSearchablesInGlobalSearchList = new ArrayList<SearchableInfo>(); - final PackageManager pm = mContext.getPackageManager(); - // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers. List<ResolveInfo> searchList; final Intent intent = new Intent(Intent.ACTION_SEARCH); - searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); + + searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA); List<ResolveInfo> webSearchInfoList; final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH); - webSearchInfoList = pm.queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA); + webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA); // analyze each one, generate a Searchables record, and record if (searchList != null || webSearchInfoList != null) { int search_count = (searchList == null ? 0 : searchList.size()); int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size()); int count = search_count + web_search_count; + long token = Binder.clearCallingIdentity(); for (int ii = 0; ii < count; ii++) { // for each component, try to find metadata ResolveInfo info = (ii < search_count) @@ -229,6 +241,7 @@ public class Searchables { } } } + Binder.restoreCallingIdentity(token); } List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities(); @@ -262,10 +275,8 @@ public class Searchables { // Step 1 : Query the package manager for a list // of activities that can handle the GLOBAL_SEARCH intent. Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); - PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> activities = - pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - + queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); if (activities != null && !activities.isEmpty()) { // Step 2: Rank matching activities according to our heuristics. Collections.sort(activities, GLOBAL_SEARCH_RANKER); @@ -301,10 +312,8 @@ public class Searchables { Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH); intent.setComponent(globalSearch); - PackageManager pm = mContext.getPackageManager(); - List<ResolveInfo> activities = - pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - + List<ResolveInfo> activities = queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); if (activities != null && !activities.isEmpty()) { return true; } @@ -374,9 +383,8 @@ public class Searchables { } Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); intent.setPackage(globalSearchActivity.getPackageName()); - PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> activities = - pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); if (activities != null && !activities.isEmpty()) { ActivityInfo ai = activities.get(0).activityInfo; @@ -387,6 +395,19 @@ public class Searchables { return null; } + private List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { + List<ResolveInfo> activities = null; + try { + activities = + mPm.queryIntentActivities(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags, mUserId); + } catch (RemoteException re) { + // Local call + } + return activities; + } + /** * Returns the list of searchable activities. */ diff --git a/core/java/android/service/dreams/Dream.java b/core/java/android/service/dreams/Dream.java index 83464c9..4e8b05b 100644 --- a/core/java/android/service/dreams/Dream.java +++ b/core/java/android/service/dreams/Dream.java @@ -1,392 +1,23 @@ /** - * + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.service.dreams; -import com.android.internal.policy.PolicyManager; - -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.graphics.drawable.ColorDrawable; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.Slog; -import android.view.ActionMode; -import android.view.IWindowManager; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager.LayoutParams; -import android.view.accessibility.AccessibilityEvent; -import android.view.WindowManager; -import android.view.WindowManagerImpl; - /** * @hide - * + * Temporarily needed to not break existing apps. */ -public class Dream extends Service implements Window.Callback { - private final static boolean DEBUG = true; - private final static String TAG = "Dream"; - - /** - * The {@link Intent} that must be declared as handled by the service. - * To be supported, the service must also require the - * {@link android.Manifest.permission#BIND_WALLPAPER} permission so - * that other applications can not abuse it. - */ - @SdkConstant(SdkConstantType.SERVICE_ACTION) - public static final String SERVICE_INTERFACE = - "android.service.dreams.Dream"; - - private Window mWindow; - - private WindowManager mWindowManager; - private IDreamManager mSandman; - - private boolean mInteractive; - - final Handler mHandler = new Handler(); - - boolean mFinished = false; - - // begin Window.Callback methods - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (!mInteractive) { - finish(); - return true; - } - return mWindow.superDispatchKeyEvent(event); - } - - @Override - public boolean dispatchKeyShortcutEvent(KeyEvent event) { - if (!mInteractive) { - finish(); - return true; - } - return mWindow.superDispatchKeyShortcutEvent(event); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - if (!mInteractive) { - finish(); - return true; - } - return mWindow.superDispatchTouchEvent(event); - } - - @Override - public boolean dispatchTrackballEvent(MotionEvent event) { - if (!mInteractive) { - finish(); - return true; - } - return mWindow.superDispatchTrackballEvent(event); - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent event) { - if (!mInteractive) { - finish(); - return true; - } - return mWindow.superDispatchGenericMotionEvent(event); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - return false; - } - - @Override - public View onCreatePanelView(int featureId) { - return null; - } - - @Override - public boolean onCreatePanelMenu(int featureId, Menu menu) { - return false; - } - - @Override - public boolean onPreparePanel(int featureId, View view, Menu menu) { - return false; - } - - @Override - public boolean onMenuOpened(int featureId, Menu menu) { - return false; - } - - @Override - public boolean onMenuItemSelected(int featureId, MenuItem item) { - return false; - } - - @Override - public void onWindowAttributesChanged(LayoutParams attrs) { - - } - - @Override - public void onContentChanged() { - - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - - } - - @Override - public void onAttachedToWindow() { - mWindow.addFlags( - WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - ); - lightsOut(); - } - - @Override - public void onDetachedFromWindow() { - } - - @Override - public void onPanelClosed(int featureId, Menu menu) { - } - - @Override - public boolean onSearchRequested() { - return false; - } - - @Override - public ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) { - return null; - } - - @Override - public void onActionModeStarted(ActionMode mode) { - } - - @Override - public void onActionModeFinished(ActionMode mode) { - } - // end Window.Callback methods - - public WindowManager getWindowManager() { - return mWindowManager; - } - - public Window getWindow() { - return mWindow; - } - - /** - * Called when this Dream is constructed. Place your initialization here. - * - * Subclasses must call through to the superclass implementation. - */ - @Override - public void onCreate() { - super.onCreate(); - - if (DEBUG) Slog.v(TAG, "Dream created on thread " + Thread.currentThread().getId()); - - mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams")); - } - - /** - * Called when this Dream is started. Place your initialization here. - * - * Subclasses must call through to the superclass implementation. - * - * XXX(dsandler) Might want to make this final and have a different method for clients to override - */ - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - return super.onStartCommand(intent, flags, startId); - } - - /** - * Inflate a layout resource and set it to be the content view for this Dream. - * Behaves similarly to {@link android.app.Activity#setContentView(int)}. - * - * @param layoutResID Resource ID to be inflated. - * - * @see #setContentView(android.view.View) - * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) - */ - public void setContentView(int layoutResID) { - getWindow().setContentView(layoutResID); - } - - /** - * Set a view to be the content view for this Dream. - * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)}, - * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view. - * - * @param view The desired content to display. - * - * @see #setContentView(int) - * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) - */ - public void setContentView(View view) { - getWindow().setContentView(view); - } - - /** - * Set a view to be the content view for this Dream. - * Behaves similarly to - * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}. - * - * @param view The desired content to display. - * @param params Layout parameters for the view. - * - * @see #setContentView(android.view.View) - * @see #setContentView(int) - */ - public void setContentView(View view, ViewGroup.LayoutParams params) { - getWindow().setContentView(view, params); - } - - /** - * Add a view to the Dream's window, leaving other content views in place. - * - * @param view The desired content to display. - * @param params Layout parameters for the view. - */ - public void addContentView(View view, ViewGroup.LayoutParams params) { - getWindow().addContentView(view, params); - } - - /** - * @param mInteractive the mInteractive to set - */ - public void setInteractive(boolean mInteractive) { - this.mInteractive = mInteractive; - } - - /** - * @return the mInteractive - */ - public boolean isInteractive() { - return mInteractive; - } - - /** Convenience method for setting View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view. */ - protected void lightsOut() { - // turn the lights down low - final View v = mWindow.getDecorView(); - if (v != null) { - v.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); - } - } - - /** - * Finds a view that was identified by the id attribute from the XML that - * was processed in {@link #onCreate}. - * - * @return The view if found or null otherwise. - */ - public View findViewById(int id) { - return getWindow().findViewById(id); - } - - /** - * Called when this Dream is being removed from the screen and stopped. - */ - @Override - public void onDestroy() { - super.onDestroy(); - mWindowManager.removeView(mWindow.getDecorView()); - } - - /** - * Creates a new dream window, attaches the current content view, and shows it. - * - * @param windowToken Binder to attach to the window to allow access to the correct window type. - * @hide - */ - final /*package*/ void attach(IBinder windowToken) { - if (DEBUG) Slog.v(TAG, "Dream attached on thread " + Thread.currentThread().getId()); - - mWindow = PolicyManager.makeNewWindow(this); - mWindow.setCallback(this); - mWindow.requestFeature(Window.FEATURE_NO_TITLE); - mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000)); - - if (DEBUG) Slog.v(TAG, "attaching window token: " + windowToken - + " to window of type " + WindowManager.LayoutParams.TYPE_DREAM); - - WindowManager.LayoutParams lp = mWindow.getAttributes(); - lp.type = WindowManager.LayoutParams.TYPE_DREAM; - lp.token = windowToken; - lp.windowAnimations = com.android.internal.R.style.Animation_Dream; - - //WindowManagerImpl.getDefault().addView(mWindow.getDecorView(), lp); - - if (DEBUG) Slog.v(TAG, "created and attached window: " + mWindow); - - mWindow.setWindowManager(null, windowToken, "dream", true); - mWindowManager = mWindow.getWindowManager(); - - // now make it visible - mHandler.post(new Runnable(){ - @Override - public void run() { - if (DEBUG) Slog.v(TAG, "Dream window added on thread " + Thread.currentThread().getId()); - - getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes()); - }}); - } - - /** - * Stop the dream and wake up. - * - * After this method is called, the service will be stopped. - */ - public void finish() { - if (mFinished) return; - try { - mSandman.awaken(); // assuming we were started by the DreamManager - stopSelf(); // if launched via any other means - mFinished = true; - } catch (RemoteException ex) { - // sigh - } - } - - class IDreamServiceWrapper extends IDreamService.Stub { - public IDreamServiceWrapper() { - } - - public void attach(IBinder windowToken) { - Dream.this.attach(windowToken); - } - } - - /** - * Implement to return the implementation of the internal accessibility - * service interface. Subclasses should not override. - */ - @Override - public final IBinder onBind(Intent intent) { - return new IDreamServiceWrapper(); - } +public class Dream extends DreamService { } diff --git a/core/java/android/service/dreams/DreamManagerService.java b/core/java/android/service/dreams/DreamManagerService.java deleted file mode 100644 index 4a14ced..0000000 --- a/core/java/android/service/dreams/DreamManagerService.java +++ /dev/null @@ -1,184 +0,0 @@ -package android.service.dreams; - -import static android.provider.Settings.Secure.SCREENSAVER_COMPONENT; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -import com.android.internal.view.IInputMethod; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.os.Binder; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.provider.Settings; -import android.util.Log; -import android.util.Slog; -import android.view.IWindowManager; -import android.view.WindowManager; - -/** - * - * @hide - * - */ - -public class DreamManagerService - extends IDreamManager.Stub - implements ServiceConnection -{ - private static final boolean DEBUG = true; - private static final String TAG = "DreamManagerService"; - - final Object mLock = new Object[0]; - - private Context mContext; - private IWindowManager mIWindowManager; - - private ComponentName mCurrentDreamComponent; - private IDreamService mCurrentDream; - private Binder mCurrentDreamToken; - - public DreamManagerService(Context context) { - if (DEBUG) Slog.v(TAG, "DreamManagerService startup"); - mContext = context; - mIWindowManager = IWindowManager.Stub.asInterface( - ServiceManager.getService(Context.WINDOW_SERVICE)); - } - - private void checkPermission(String permission) { - if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) { - throw new SecurityException("Access denied to process: " + Binder.getCallingPid() - + ", must have permission " + permission); - } - } - - // IDreamManager method - public void dream() { - ComponentName name = getDreamComponent(); - if (name != null) { - synchronized (mLock) { - final long ident = Binder.clearCallingIdentity(); - try { - bindDreamComponentL(name, false); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - } - - // IDreamManager method - public void setDreamComponent(ComponentName name) { - Settings.Secure.putString(mContext.getContentResolver(), SCREENSAVER_COMPONENT, name.flattenToString()); - } - - // IDreamManager method - public ComponentName getDreamComponent() { - // TODO(dsandler) don't load this every time, watch the value - String component = Settings.Secure.getString(mContext.getContentResolver(), SCREENSAVER_COMPONENT); - if (component == null) { - component = mContext.getResources().getString( - com.android.internal.R.string.config_defaultDreamComponent); - } - if (component != null) { - return ComponentName.unflattenFromString(component); - } else { - return null; - } - } - - // IDreamManager method - public void testDream(ComponentName name) { - if (DEBUG) Slog.v(TAG, "startDream name=" + name - + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); -// checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); - synchronized (mLock) { - final long ident = Binder.clearCallingIdentity(); - try { - bindDreamComponentL(name, true); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - // IDreamManager method - public void awaken() { - if (DEBUG) Slog.v(TAG, "awaken()"); - synchronized (mLock) { - if (mCurrentDream != null) { - mContext.unbindService(this); - } - } - } - - public void bindDreamComponentL(ComponentName componentName, boolean test) { - if (DEBUG) Slog.v(TAG, "bindDreamComponent: componentName=" + componentName - + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); - - Intent intent = new Intent(Intent.ACTION_MAIN) - .setComponent(componentName) - .addFlags( - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - ) - .putExtra("android.dreams.TEST", test); - - if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) { - Slog.w(TAG, "unable to bind service: " + componentName); - return; - } - mCurrentDreamComponent = componentName; - mCurrentDreamToken = new Binder(); - try { - if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurrentDreamToken - + " for window type: " + WindowManager.LayoutParams.TYPE_DREAM); - mIWindowManager.addWindowToken(mCurrentDreamToken, - WindowManager.LayoutParams.TYPE_DREAM); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to add window token. Proceed at your own risk."); - } - - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) Slog.v(TAG, "connected to dream: " + name + " binder=" + service + " thread=" + Thread.currentThread().getId()); - - mCurrentDream = IDreamService.Stub.asInterface(service); - try { - if (DEBUG) Slog.v(TAG, "attaching with token:" + mCurrentDreamToken); - mCurrentDream.attach(mCurrentDreamToken); - } catch (RemoteException ex) { - Slog.w(TAG, "Unable to send window token to dream:" + ex); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) Slog.v(TAG, "disconnected: " + name + " service: " + mCurrentDream); - mCurrentDream = null; - mCurrentDreamToken = null; - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); - - pw.println("Dreamland:"); - pw.print(" component="); pw.println(mCurrentDreamComponent); - pw.print(" token="); pw.println(mCurrentDreamToken); - pw.print(" dream="); pw.println(mCurrentDream); - } - - public void systemReady() { - if (DEBUG) Slog.v(TAG, "ready to dream!"); - } - -} diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java new file mode 100644 index 0000000..03b685b --- /dev/null +++ b/core/java/android/service/dreams/DreamService.java @@ -0,0 +1,625 @@ +/** + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.dreams; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.app.Service; +import android.content.Intent; +import android.graphics.drawable.ColorDrawable; +import android.os.Handler; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Slog; +import android.view.ActionMode; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.view.accessibility.AccessibilityEvent; + +import com.android.internal.policy.PolicyManager; + +/** + * Extend this class to implement a custom Dream. + * + * <p>Dreams are interactive screensavers launched when a charging device is idle, or docked in a + * desk dock. Dreams provide another modality for apps to express themselves, tailored for + * an exhibition/lean-back experience.</p> + * + * <p>Dreams should be declared in the manifest as follows:</p> + * <pre> + * <service + * android:name=".MyDream" + * android:exported="true" + * android:icon="@drawable/my_icon" + * android:label="@string/my_dream_label" > + * + * <intent-filter> + * <action android:name="android.service.dreams.DreamService" /> + * <category android:name="android.intent.category.DEFAULT" /> + * </intent-filter> + * + * <!-- Point to additional information for this dream (optional) --> + * <meta-data + * android:name="android.service.dream" + * android:resource="@xml/my_dream" /> + * </service> + * </pre> + * <p>If specified, additional information for the dream is defined using the + * <code><{@link android.R.styleable#Dream dream}></code> element. For example:</p> + * <pre> + * (in res/xml/my_dream.xml) + * + * <dream xmlns:android="http://schemas.android.com/apk/res/android" + * android:settingsActivity="com.example.app/.MyDreamSettingsActivity" /> + * </pre> + */ +public class DreamService extends Service implements Window.Callback { + private final static boolean DEBUG = true; + private final String TAG = DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; + + /** + * The name of the dream manager service. + * @hide + */ + public static final String DREAM_SERVICE = "dreams"; + + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.service.dreams.DreamService"; + + /** + * Name under which a Dream publishes information about itself. + * This meta-data must reference an XML resource containing + * a <code><{@link android.R.styleable#Dream dream}></code> + * tag. + */ + public static final String DREAM_META_DATA = "android.service.dream"; + + private final Handler mHandler = new Handler(); + private IBinder mWindowToken; + private Window mWindow; + private WindowManager mWindowManager; + private IDreamManager mSandman; + private boolean mInteractive = false; + private boolean mLowProfile = true; + private boolean mFullscreen = false; + private boolean mScreenBright = false; + private boolean mFinished; + + // begin Window.Callback methods + /** {@inheritDoc} */ + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK + if (!mInteractive) { + if (DEBUG) Slog.v(TAG, "Finishing on keyEvent"); + safelyFinish(); + return true; + } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + if (DEBUG) Slog.v(TAG, "Finishing on back key"); + safelyFinish(); + return true; + } + return mWindow.superDispatchKeyEvent(event); + } + + /** {@inheritDoc} */ + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + if (!mInteractive) { + if (DEBUG) Slog.v(TAG, "Finishing on keyShortcutEvent"); + safelyFinish(); + return true; + } + return mWindow.superDispatchKeyShortcutEvent(event); + } + + /** {@inheritDoc} */ + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + // TODO: create more flexible version of mInteractive that allows clicks + // but finish()es on any other kind of activity + if (!mInteractive) { + if (DEBUG) Slog.v(TAG, "Finishing on touchEvent"); + safelyFinish(); + return true; + } + return mWindow.superDispatchTouchEvent(event); + } + + /** {@inheritDoc} */ + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + if (!mInteractive) { + if (DEBUG) Slog.v(TAG, "Finishing on trackballEvent"); + safelyFinish(); + return true; + } + return mWindow.superDispatchTrackballEvent(event); + } + + /** {@inheritDoc} */ + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + if (!mInteractive) { + if (DEBUG) Slog.v(TAG, "Finishing on genericMotionEvent"); + safelyFinish(); + return true; + } + return mWindow.superDispatchGenericMotionEvent(event); + } + + /** {@inheritDoc} */ + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + return false; + } + + /** {@inheritDoc} */ + @Override + public View onCreatePanelView(int featureId) { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean onMenuOpened(int featureId, Menu menu) { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + return false; + } + + /** {@inheritDoc} */ + @Override + public void onWindowAttributesChanged(LayoutParams attrs) { + } + + /** {@inheritDoc} */ + @Override + public void onContentChanged() { + } + + /** {@inheritDoc} */ + @Override + public void onWindowFocusChanged(boolean hasFocus) { + } + + /** {@inheritDoc} */ + @Override + public void onAttachedToWindow() { + } + + /** {@inheritDoc} */ + @Override + public void onDetachedFromWindow() { + } + + /** {@inheritDoc} */ + @Override + public void onPanelClosed(int featureId, Menu menu) { + } + + /** {@inheritDoc} */ + @Override + public boolean onSearchRequested() { + return false; + } + + /** {@inheritDoc} */ + @Override + public ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) { + return null; + } + + /** {@inheritDoc} */ + @Override + public void onActionModeStarted(ActionMode mode) { + } + + /** {@inheritDoc} */ + @Override + public void onActionModeFinished(ActionMode mode) { + } + // end Window.Callback methods + + // begin public api + /** + * Retrieves the current {@link android.view.WindowManager} for the dream. + * Behaves similarly to {@link android.app.Activity#getWindowManager()}. + * + * @return The current window manager, or null if the dream is not started. + */ + public WindowManager getWindowManager() { + return mWindowManager; + } + + /** + * Retrieves the current {@link android.view.Window} for the dream. + * Behaves similarly to {@link android.app.Activity#getWindow()}. + * + * @return The current window, or null if the dream is not started. + */ + public Window getWindow() { + return mWindow; + } + + /** + * Inflates a layout resource and set it to be the content view for this Dream. + * Behaves similarly to {@link android.app.Activity#setContentView(int)}. + * + * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> + * + * @param layoutResID Resource ID to be inflated. + * + * @see #setContentView(android.view.View) + * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) + */ + public void setContentView(int layoutResID) { + getWindow().setContentView(layoutResID); + } + + /** + * Sets a view to be the content view for this Dream. + * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)}, + * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view. + * + * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> + * @param view The desired content to display. + * + * @see #setContentView(int) + * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) + */ + public void setContentView(View view) { + getWindow().setContentView(view); + } + + /** + * Sets a view to be the content view for this Dream. + * Behaves similarly to + * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}. + * + * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + * + * @see #setContentView(android.view.View) + * @see #setContentView(int) + */ + public void setContentView(View view, ViewGroup.LayoutParams params) { + getWindow().setContentView(view, params); + } + + /** + * Adds a view to the Dream's window, leaving other content views in place. + * + * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + */ + public void addContentView(View view, ViewGroup.LayoutParams params) { + getWindow().addContentView(view, params); + } + + /** + * Finds a view that was identified by the id attribute from the XML that + * was processed in {@link #onCreate}. + * + * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p> + * + * @return The view if found or null otherwise. + */ + public View findViewById(int id) { + return getWindow().findViewById(id); + } + + /** + * Marks this dream as interactive to receive input events. + * + * <p>Non-interactive dreams (default) will dismiss on the first input event.</p> + * + * <p>Interactive dreams should call {@link #finish()} to dismiss themselves.</p> + * + * @param interactive True if this dream will handle input events. + */ + public void setInteractive(boolean interactive) { + mInteractive = interactive; + } + + /** + * Returns whether or not this dream is interactive. Defaults to false. + * + * @see #setInteractive(boolean) + */ + public boolean isInteractive() { + return mInteractive; + } + + /** + * Sets View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view. + * + * @param lowProfile True to set View.SYSTEM_UI_FLAG_LOW_PROFILE + */ + public void setLowProfile(boolean lowProfile) { + mLowProfile = lowProfile; + int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE; + applySystemUiVisibilityFlags(mLowProfile ? flag : 0, flag); + } + + /** + * Returns whether or not this dream is in low profile mode. Defaults to true. + * + * @see #setLowProfile(boolean) + */ + public boolean isLowProfile() { + return getSystemUiVisibilityFlagValue(View.SYSTEM_UI_FLAG_LOW_PROFILE, mLowProfile); + } + + /** + * Sets View.SYSTEM_UI_FLAG_FULLSCREEN on the content view. + * + * @param fullscreen True to set View.SYSTEM_UI_FLAG_FULLSCREEN + */ + public void setFullscreen(boolean fullscreen) { + mFullscreen = fullscreen; + int flag = View.SYSTEM_UI_FLAG_FULLSCREEN; + applySystemUiVisibilityFlags(mFullscreen ? flag : 0, flag); + } + + /** + * Returns whether or not this dream is in fullscreen mode. Defaults to false. + * + * @see #setFullscreen(boolean) + */ + public boolean isFullscreen() { + return getSystemUiVisibilityFlagValue(View.SYSTEM_UI_FLAG_FULLSCREEN, mFullscreen); + } + + /** + * Marks this dream as keeping the screen bright while dreaming. + * + * @param screenBright True to keep the screen bright while dreaming. + */ + public void setScreenBright(boolean screenBright) { + mScreenBright = screenBright; + int flag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + applyWindowFlags(mScreenBright ? flag : 0, flag); + } + + /** + * Returns whether or not this dream keeps the screen bright while dreaming. Defaults to false, + * allowing the screen to dim if necessary. + * + * @see #setScreenBright(boolean) + */ + public boolean isScreenBright() { + return getWindowFlagValue(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, mScreenBright); + } + + /** + * Called when this Dream is constructed. Place your initialization here. + * + * <p>Subclasses must call through to the superclass implementation.</p> + */ + @Override + public void onCreate() { + if (DEBUG) Slog.v(TAG, "onCreate() on thread " + Thread.currentThread().getId()); + super.onCreate(); + loadSandman(); + } + + /** + * Called when this Dream is started. The window is created and visible at this point. + */ + public void onStart() { + if (DEBUG) Slog.v(TAG, "onStart()"); + // hook for subclasses + } + + /** {@inheritDoc} */ + @Override + public final IBinder onBind(Intent intent) { + if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent); + return new DreamServiceWrapper(); + } + + /** + * Stops the dream, detaches from the window, and wakes up. + * + * <p>Subclasses must call through to the superclass implementation.</p> + * + * <p>After this method is called, the service will be stopped.</p> + */ + public void finish() { + if (DEBUG) Slog.v(TAG, "finish()"); + finishInternal(); + } + + /** {@inheritDoc} */ + @Override + public void onDestroy() { + if (DEBUG) Slog.v(TAG, "onDestroy()"); + super.onDestroy(); + + if (DEBUG) Slog.v(TAG, "Removing window"); + try { + mWindowManager.removeView(mWindow.getDecorView()); + } catch (Throwable t) { + Slog.w(TAG, "Crashed removing window view", t); + } + } + // end public api + + private void loadSandman() { + mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE)); + } + + private final void attach(IBinder windowToken) { + if (DEBUG) Slog.v(TAG, "Attached on thread " + Thread.currentThread().getId()); + + if (mSandman == null) { + Slog.w(TAG, "No dream manager found, super.onCreate may not have been called"); + loadSandman(); + } + mWindowToken = windowToken; + mWindow = PolicyManager.makeNewWindow(this); + mWindow.setCallback(this); + mWindow.requestFeature(Window.FEATURE_NO_TITLE); + mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000)); + + if (DEBUG) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s", + windowToken, WindowManager.LayoutParams.TYPE_DREAM)); + + WindowManager.LayoutParams lp = mWindow.getAttributes(); + lp.type = WindowManager.LayoutParams.TYPE_DREAM; + lp.token = windowToken; + lp.windowAnimations = com.android.internal.R.style.Animation_Dream; + lp.flags |= ( WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0) + ); + mWindow.setAttributes(lp); + + if (DEBUG) Slog.v(TAG, "Created and attached window: " + mWindow); + + mWindow.setWindowManager(null, windowToken, "dream", true); + mWindowManager = mWindow.getWindowManager(); + + // now make it visible (on the ui thread) + mHandler.post(new Runnable(){ + @Override + public void run() { + if (DEBUG) Slog.v(TAG, "Window added on thread " + Thread.currentThread().getId()); + try { + applySystemUiVisibilityFlags( + (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0) + | (mFullscreen ? View.SYSTEM_UI_FLAG_FULLSCREEN : 0), + View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN); + getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes()); + } catch (Throwable t) { + Slog.w("Crashed adding window view", t); + safelyFinish(); + return; + } + + // start it up + try { + onStart(); + } catch (Throwable t) { + Slog.w("Crashed in onStart()", t); + safelyFinish(); + } + }}); + } + + private void safelyFinish() { + if (DEBUG) Slog.v(TAG, "safelyFinish()"); + try { + finish(); + } catch (Throwable t) { + Slog.w(TAG, "Crashed in safelyFinish()", t); + finishInternal(); + return; + } + + if (!mFinished) { + Slog.w(TAG, "Bad dream, did not call super.finish()"); + finishInternal(); + } + } + + private void finishInternal() { + if (DEBUG) Slog.v(TAG, "finishInternal() mFinished = " + mFinished); + if (mFinished) return; + try { + mFinished = true; + + if (mSandman != null) { + mSandman.finishSelf(mWindowToken); + } else { + Slog.w(TAG, "No dream manager found"); + } + stopSelf(); // if launched via any other means + + } catch (Throwable t) { + Slog.w(TAG, "Crashed in finishInternal()", t); + } + } + + private boolean getWindowFlagValue(int flag, boolean defaultValue) { + return mWindow == null ? defaultValue : (mWindow.getAttributes().flags & flag) != 0; + } + + private void applyWindowFlags(int flags, int mask) { + if (mWindow != null) { + WindowManager.LayoutParams lp = mWindow.getAttributes(); + lp.flags = applyFlags(lp.flags, flags, mask); + mWindow.setAttributes(lp); + mWindowManager.updateViewLayout(mWindow.getDecorView(), lp); + } + } + + private boolean getSystemUiVisibilityFlagValue(int flag, boolean defaultValue) { + View v = mWindow == null ? null : mWindow.getDecorView(); + return v == null ? defaultValue : (v.getSystemUiVisibility() & flag) != 0; + } + + private void applySystemUiVisibilityFlags(int flags, int mask) { + View v = mWindow == null ? null : mWindow.getDecorView(); + if (v != null) { + v.setSystemUiVisibility(applyFlags(v.getSystemUiVisibility(), flags, mask)); + } + } + + private int applyFlags(int oldFlags, int flags, int mask) { + return (oldFlags&~mask) | (flags&mask); + } + + private class DreamServiceWrapper extends IDreamService.Stub { + public void attach(IBinder windowToken) { + DreamService.this.attach(windowToken); + } + } + +} diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 7225013..1c1b390 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -19,12 +19,16 @@ package android.service.dreams; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.content.ComponentName; +import android.os.IBinder; /** @hide */ interface IDreamManager { void dream(); void awaken(); - void setDreamComponent(in ComponentName componentName); - ComponentName getDreamComponent(); + void setDreamComponents(in ComponentName[] componentNames); + ComponentName[] getDreamComponents(); + ComponentName getDefaultDreamComponent(); void testDream(in ComponentName componentName); + boolean isDreaming(); + void finishSelf(in IBinder token); }
\ No newline at end of file diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl index b09ccab..f9c5aaa 100644 --- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl +++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl @@ -24,5 +24,6 @@ import android.service.wallpaper.IWallpaperEngine; */ interface IWallpaperConnection { void attachEngine(IWallpaperEngine engine); + void engineShown(IWallpaperEngine engine); ParcelFileDescriptor setWallpaper(String name); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 3e0942c..86bbc55 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -36,10 +36,10 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.Process; import android.os.RemoteException; import android.util.Log; import android.util.LogPrinter; +import android.view.Display; import android.view.Gravity; import android.view.IWindowSession; import android.view.InputChannel; @@ -50,9 +50,8 @@ import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; -import android.view.ViewRootImpl; import android.view.WindowManager; -import android.view.WindowManagerImpl; +import android.view.WindowManagerGlobal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -98,6 +97,7 @@ public abstract class WallpaperService extends Service { private static final int MSG_WALLPAPER_OFFSETS = 10020; private static final int MSG_WALLPAPER_COMMAND = 10025; private static final int MSG_WINDOW_RESIZED = 10030; + private static final int MSG_WINDOW_MOVED = 10035; private static final int MSG_TOUCH_EVENT = 10040; private Looper mCallbackLooper; @@ -253,13 +253,19 @@ public abstract class WallpaperService extends Service { final BaseIWindow mWindow = new BaseIWindow() { @Override - public void resized(int w, int h, Rect contentInsets, + public void resized(Rect frame, Rect contentInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig) { Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED, reportDraw ? 1 : 0); mCaller.sendMessage(msg); } - + + @Override + public void moved(int newX, int newY) { + Message msg = mCaller.obtainMessageII(MSG_WINDOW_MOVED, newX, newY); + mCaller.sendMessage(msg); + } + @Override public void dispatchAppVisibility(boolean visible) { // We don't do this in preview mode; we'll let the preview @@ -290,7 +296,8 @@ public abstract class WallpaperService extends Service { } } } - + + @Override public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, boolean sync) { synchronized (mLock) { @@ -599,13 +606,13 @@ public abstract class WallpaperService extends Service { if (!mCreated) { mLayout.type = mIWallpaperEngine.mWindowType; - mLayout.gravity = Gravity.LEFT|Gravity.TOP; + mLayout.gravity = Gravity.START|Gravity.TOP; mLayout.setTitle(WallpaperService.this.getClass().getName()); mLayout.windowAnimations = com.android.internal.R.style.Animation_Wallpaper; mInputChannel = new InputChannel(); - if (mSession.add(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, mContentInsets, - mInputChannel) < 0) { + if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, + Display.DEFAULT_DISPLAY, mContentInsets, mInputChannel) < 0) { Log.w(TAG, "Failed to add window while updating wallpaper surface."); return; } @@ -665,8 +672,8 @@ public abstract class WallpaperService extends Service { } } - redrawNeeded |= creating - || (relayoutResult&WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0; + redrawNeeded |= creating || (relayoutResult + & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0; if (forceReport || creating || surfaceCreating || formatChanged || sizeChanged) { @@ -753,7 +760,7 @@ public abstract class WallpaperService extends Service { mWindowToken = wrapper.mWindowToken; mSurfaceHolder.setSizeFromLayout(); mInitializing = true; - mSession = ViewRootImpl.getWindowSession(getMainLooper()); + mSession = WindowManagerGlobal.getWindowSession(getMainLooper()); mWindow.setSession(mSession); @@ -1013,6 +1020,12 @@ public abstract class WallpaperService extends Service { mEngine = engine; mActiveEngines.add(engine); engine.attach(this); + try { + mConnection.engineShown(this); + } catch (RemoteException e) { + Log.w(TAG, "Wallpaper host disappeared", e); + return; + } return; } case DO_DETACH: { @@ -1044,6 +1057,9 @@ public abstract class WallpaperService extends Service { mEngine.updateSurface(true, false, reportDraw); mEngine.doOffsetsChanged(true); } break; + case MSG_WINDOW_MOVED: { + // Do nothing. What does it mean for a Wallpaper to move? + } break; case MSG_TOUCH_EVENT: { boolean skip = false; MotionEvent ev = (MotionEvent)message.obj; diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java index fcadad7..186cb49 100644 --- a/core/java/android/speech/tts/BlockingAudioTrack.java +++ b/core/java/android/speech/tts/BlockingAudioTrack.java @@ -67,12 +67,11 @@ class BlockingAudioTrack { private int mAudioBufferSize; private int mBytesWritten = 0; + // Need to be seen by stop() which can be called from another thread. mAudioTrack will be + // set to null only after waitAndRelease(). + private Object mAudioTrackLock = new Object(); private AudioTrack mAudioTrack; private volatile boolean mStopped; - // Locks the initialization / uninitialization of the audio track. - // This is required because stop() will throw an illegal state exception - // if called before init() or after mAudioTrack.release(). - private final Object mAudioTrackLock = new Object(); BlockingAudioTrack(int streamType, int sampleRate, int audioFormat, int channelCount, @@ -93,12 +92,17 @@ class BlockingAudioTrack { mStopped = false; } - public void init() { + public boolean init() { AudioTrack track = createStreamingAudioTrack(); - synchronized (mAudioTrackLock) { mAudioTrack = track; } + + if (track == null) { + return false; + } else { + return true; + } } public void stop() { @@ -106,20 +110,35 @@ class BlockingAudioTrack { if (mAudioTrack != null) { mAudioTrack.stop(); } + mStopped = true; } - mStopped = true; } public int write(byte[] data) { - if (mAudioTrack == null || mStopped) { + AudioTrack track = null; + synchronized (mAudioTrackLock) { + track = mAudioTrack; + } + + if (track == null || mStopped) { return -1; } - final int bytesWritten = writeToAudioTrack(mAudioTrack, data); + final int bytesWritten = writeToAudioTrack(track, data); + mBytesWritten += bytesWritten; return bytesWritten; } public void waitAndRelease() { + AudioTrack track = null; + synchronized (mAudioTrackLock) { + track = mAudioTrack; + } + if (track == null) { + if (DBG) Log.d(TAG, "Audio track null [duplicate call to waitAndRelease ?]"); + return; + } + // For "small" audio tracks, we have to stop() them to make them mixable, // else the audio subsystem will wait indefinitely for us to fill the buffer // before rendering the track mixable. @@ -129,11 +148,11 @@ class BlockingAudioTrack { if (mBytesWritten < mAudioBufferSize && !mStopped) { if (DBG) { Log.d(TAG, "Stopping audio track to flush audio, state was : " + - mAudioTrack.getPlayState() + ",stopped= " + mStopped); + track.getPlayState() + ",stopped= " + mStopped); } mIsShortUtterance = true; - mAudioTrack.stop(); + track.stop(); } // Block until the audio track is done only if we haven't stopped yet. @@ -145,11 +164,11 @@ class BlockingAudioTrack { // The last call to AudioTrack.write( ) will return only after // all data from the audioTrack has been sent to the mixer, so // it's safe to release at this point. - if (DBG) Log.d(TAG, "Releasing audio track [" + mAudioTrack.hashCode() + "]"); - synchronized (mAudioTrackLock) { - mAudioTrack.release(); + if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]"); + synchronized(mAudioTrackLock) { mAudioTrack = null; } + track.release(); } diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java index 04c3377..3e33e8e 100644 --- a/core/java/android/speech/tts/FileSynthesisCallback.java +++ b/core/java/android/speech/tts/FileSynthesisCallback.java @@ -95,6 +95,22 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { } } + /** + * Checks whether a given file exists, and deletes it if it does. + */ + private boolean maybeCleanupExistingFile(File file) { + if (file.exists()) { + Log.v(TAG, "File " + file + " exists, deleting."); + if (!file.delete()) { + Log.e(TAG, "Failed to delete " + file); + return false; + } + } + + return true; + } + + @Override public int getMaxBufferSize() { return MAX_AUDIO_BUFFER_SIZE; @@ -120,6 +136,11 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { cleanUp(); throw new IllegalArgumentException("FileSynthesisRequest.start() called twice"); } + + if (!maybeCleanupExistingFile(mFileName)) { + return TextToSpeech.ERROR; + } + mSampleRateInHz = sampleRateInHz; mAudioFormat = audioFormat; mChannelCount = channelCount; @@ -166,6 +187,12 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { public int done() { if (DBG) Log.d(TAG, "FileSynthesisRequest.done()"); synchronized (mStateLock) { + if (mDone) { + if (DBG) Log.d(TAG, "Duplicate call to done()"); + // This preserves existing behaviour. Earlier, if done was called twice + // we'd return ERROR because mFile == null and we'd add to logspam. + return TextToSpeech.ERROR; + } if (mStopped) { if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java index d299d70..e853c9e 100644 --- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java +++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java @@ -87,7 +87,10 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { dispatcher.dispatchOnStart(); - mAudioTrack.init(); + if (!mAudioTrack.init()) { + dispatcher.dispatchOnError(); + return; + } try { byte[] buffer = null; @@ -242,4 +245,3 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { } } } - diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 7a174af..5e367cb 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1282,6 +1282,7 @@ public class TextToSpeech { } }; + @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i(TAG, "Connected to " + name); synchronized(mStartLock) { @@ -1305,6 +1306,7 @@ public class TextToSpeech { return mCallback; } + @Override public void onServiceDisconnected(ComponentName name) { synchronized(mStartLock) { mService = null; @@ -1317,24 +1319,33 @@ public class TextToSpeech { public void disconnect() { mContext.unbindService(this); + + synchronized (mStartLock) { + mService = null; + // If this is the active connection, clear it + if (mServiceConnection == this) { + mServiceConnection = null; + } + + } } public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) { - try { - synchronized (mStartLock) { + synchronized (mStartLock) { + try { if (mService == null) { Log.w(TAG, method + " failed: not connected to TTS engine"); return errorResult; } return action.run(mService); + } catch (RemoteException ex) { + Log.e(TAG, method + " failed", ex); + if (reconnect) { + disconnect(); + initTts(); + } + return errorResult; } - } catch (RemoteException ex) { - Log.e(TAG, method + " failed", ex); - if (reconnect) { - disconnect(); - initTts(); - } - return errorResult; } } } diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 4c1a0af..d124e68 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -549,7 +549,7 @@ public abstract class TextToSpeechService extends Service { @Override public boolean isValid() { if (mText == null) { - Log.wtf(TAG, "Got null text"); + Log.e(TAG, "null synthesis text"); return false; } if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) { @@ -641,14 +641,6 @@ public abstract class TextToSpeechService extends Service { } @Override - public boolean isValid() { - if (!super.isValid()) { - return false; - } - return checkFile(mFile); - } - - @Override protected AbstractSynthesisCallback createSynthesisCallback() { return new FileSynthesisCallback(mFile); } @@ -664,33 +656,6 @@ public abstract class TextToSpeechService extends Service { } return status; } - - /** - * Checks that the given file can be used for synthesis output. - */ - private boolean checkFile(File file) { - try { - if (file.exists()) { - Log.v(TAG, "File " + file + " exists, deleting."); - if (!file.delete()) { - Log.e(TAG, "Failed to delete " + file); - return false; - } - } - if (!file.createNewFile()) { - Log.e(TAG, "Can't create file " + file); - return false; - } - if (!file.delete()) { - Log.e(TAG, "Failed to delete " + file); - return false; - } - return true; - } catch (IOException e) { - Log.e(TAG, "Can't use " + file + " due to exception " + e); - return false; - } - } } private class AudioSpeechItem extends SpeechItem { diff --git a/core/java/android/test/AndroidTestCase.java b/core/java/android/test/AndroidTestCase.java index 1015506..0c8cbe6 100644 --- a/core/java/android/test/AndroidTestCase.java +++ b/core/java/android/test/AndroidTestCase.java @@ -135,7 +135,8 @@ public class AndroidTestCase extends TestCase { fail("expected SecurityException requiring " + permission); } catch (SecurityException expected) { assertNotNull("security exception's error message.", expected.getMessage()); - assertTrue("error message should contain " + permission + ".", + assertTrue("error message should contain \"" + permission + "\". Got: \"" + + expected.getMessage() + "\".", expected.getMessage().contains(permission)); } } diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java index 2b73763..b4622e0 100644 --- a/core/java/android/text/Spanned.java +++ b/core/java/android/text/Spanned.java @@ -30,8 +30,19 @@ extends CharSequence * of spans. * * MARK and POINT are conceptually located <i>between</i> two adjacent characters. - * A MARK is "attached" to the character on the left hand side, while a POINT - * tends to stick to the character on the right hand side. + * A MARK is "attached" to the character before, while a POINT will stick to the character + * after. The insertion cursor is conceptually located between the MARK and the POINT. + * + * As a result, inserting a new character between a MARK and a POINT will leave the MARK + * unchanged, while the POINT will be shifted, now located after the inserted character and + * still glued to the same character after it. + * + * Depending on whether the insertion happens at the beginning or the end of a span, the span + * will hence be expanded to <i>include</i> the new character (when the span is using a MARK at + * its beginning or a POINT at its end) or it will be <i>excluded</i>. + * + * Note that <i>before</i> and <i>after</i> here refer to offsets in the String, which are + * independent from the visual representation of the text (left-to-right or right-to-left). */ public static final int SPAN_POINT_MARK_MASK = 0x33; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 6a619af..9051285 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -150,8 +150,8 @@ public class StaticLayout extends Layout { mColumns = COLUMNS_ELLIPSIZE; mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)]; - mLineDirections = new Directions[ - ArrayUtils.idealIntArraySize(2 * mColumns)]; + mLineDirections = new Directions[ArrayUtils.idealIntArraySize(2 * mColumns)]; + // FIXME This is never recycled mMeasured = MeasuredText.obtain(); } @@ -340,7 +340,9 @@ public class StaticLayout extends Layout { w += widths[j - paraStart]; } - if (w <= width) { + boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB; + + if (w <= width || isSpaceOrTab) { fitWidth = w; fit = j + 1; @@ -353,30 +355,17 @@ public class StaticLayout extends Layout { if (fmBottom > fitBottom) fitBottom = fmBottom; - /* - * From the Unicode Line Breaking Algorithm: - * (at least approximately) - * - * .,:; are class IS: breakpoints - * except when adjacent to digits - * / is class SY: a breakpoint - * except when followed by a digit. - * - is class HY: a breakpoint - * except when followed by a digit. - * - * Ideographs are class ID: breakpoints when adjacent, - * except for NS (non-starters), which can be broken - * after but not before. - */ - if (c == CHAR_SPACE || c == CHAR_TAB || - ((c == CHAR_DOT || c == CHAR_COMMA || - c == CHAR_COLON || c == CHAR_SEMICOLON) && - (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) && - (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) || - ((c == CHAR_SLASH || c == CHAR_HYPHEN) && - (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) || - (c >= CHAR_FIRST_CJK && isIdeographic(c, true) && - j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) { + // From the Unicode Line Breaking Algorithm (at least approximately) + boolean isLineBreak = isSpaceOrTab || + // / is class SY and - is class HY, except when followed by a digit + ((c == CHAR_SLASH || c == CHAR_HYPHEN) && + (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) || + // Ideographs are class ID: breakpoints when adjacent, except for NS + // (non-starters), which can be broken after but not before + (c >= CHAR_FIRST_CJK && isIdeographic(c, true) && + j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false)); + + if (isLineBreak) { okWidth = w; ok = j + 1; @@ -396,13 +385,6 @@ public class StaticLayout extends Layout { float currentTextWidth; if (ok != here) { - // If it is a space that makes the length exceed width, cut here - if (c == CHAR_SPACE) ok = j + 1; - - while (ok < spanEnd && chs[ok - paraStart] == CHAR_SPACE) { - ok++; - } - endPos = ok; above = okAscent; below = okDescent; @@ -450,10 +432,10 @@ public class StaticLayout extends Layout { spanEnd = here; break; } - } - // FIXME This should be moved in the above else block which changes mLineCount - if (mLineCount >= mMaximumVisibleLineCount) { - break; + + if (mLineCount >= mMaximumVisibleLineCount) { + break; + } } } } @@ -972,10 +954,6 @@ public class StaticLayout extends Layout { private static final char CHAR_NEW_LINE = '\n'; private static final char CHAR_TAB = '\t'; private static final char CHAR_SPACE = ' '; - private static final char CHAR_DOT = '.'; - private static final char CHAR_COMMA = ','; - private static final char CHAR_COLON = ':'; - private static final char CHAR_SEMICOLON = ';'; private static final char CHAR_SLASH = '/'; private static final char CHAR_HYPHEN = '-'; diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java index be2840b..df8c4c6 100644 --- a/core/java/android/text/TextDirectionHeuristics.java +++ b/core/java/android/text/TextDirectionHeuristics.java @@ -17,11 +17,11 @@ package android.text; -import android.util.LocaleUtil; import android.view.View; /** * Some objects that implement TextDirectionHeuristic. + * * @hide */ public class TextDirectionHeuristics { @@ -241,7 +241,7 @@ public class TextDirectionHeuristics { @Override protected boolean defaultIsRtl() { - final int dir = LocaleUtil.getLayoutDirectionFromLocale(java.util.Locale.getDefault()); + final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault()); return (dir == View.LAYOUT_DIRECTION_RTL); } diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 270624c..1508d10 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -27,6 +27,7 @@ import android.text.style.CharacterStyle; import android.text.style.EasyEditSpan; import android.text.style.ForegroundColorSpan; import android.text.style.LeadingMarginSpan; +import android.text.style.LocaleSpan; import android.text.style.MetricAffectingSpan; import android.text.style.QuoteSpan; import android.text.style.RelativeSizeSpan; @@ -45,11 +46,14 @@ import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.util.Printer; +import android.view.View; import com.android.internal.R; import com.android.internal.util.ArrayUtils; +import libcore.icu.ICU; import java.lang.reflect.Array; import java.util.Iterator; +import java.util.Locale; import java.util.regex.Pattern; public class TextUtils { @@ -587,6 +591,8 @@ public class TextUtils { public static final int SUGGESTION_RANGE_SPAN = 21; /** @hide */ public static final int EASY_EDIT_SPAN = 22; + /** @hide */ + public static final int LOCALE_SPAN = 23; /** * Flatten a CharSequence and whatever styles can be copied across processes @@ -754,6 +760,10 @@ public class TextUtils { readSpan(p, sp, new EasyEditSpan()); break; + case LOCALE_SPAN: + readSpan(p, sp, new LocaleSpan(p)); + break; + default: throw new RuntimeException("bogus span encoding " + kind); } @@ -1042,9 +1052,14 @@ public class TextUtils { float avail, TruncateAt where, boolean preserveLength, EllipsizeCallback callback) { + + final String ellipsis = (where == TruncateAt.END_SMALL) ? + Resources.getSystem().getString(R.string.ellipsis_two_dots) : + Resources.getSystem().getString(R.string.ellipsis); + return ellipsize(text, paint, avail, where, preserveLength, callback, TextDirectionHeuristics.FIRSTSTRONG_LTR, - (where == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL); + ellipsis); } /** @@ -1694,15 +1709,64 @@ public class TextUtils { return (int) (range & 0x00000000FFFFFFFFL); } + /** + * Return the layout direction for a given Locale + * + * @param locale the Locale for which we want the layout direction. Can be null. + * @return the layout direction. This may be one of: + * {@link android.view.View#LAYOUT_DIRECTION_LTR} or + * {@link android.view.View#LAYOUT_DIRECTION_RTL}. + * + * Be careful: this code will need to be updated when vertical scripts will be supported + */ + public static int getLayoutDirectionFromLocale(Locale locale) { + if (locale != null && !locale.equals(Locale.ROOT)) { + final String scriptSubtag = ICU.getScript(ICU.addLikelySubtags(locale.toString())); + if (scriptSubtag == null) return getLayoutDirectionFromFirstChar(locale); + + if (scriptSubtag.equalsIgnoreCase(ARAB_SCRIPT_SUBTAG) || + scriptSubtag.equalsIgnoreCase(HEBR_SCRIPT_SUBTAG)) { + return View.LAYOUT_DIRECTION_RTL; + } + } + + return View.LAYOUT_DIRECTION_LTR; + } + + /** + * Fallback algorithm to detect the locale direction. Rely on the fist char of the + * localized locale name. This will not work if the localized locale name is in English + * (this is the case for ICU 4.4 and "Urdu" script) + * + * @param locale + * @return the layout direction. This may be one of: + * {@link View#LAYOUT_DIRECTION_LTR} or + * {@link View#LAYOUT_DIRECTION_RTL}. + * + * Be careful: this code will need to be updated when vertical scripts will be supported + * + * @hide + */ + private static int getLayoutDirectionFromFirstChar(Locale locale) { + switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) { + case Character.DIRECTIONALITY_RIGHT_TO_LEFT: + case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: + return View.LAYOUT_DIRECTION_RTL; + + case Character.DIRECTIONALITY_LEFT_TO_RIGHT: + default: + return View.LAYOUT_DIRECTION_LTR; + } + } + private static Object sLock = new Object(); + private static char[] sTemp = null; private static String[] EMPTY_STRING_ARRAY = new String[]{}; private static final char ZWNBS_CHAR = '\uFEFF'; - private static final String ELLIPSIS_NORMAL = Resources.getSystem().getString( - R.string.ellipsis); - private static final String ELLIPSIS_TWO_DOTS = Resources.getSystem().getString( - R.string.ellipsis_two_dots); + private static String ARAB_SCRIPT_SUBTAG = "Arab"; + private static String HEBR_SCRIPT_SUBTAG = "Hebr"; } diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index 524f941..c36273e 100644 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -35,10 +35,18 @@ import java.text.SimpleDateFormat; Utility class for producing strings with formatted date/time. <p> - This class takes as inputs a format string and a representation of a date/time. - The format string controls how the output is generated. + Most callers should avoid supplying their own format strings to this + class' {@code format} methods and rely on the correctly localized ones + supplied by the system. This class' factory methods return + appropriately-localized {@link java.text.DateFormat} instances, suitable + for both formatting and parsing dates. For the canonical documentation + of format strings, see {@link java.text.SimpleDateFormat}. </p> <p> + The format methods in this class takes as inputs a format string and a representation of a date/time. + The format string controls how the output is generated. + This class only supports a subset of the full Unicode specification. + Use {@link java.text.SimpleDateFormat} if you need more. Formatting characters may be repeated in order to get more detailed representations of that field. For instance, the format character 'M' is used to represent the month. Depending on how many times that character is repeated @@ -152,7 +160,8 @@ public class DateFormat { public static final char MINUTE = 'm'; /** - This designator indicates the month of the year + This designator indicates the month of the year. See also + {@link #STANDALONE_MONTH}. Examples for September: M -> 9 @@ -163,6 +172,14 @@ public class DateFormat { public static final char MONTH = 'M'; /** + This designator indicates the standalone month of the year, + necessary in some format strings in some languages. For + example, Russian distinguishes between the "June" in + "June" and that in "June 2010". + */ + public static final char STANDALONE_MONTH = 'L'; + + /** This designator indicates the seconds of the minute. Examples for 7 seconds past the minute: @@ -374,7 +391,7 @@ public class DateFormat { index++; } - if (!foundMonth && (c == MONTH)) { + if (!foundMonth && (c == MONTH || c == STANDALONE_MONTH)) { foundMonth = true; order[index] = MONTH; index++; @@ -494,9 +511,10 @@ public class DateFormat { break; case MONTH: - replacement = getMonthString(inDate, count); + case STANDALONE_MONTH: + replacement = getMonthString(inDate, count, c); break; - + case SECONDS: replacement = zeroPad(inDate.get(Calendar.SECOND), count); break; @@ -527,14 +545,19 @@ public class DateFormat { return s.toString(); } - private static final String getMonthString(Calendar inDate, int count) { + private static final String getMonthString(Calendar inDate, int count, int kind) { + boolean standalone = (kind == STANDALONE_MONTH); int month = inDate.get(Calendar.MONTH); - if (count >= 4) - return DateUtils.getMonthString(month, DateUtils.LENGTH_LONG); - else if (count == 3) - return DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM); - else { + if (count >= 4) { + return standalone + ? DateUtils.getStandaloneMonthString(month, DateUtils.LENGTH_LONG) + : DateUtils.getMonthString(month, DateUtils.LENGTH_LONG); + } else if (count == 3) { + return standalone + ? DateUtils.getStandaloneMonthString(month, DateUtils.LENGTH_MEDIUM) + : DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM); + } else { // Calendar.JANUARY == 0, so add 1 to month. return zeroPad(month+1, count); } @@ -574,7 +597,8 @@ public class DateFormat { private static final String getYearString(Calendar inDate, int count) { int year = inDate.get(Calendar.YEAR); - return (count <= 2) ? zeroPad(year % 100, 2) : String.valueOf(year); + return (count <= 2) ? zeroPad(year % 100, 2) + : String.format(Locale.getDefault(), "%d", year); } private static final int appendQuotedText(SpannableStringBuilder s, int i, int len) { @@ -615,17 +639,6 @@ public class DateFormat { } private static final String zeroPad(int inValue, int inMinDigits) { - String val = String.valueOf(inValue); - - if (val.length() < inMinDigits) { - char[] buf = new char[inMinDigits]; - - for (int i = 0; i < inMinDigits; i++) - buf[i] = '0'; - - val.getChars(0, val.length(), buf, inMinDigits - val.length()); - val = new String(buf); - } - return val; + return String.format(Locale.getDefault(), "%0" + inMinDigits + "d", inValue); } } diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 2e962a0..1060bd8 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -29,6 +29,8 @@ import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; +import libcore.icu.LocaleData; + /** * This class contains various date-related utilities for creating text for things like * elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc. @@ -36,102 +38,6 @@ import java.util.TimeZone; public class DateUtils { private static final Object sLock = new Object(); - private static final int[] sDaysLong = new int[] { - com.android.internal.R.string.day_of_week_long_sunday, - com.android.internal.R.string.day_of_week_long_monday, - com.android.internal.R.string.day_of_week_long_tuesday, - com.android.internal.R.string.day_of_week_long_wednesday, - com.android.internal.R.string.day_of_week_long_thursday, - com.android.internal.R.string.day_of_week_long_friday, - com.android.internal.R.string.day_of_week_long_saturday, - }; - private static final int[] sDaysMedium = new int[] { - com.android.internal.R.string.day_of_week_medium_sunday, - com.android.internal.R.string.day_of_week_medium_monday, - com.android.internal.R.string.day_of_week_medium_tuesday, - com.android.internal.R.string.day_of_week_medium_wednesday, - com.android.internal.R.string.day_of_week_medium_thursday, - com.android.internal.R.string.day_of_week_medium_friday, - com.android.internal.R.string.day_of_week_medium_saturday, - }; - private static final int[] sDaysShort = new int[] { - com.android.internal.R.string.day_of_week_short_sunday, - com.android.internal.R.string.day_of_week_short_monday, - com.android.internal.R.string.day_of_week_short_tuesday, - com.android.internal.R.string.day_of_week_short_wednesday, - com.android.internal.R.string.day_of_week_short_thursday, - com.android.internal.R.string.day_of_week_short_friday, - com.android.internal.R.string.day_of_week_short_saturday, - }; - private static final int[] sDaysShortest = new int[] { - com.android.internal.R.string.day_of_week_shortest_sunday, - com.android.internal.R.string.day_of_week_shortest_monday, - com.android.internal.R.string.day_of_week_shortest_tuesday, - com.android.internal.R.string.day_of_week_shortest_wednesday, - com.android.internal.R.string.day_of_week_shortest_thursday, - com.android.internal.R.string.day_of_week_shortest_friday, - com.android.internal.R.string.day_of_week_shortest_saturday, - }; - private static final int[] sMonthsStandaloneLong = new int [] { - com.android.internal.R.string.month_long_standalone_january, - com.android.internal.R.string.month_long_standalone_february, - com.android.internal.R.string.month_long_standalone_march, - com.android.internal.R.string.month_long_standalone_april, - com.android.internal.R.string.month_long_standalone_may, - com.android.internal.R.string.month_long_standalone_june, - com.android.internal.R.string.month_long_standalone_july, - com.android.internal.R.string.month_long_standalone_august, - com.android.internal.R.string.month_long_standalone_september, - com.android.internal.R.string.month_long_standalone_october, - com.android.internal.R.string.month_long_standalone_november, - com.android.internal.R.string.month_long_standalone_december, - }; - private static final int[] sMonthsLong = new int [] { - com.android.internal.R.string.month_long_january, - com.android.internal.R.string.month_long_february, - com.android.internal.R.string.month_long_march, - com.android.internal.R.string.month_long_april, - com.android.internal.R.string.month_long_may, - com.android.internal.R.string.month_long_june, - com.android.internal.R.string.month_long_july, - com.android.internal.R.string.month_long_august, - com.android.internal.R.string.month_long_september, - com.android.internal.R.string.month_long_october, - com.android.internal.R.string.month_long_november, - com.android.internal.R.string.month_long_december, - }; - private static final int[] sMonthsMedium = new int [] { - com.android.internal.R.string.month_medium_january, - com.android.internal.R.string.month_medium_february, - com.android.internal.R.string.month_medium_march, - com.android.internal.R.string.month_medium_april, - com.android.internal.R.string.month_medium_may, - com.android.internal.R.string.month_medium_june, - com.android.internal.R.string.month_medium_july, - com.android.internal.R.string.month_medium_august, - com.android.internal.R.string.month_medium_september, - com.android.internal.R.string.month_medium_october, - com.android.internal.R.string.month_medium_november, - com.android.internal.R.string.month_medium_december, - }; - private static final int[] sMonthsShortest = new int [] { - com.android.internal.R.string.month_shortest_january, - com.android.internal.R.string.month_shortest_february, - com.android.internal.R.string.month_shortest_march, - com.android.internal.R.string.month_shortest_april, - com.android.internal.R.string.month_shortest_may, - com.android.internal.R.string.month_shortest_june, - com.android.internal.R.string.month_shortest_july, - com.android.internal.R.string.month_shortest_august, - com.android.internal.R.string.month_shortest_september, - com.android.internal.R.string.month_shortest_october, - com.android.internal.R.string.month_shortest_november, - com.android.internal.R.string.month_shortest_december, - }; - private static final int[] sAmPm = new int[] { - com.android.internal.R.string.am, - com.android.internal.R.string.pm, - }; private static Configuration sLastConfig; private static java.text.DateFormat sStatusTimeFormat; private static String sElapsedFormatMMSS; @@ -139,7 +45,6 @@ public class DateUtils private static final String FAST_FORMAT_HMMSS = "%1$d:%2$02d:%3$02d"; private static final String FAST_FORMAT_MMSS = "%1$02d:%2$02d"; - private static final char TIME_PADDING = '0'; private static final char TIME_SEPARATOR = ':'; @@ -336,18 +241,17 @@ public class DateUtils */ @Deprecated public static String getDayOfWeekString(int dayOfWeek, int abbrev) { - int[] list; + LocaleData d = LocaleData.get(Locale.getDefault()); + String[] names; switch (abbrev) { - case LENGTH_LONG: list = sDaysLong; break; - case LENGTH_MEDIUM: list = sDaysMedium; break; - case LENGTH_SHORT: list = sDaysShort; break; - case LENGTH_SHORTER: list = sDaysShort; break; - case LENGTH_SHORTEST: list = sDaysShortest; break; - default: list = sDaysMedium; break; + case LENGTH_LONG: names = d.longWeekdayNames; break; + case LENGTH_MEDIUM: names = d.shortWeekdayNames; break; + case LENGTH_SHORT: names = d.shortWeekdayNames; break; // TODO + case LENGTH_SHORTER: names = d.shortWeekdayNames; break; // TODO + case LENGTH_SHORTEST: names = d.tinyWeekdayNames; break; + default: names = d.shortWeekdayNames; break; } - - Resources r = Resources.getSystem(); - return r.getString(list[dayOfWeek - Calendar.SUNDAY]); + return names[dayOfWeek]; } /** @@ -359,8 +263,7 @@ public class DateUtils */ @Deprecated public static String getAMPMString(int ampm) { - Resources r = Resources.getSystem(); - return r.getString(sAmPm[ampm - Calendar.AM]); + return LocaleData.get(Locale.getDefault()).amPm[ampm - Calendar.AM]; } /** @@ -376,22 +279,21 @@ public class DateUtils */ @Deprecated public static String getMonthString(int month, int abbrev) { - // Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER. + // Note that here we use d.shortMonthNames for MEDIUM, SHORT and SHORTER. // This is a shortcut to not spam the translators with too many variations // of the same string. If we find that in a language the distinction // is necessary, we can can add more without changing this API. - int[] list; + LocaleData d = LocaleData.get(Locale.getDefault()); + String[] names; switch (abbrev) { - case LENGTH_LONG: list = sMonthsLong; break; - case LENGTH_MEDIUM: list = sMonthsMedium; break; - case LENGTH_SHORT: list = sMonthsMedium; break; - case LENGTH_SHORTER: list = sMonthsMedium; break; - case LENGTH_SHORTEST: list = sMonthsShortest; break; - default: list = sMonthsMedium; break; + case LENGTH_LONG: names = d.longMonthNames; break; + case LENGTH_MEDIUM: names = d.shortMonthNames; break; + case LENGTH_SHORT: names = d.shortMonthNames; break; + case LENGTH_SHORTER: names = d.shortMonthNames; break; + case LENGTH_SHORTEST: names = d.tinyMonthNames; break; + default: names = d.shortMonthNames; break; } - - Resources r = Resources.getSystem(); - return r.getString(list[month - Calendar.JANUARY]); + return names[month]; } /** @@ -411,23 +313,22 @@ public class DateUtils */ @Deprecated public static String getStandaloneMonthString(int month, int abbrev) { - // Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER. + // Note that here we use d.shortMonthNames for MEDIUM, SHORT and SHORTER. // This is a shortcut to not spam the translators with too many variations // of the same string. If we find that in a language the distinction // is necessary, we can can add more without changing this API. - int[] list; + LocaleData d = LocaleData.get(Locale.getDefault()); + String[] names; switch (abbrev) { - case LENGTH_LONG: list = sMonthsStandaloneLong; + case LENGTH_LONG: names = d.longStandAloneMonthNames; break; - case LENGTH_MEDIUM: list = sMonthsMedium; break; - case LENGTH_SHORT: list = sMonthsMedium; break; - case LENGTH_SHORTER: list = sMonthsMedium; break; - case LENGTH_SHORTEST: list = sMonthsShortest; break; - default: list = sMonthsMedium; break; + case LENGTH_MEDIUM: names = d.shortMonthNames; break; + case LENGTH_SHORT: names = d.shortMonthNames; break; + case LENGTH_SHORTER: names = d.shortMonthNames; break; + case LENGTH_SHORTEST: names = d.tinyMonthNames; break; + default: names = d.shortMonthNames; break; } - - Resources r = Resources.getSystem(); - return r.getString(list[month - Calendar.JANUARY]); + return names[month]; } /** @@ -650,14 +551,19 @@ public class DateUtils int days = Math.abs(currentDay - startDay); boolean past = (today > day); + // TODO: some locales name other days too, such as de_DE's "Vorgestern" (today - 2). + Locale locale = r.getConfiguration().locale; + if (locale == null) { + locale = Locale.getDefault(); + } if (days == 1) { if (past) { - return r.getString(com.android.internal.R.string.yesterday); + return LocaleData.get(locale).yesterday; } else { - return r.getString(com.android.internal.R.string.tomorrow); + return LocaleData.get(locale).tomorrow; } } else if (days == 0) { - return r.getString(com.android.internal.R.string.today); + return LocaleData.get(locale).today; } int resId; @@ -741,33 +647,36 @@ public class DateUtils } } + private static void append(StringBuilder sb, long value, boolean pad, char zeroDigit) { + if (value < 10) { + if (pad) { + sb.append(zeroDigit); + } + } else { + sb.append((char) (zeroDigit + (value / 10))); + } + sb.append((char) (zeroDigit + (value % 10))); + } + /** - * Fast formatting of h:mm:ss + * Fast formatting of h:mm:ss. */ private static String formatElapsedTime(StringBuilder recycle, String format, long hours, long minutes, long seconds) { if (FAST_FORMAT_HMMSS.equals(format)) { + char zeroDigit = LocaleData.get(Locale.getDefault()).zeroDigit; + StringBuilder sb = recycle; if (sb == null) { sb = new StringBuilder(8); } else { sb.setLength(0); } - sb.append(hours); + append(sb, hours, false, zeroDigit); sb.append(TIME_SEPARATOR); - if (minutes < 10) { - sb.append(TIME_PADDING); - } else { - sb.append(toDigitChar(minutes / 10)); - } - sb.append(toDigitChar(minutes % 10)); + append(sb, minutes, true, zeroDigit); sb.append(TIME_SEPARATOR); - if (seconds < 10) { - sb.append(TIME_PADDING); - } else { - sb.append(toDigitChar(seconds / 10)); - } - sb.append(toDigitChar(seconds % 10)); + append(sb, seconds, true, zeroDigit); return sb.toString(); } else { return String.format(format, hours, minutes, seconds); @@ -775,40 +684,28 @@ public class DateUtils } /** - * Fast formatting of m:ss + * Fast formatting of mm:ss. */ private static String formatElapsedTime(StringBuilder recycle, String format, long minutes, long seconds) { if (FAST_FORMAT_MMSS.equals(format)) { + char zeroDigit = LocaleData.get(Locale.getDefault()).zeroDigit; + StringBuilder sb = recycle; if (sb == null) { sb = new StringBuilder(8); } else { sb.setLength(0); } - if (minutes < 10) { - sb.append(TIME_PADDING); - } else { - sb.append(toDigitChar(minutes / 10)); - } - sb.append(toDigitChar(minutes % 10)); + append(sb, minutes, false, zeroDigit); sb.append(TIME_SEPARATOR); - if (seconds < 10) { - sb.append(TIME_PADDING); - } else { - sb.append(toDigitChar(seconds / 10)); - } - sb.append(toDigitChar(seconds % 10)); + append(sb, seconds, true, zeroDigit); return sb.toString(); } else { return String.format(format, minutes, seconds); } } - private static char toDigitChar(long digit) { - return (char) (digit + '0'); - } - /** * Format a date / time such that if the then is on the same day as now, it shows * just the time and if it's a different day, it shows just the date. @@ -1480,6 +1377,14 @@ public class DateUtils String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT); String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT); + String startStandaloneMonthString = startMonthString; + String endStandaloneMonthString = endMonthString; + // We need standalone months for these strings in Persian (fa): http://b/6811327 + if (!numericDate && !abbrevMonth && Locale.getDefault().getLanguage().equals("fa")) { + startStandaloneMonthString = startDate.format("%-B"); + endStandaloneMonthString = endDate.format("%-B"); + } + if (startMonthNum != endMonthNum) { // Same year, different month. // Example: "October 28 - November 3" @@ -1500,7 +1405,8 @@ public class DateUtils startWeekDayString, startMonthString, startMonthDayString, startYearString, startTimeString, endWeekDayString, endMonthString, endMonthDayString, - endYearString, endTimeString); + endYearString, endTimeString, + startStandaloneMonthString, endStandaloneMonthString); } if (startDay != endDay) { @@ -1519,7 +1425,8 @@ public class DateUtils startWeekDayString, startMonthString, startMonthDayString, startYearString, startTimeString, endWeekDayString, endMonthString, endMonthDayString, - endYearString, endTimeString); + endYearString, endTimeString, + startStandaloneMonthString, endStandaloneMonthString); } // Same start and end day diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index e9b0d32..5ef86b1 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -21,6 +21,8 @@ import android.content.res.Resources; import java.util.Locale; import java.util.TimeZone; +import libcore.icu.LocaleData; + /** * An alternative to the {@link java.util.Calendar} and * {@link java.util.GregorianCalendar} classes. An instance of the Time class represents @@ -149,6 +151,9 @@ public class Time { private static String sDateTimeFormat; private static String sAm; private static String sPm; + private static char sZeroDigit; + + // Referenced by native code. private static String sDateCommand = "%a %b %e %H:%M:%S %Z %Y"; /** @@ -317,83 +322,52 @@ public class Time { Locale locale = Locale.getDefault(); if (sLocale == null || locale == null || !(locale.equals(sLocale))) { - Resources r = Resources.getSystem(); + LocaleData localeData = LocaleData.get(locale); + + sAm = localeData.amPm[0]; + sPm = localeData.amPm[1]; + sZeroDigit = localeData.zeroDigit; - sShortMonths = new String[] { - r.getString(com.android.internal.R.string.month_medium_january), - r.getString(com.android.internal.R.string.month_medium_february), - r.getString(com.android.internal.R.string.month_medium_march), - r.getString(com.android.internal.R.string.month_medium_april), - r.getString(com.android.internal.R.string.month_medium_may), - r.getString(com.android.internal.R.string.month_medium_june), - r.getString(com.android.internal.R.string.month_medium_july), - r.getString(com.android.internal.R.string.month_medium_august), - r.getString(com.android.internal.R.string.month_medium_september), - r.getString(com.android.internal.R.string.month_medium_october), - r.getString(com.android.internal.R.string.month_medium_november), - r.getString(com.android.internal.R.string.month_medium_december), - }; - sLongMonths = new String[] { - r.getString(com.android.internal.R.string.month_long_january), - r.getString(com.android.internal.R.string.month_long_february), - r.getString(com.android.internal.R.string.month_long_march), - r.getString(com.android.internal.R.string.month_long_april), - r.getString(com.android.internal.R.string.month_long_may), - r.getString(com.android.internal.R.string.month_long_june), - r.getString(com.android.internal.R.string.month_long_july), - r.getString(com.android.internal.R.string.month_long_august), - r.getString(com.android.internal.R.string.month_long_september), - r.getString(com.android.internal.R.string.month_long_october), - r.getString(com.android.internal.R.string.month_long_november), - r.getString(com.android.internal.R.string.month_long_december), - }; - sLongStandaloneMonths = new String[] { - r.getString(com.android.internal.R.string.month_long_standalone_january), - r.getString(com.android.internal.R.string.month_long_standalone_february), - r.getString(com.android.internal.R.string.month_long_standalone_march), - r.getString(com.android.internal.R.string.month_long_standalone_april), - r.getString(com.android.internal.R.string.month_long_standalone_may), - r.getString(com.android.internal.R.string.month_long_standalone_june), - r.getString(com.android.internal.R.string.month_long_standalone_july), - r.getString(com.android.internal.R.string.month_long_standalone_august), - r.getString(com.android.internal.R.string.month_long_standalone_september), - r.getString(com.android.internal.R.string.month_long_standalone_october), - r.getString(com.android.internal.R.string.month_long_standalone_november), - r.getString(com.android.internal.R.string.month_long_standalone_december), - }; - sShortWeekdays = new String[] { - r.getString(com.android.internal.R.string.day_of_week_medium_sunday), - r.getString(com.android.internal.R.string.day_of_week_medium_monday), - r.getString(com.android.internal.R.string.day_of_week_medium_tuesday), - r.getString(com.android.internal.R.string.day_of_week_medium_wednesday), - r.getString(com.android.internal.R.string.day_of_week_medium_thursday), - r.getString(com.android.internal.R.string.day_of_week_medium_friday), - r.getString(com.android.internal.R.string.day_of_week_medium_saturday), - }; - sLongWeekdays = new String[] { - r.getString(com.android.internal.R.string.day_of_week_long_sunday), - r.getString(com.android.internal.R.string.day_of_week_long_monday), - r.getString(com.android.internal.R.string.day_of_week_long_tuesday), - r.getString(com.android.internal.R.string.day_of_week_long_wednesday), - r.getString(com.android.internal.R.string.day_of_week_long_thursday), - r.getString(com.android.internal.R.string.day_of_week_long_friday), - r.getString(com.android.internal.R.string.day_of_week_long_saturday), - }; + sShortMonths = localeData.shortMonthNames; + sLongMonths = localeData.longMonthNames; + sLongStandaloneMonths = localeData.longStandAloneMonthNames; + sShortWeekdays = localeData.shortWeekdayNames; + sLongWeekdays = localeData.longWeekdayNames; + + Resources r = Resources.getSystem(); sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day); sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year); sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time); - sAm = r.getString(com.android.internal.R.string.am); - sPm = r.getString(com.android.internal.R.string.pm); sLocale = locale; } - return format1(format); + String result = format1(format); + if (sZeroDigit != '0') { + result = localizeDigits(result); + } + return result; } } native private String format1(String format); + // TODO: unify this with java.util.Formatter's copy. + private String localizeDigits(String s) { + int length = s.length(); + int offsetToLocalizedDigits = sZeroDigit - '0'; + StringBuilder result = new StringBuilder(length); + for (int i = 0; i < length; ++i) { + char ch = s.charAt(i); + if (ch >= '0' && ch <= '9') { + ch += offsetToLocalizedDigits; + } + result.append(ch); + } + return result.toString(); + } + + /** * Return the current time in YYYYMMDDTHHMMSS<tz> format */ @@ -437,6 +411,9 @@ public class Time { * @throws android.util.TimeFormatException if s cannot be parsed. */ public boolean parse(String s) { + if (s == null) { + throw new NullPointerException("time string is null"); + } if (nativeParse(s)) { timezone = TIMEZONE_UTC; return true; @@ -720,7 +697,7 @@ public class Time { int minutes = (offset % 3600) / 60; int hours = offset / 3600; - return String.format("%s%s%02d:%02d", base, sign, hours, minutes); + return String.format(Locale.US, "%s%s%02d:%02d", base, sign, hours, minutes); } } diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java new file mode 100644 index 0000000..a12c42f --- /dev/null +++ b/core/java/android/text/style/LocaleSpan.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text.style; + +import android.graphics.Paint; +import android.os.Parcel; +import android.text.ParcelableSpan; +import android.text.TextPaint; +import android.text.TextUtils; +import java.util.Locale; + +/** + * Changes the {@link Locale} of the text to which the span is attached. + */ +public class LocaleSpan extends MetricAffectingSpan implements ParcelableSpan { + private final Locale mLocale; + + /** + * Creates a LocaleSpan. + * @param locale The {@link Locale} of the text to which the span is + * attached. + */ + public LocaleSpan(Locale locale) { + mLocale = locale; + } + + public LocaleSpan(Parcel src) { + mLocale = new Locale(src.readString(), src.readString(), src.readString()); + } + + @Override + public int getSpanTypeId() { + return TextUtils.LOCALE_SPAN; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mLocale.getLanguage()); + dest.writeString(mLocale.getCountry()); + dest.writeString(mLocale.getVariant()); + } + + /** + * Returns the {@link Locale}. + * + * @return The {@link Locale} for this span. + */ + public Locale getLocale() { + return mLocale; + } + + @Override + public void updateDrawState(TextPaint ds) { + apply(ds, mLocale); + } + + @Override + public void updateMeasureState(TextPaint paint) { + apply(paint, mLocale); + } + + private static void apply(Paint paint, Locale locale) { + paint.setTextLocale(locale); + } +} diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java new file mode 100644 index 0000000..4fca570 --- /dev/null +++ b/core/java/android/util/AtomicFile.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.os.FileUtils; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Helper class for performing atomic operations on a file by creating a + * backup file until a write has successfully completed. If you need this + * on older versions of the platform you can use + * {@link android.support.v4.util.AtomicFile} in the v4 support library. + * <p> + * Atomic file guarantees file integrity by ensuring that a file has + * been completely written and sync'd to disk before removing its backup. + * As long as the backup file exists, the original file is considered + * to be invalid (left over from a previous attempt to write the file). + * </p><p> + * Atomic file does not confer any file locking semantics. + * Do not use this class when the file may be accessed or modified concurrently + * by multiple threads or processes. The caller is responsible for ensuring + * appropriate mutual exclusion invariants whenever it accesses the file. + * </p> + */ +public class AtomicFile { + private final File mBaseName; + private final File mBackupName; + + /** + * Create a new AtomicFile for a file located at the given File path. + * The secondary backup file will be the same file path with ".bak" appended. + */ + public AtomicFile(File baseName) { + mBaseName = baseName; + mBackupName = new File(baseName.getPath() + ".bak"); + } + + /** + * Return the path to the base file. You should not generally use this, + * as the data at that path may not be valid. + */ + public File getBaseFile() { + return mBaseName; + } + + /** + * Delete the atomic file. This deletes both the base and backup files. + */ + public void delete() { + mBaseName.delete(); + mBackupName.delete(); + } + + /** + * Start a new write operation on the file. This returns a FileOutputStream + * to which you can write the new file data. The existing file is replaced + * with the new data. You <em>must not</em> directly close the given + * FileOutputStream; instead call either {@link #finishWrite(FileOutputStream)} + * or {@link #failWrite(FileOutputStream)}. + * + * <p>Note that if another thread is currently performing + * a write, this will simply replace whatever that thread is writing + * with the new file being written by this thread, and when the other + * thread finishes the write the new write operation will no longer be + * safe (or will be lost). You must do your own threading protection for + * access to AtomicFile. + */ + public FileOutputStream startWrite() throws IOException { + // Rename the current file so it may be used as a backup during the next read + if (mBaseName.exists()) { + if (!mBackupName.exists()) { + if (!mBaseName.renameTo(mBackupName)) { + Log.w("AtomicFile", "Couldn't rename file " + mBaseName + + " to backup file " + mBackupName); + } + } else { + mBaseName.delete(); + } + } + FileOutputStream str = null; + try { + str = new FileOutputStream(mBaseName); + } catch (FileNotFoundException e) { + File parent = mBaseName.getParentFile(); + if (!parent.mkdir()) { + throw new IOException("Couldn't create directory " + mBaseName); + } + FileUtils.setPermissions( + parent.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + try { + str = new FileOutputStream(mBaseName); + } catch (FileNotFoundException e2) { + throw new IOException("Couldn't create " + mBaseName); + } + } + return str; + } + + /** + * Call when you have successfully finished writing to the stream + * returned by {@link #startWrite()}. This will close, sync, and + * commit the new data. The next attempt to read the atomic file + * will return the new file stream. + */ + public void finishWrite(FileOutputStream str) { + if (str != null) { + FileUtils.sync(str); + try { + str.close(); + mBackupName.delete(); + } catch (IOException e) { + Log.w("AtomicFile", "finishWrite: Got exception:", e); + } + } + } + + /** + * Call when you have failed for some reason at writing to the stream + * returned by {@link #startWrite()}. This will close the current + * write stream, and roll back to the previous state of the file. + */ + public void failWrite(FileOutputStream str) { + if (str != null) { + FileUtils.sync(str); + try { + str.close(); + mBaseName.delete(); + mBackupName.renameTo(mBaseName); + } catch (IOException e) { + Log.w("AtomicFile", "failWrite: Got exception:", e); + } + } + } + + /** @hide + * @deprecated This is not safe. + */ + @Deprecated public void truncate() throws IOException { + try { + FileOutputStream fos = new FileOutputStream(mBaseName); + FileUtils.sync(fos); + fos.close(); + } catch (FileNotFoundException e) { + throw new IOException("Couldn't append " + mBaseName); + } catch (IOException e) { + } + } + + /** @hide + * @deprecated This is not safe. + */ + @Deprecated public FileOutputStream openAppend() throws IOException { + try { + return new FileOutputStream(mBaseName, true); + } catch (FileNotFoundException e) { + throw new IOException("Couldn't append " + mBaseName); + } + } + + /** + * Open the atomic file for reading. If there previously was an + * incomplete write, this will roll back to the last good data before + * opening for read. You should call close() on the FileInputStream when + * you are done reading from it. + * + * <p>Note that if another thread is currently performing + * a write, this will incorrectly consider it to be in the state of a bad + * write and roll back, causing the new data currently being written to + * be dropped. You must do your own threading protection for access to + * AtomicFile. + */ + public FileInputStream openRead() throws FileNotFoundException { + if (mBackupName.exists()) { + mBaseName.delete(); + mBackupName.renameTo(mBaseName); + } + return new FileInputStream(mBaseName); + } + + /** + * A convenience for {@link #openRead()} that also reads all of the + * file contents into a byte array which is returned. + */ + public byte[] readFully() throws IOException { + FileInputStream stream = openRead(); + try { + int pos = 0; + int avail = stream.available(); + byte[] data = new byte[avail]; + while (true) { + int amt = stream.read(data, pos, data.length-pos); + //Log.i("foo", "Read " + amt + " bytes at " + pos + // + " of avail " + data.length); + if (amt <= 0) { + //Log.i("foo", "**** FINISHED READING: pos=" + pos + // + " len=" + data.length); + return data; + } + pos += amt; + avail = stream.available(); + if (avail > data.length-pos) { + byte[] newData = new byte[pos+avail]; + System.arraycopy(data, 0, newData, 0, pos); + data = newData; + } + } + } finally { + stream.close(); + } + } +} diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 80da0b2..85e4b9d 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -79,11 +79,20 @@ public class DisplayMetrics { public static final int DENSITY_DEFAULT = DENSITY_MEDIUM; /** + * Scaling factor to convert a density in DPI units to the density scale. + * @hide + */ + public static final float DENSITY_DEFAULT_SCALE = 1.0f / DENSITY_DEFAULT; + + /** * The device's density. - * @hide becase eventually this should be able to change while + * @hide because eventually this should be able to change while * running, so shouldn't be a constant. + * @deprecated There is no longer a static density; you can find the + * density for a display in {@link #densityDpi}. */ - public static final int DENSITY_DEVICE = getDeviceDensity(); + @Deprecated + public static int DENSITY_DEVICE = getDeviceDensity(); /** * The absolute width of the display in pixels. @@ -150,6 +159,12 @@ public class DisplayMetrics { */ public float noncompatDensity; /** + * The reported display density prior to any compatibility mode scaling + * being applied. + * @hide + */ + public int noncompatDensityDpi; + /** * The reported scaled density prior to any compatibility mode scaling * being applied. * @hide @@ -182,6 +197,7 @@ public class DisplayMetrics { noncompatWidthPixels = o.noncompatWidthPixels; noncompatHeightPixels = o.noncompatHeightPixels; noncompatDensity = o.noncompatDensity; + noncompatDensityDpi = o.noncompatDensityDpi; noncompatScaledDensity = o.noncompatScaledDensity; noncompatXdpi = o.noncompatXdpi; noncompatYdpi = o.noncompatYdpi; @@ -190,13 +206,52 @@ public class DisplayMetrics { public void setToDefaults() { widthPixels = 0; heightPixels = 0; - density = DENSITY_DEVICE / (float) DENSITY_DEFAULT; - densityDpi = DENSITY_DEVICE; + density = DENSITY_DEVICE / (float) DENSITY_DEFAULT; + densityDpi = DENSITY_DEVICE; scaledDensity = density; xdpi = DENSITY_DEVICE; ydpi = DENSITY_DEVICE; - noncompatWidthPixels = 0; - noncompatHeightPixels = 0; + noncompatWidthPixels = widthPixels; + noncompatHeightPixels = heightPixels; + noncompatDensity = density; + noncompatDensityDpi = densityDpi; + noncompatScaledDensity = scaledDensity; + noncompatXdpi = xdpi; + noncompatYdpi = ydpi; + } + + @Override + public boolean equals(Object o) { + return o instanceof DisplayMetrics && equals((DisplayMetrics)o); + } + + /** + * Returns true if these display metrics equal the other display metrics. + * + * @param other The display metrics with which to compare. + * @return True if the display metrics are equal. + */ + public boolean equals(DisplayMetrics other) { + return other != null + && widthPixels == other.widthPixels + && heightPixels == other.heightPixels + && density == other.density + && densityDpi == other.densityDpi + && scaledDensity == other.scaledDensity + && xdpi == other.xdpi + && ydpi == other.ydpi + && noncompatWidthPixels == other.noncompatWidthPixels + && noncompatHeightPixels == other.noncompatHeightPixels + && noncompatDensity == other.noncompatDensity + && noncompatDensityDpi == other.noncompatDensityDpi + && noncompatScaledDensity == other.noncompatScaledDensity + && noncompatXdpi == other.noncompatXdpi + && noncompatYdpi == other.noncompatYdpi; + } + + @Override + public int hashCode() { + return widthPixels * heightPixels * densityDpi; } @Override diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java index 6216638..9556223 100644 --- a/core/java/android/util/FloatMath.java +++ b/core/java/android/util/FloatMath.java @@ -71,4 +71,33 @@ public class FloatMath { * @return the square root of value */ public static native float sqrt(float value); + + /** + * Returns the closest float approximation of the raising "e" to the power + * of the argument. + * + * @param value to compute the exponential of + * @return the exponential of value + */ + public static native float exp(float value); + + /** + * Returns the closest float approximation of the result of raising {@code + * x} to the power of {@code y}. + * + * @param x the base of the operation. + * @param y the exponent of the operation. + * @return {@code x} to the power of {@code y}. + */ + public static native float pow(float x, float y); + + /** + * Returns {@code sqrt(}<i>{@code x}</i><sup>{@code 2}</sup>{@code +} <i> + * {@code y}</i><sup>{@code 2}</sup>{@code )}. + * + * @param x a float number + * @param y a float number + * @return the hypotenuse + */ + public static native float hypot(float x, float y); } diff --git a/core/java/android/util/LocaleUtil.java b/core/java/android/util/LocaleUtil.java deleted file mode 100644 index 93f5cd3..0000000 --- a/core/java/android/util/LocaleUtil.java +++ /dev/null @@ -1,85 +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.util; - -import java.util.Locale; - -import android.view.View; -import libcore.icu.ICU; - -/** - * Various utilities for Locales - * - * @hide - */ -public class LocaleUtil { - - private LocaleUtil() { /* cannot be instantiated */ } - - private static String ARAB_SCRIPT_SUBTAG = "Arab"; - private static String HEBR_SCRIPT_SUBTAG = "Hebr"; - - /** - * Return the layout direction for a given Locale - * - * @param locale the Locale for which we want the layout direction. Can be null. - * @return the layout direction. This may be one of: - * {@link View#LAYOUT_DIRECTION_LTR} or - * {@link View#LAYOUT_DIRECTION_RTL}. - * - * Warning: this code does not support vertical scripts. - * @hide - */ - public static int getLayoutDirectionFromLocale(Locale locale) { - if (locale != null && !locale.equals(Locale.ROOT)) { - final String scriptSubtag = ICU.getScript(ICU.addLikelySubtags(locale.toString())); - if (scriptSubtag == null) return getLayoutDirectionFromFirstChar(locale); - - if (scriptSubtag.equalsIgnoreCase(ARAB_SCRIPT_SUBTAG) || - scriptSubtag.equalsIgnoreCase(HEBR_SCRIPT_SUBTAG)) { - return View.LAYOUT_DIRECTION_RTL; - } - } - - return View.LAYOUT_DIRECTION_LTR; - } - - /** - * Fallback algorithm to detect the locale direction. Rely on the fist char of the - * localized locale name. This will not work if the localized locale name is in English - * (this is the case for ICU 4.4 and "Urdu" script) - * - * @param locale - * @return the layout direction. This may be one of: - * {@link View#LAYOUT_DIRECTION_LTR} or - * {@link View#LAYOUT_DIRECTION_RTL}. - * - * Warning: this code does not support vertical scripts. - * @hide - */ - private static int getLayoutDirectionFromFirstChar(Locale locale) { - switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) { - case Character.DIRECTIONALITY_RIGHT_TO_LEFT: - case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: - return View.LAYOUT_DIRECTION_RTL; - - case Character.DIRECTIONALITY_LEFT_TO_RIGHT: - default: - return View.LAYOUT_DIRECTION_LTR; - } - } -} diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java index 51e373c..dd504c1 100644 --- a/core/java/android/util/LruCache.java +++ b/core/java/android/util/LruCache.java @@ -186,10 +186,13 @@ public class LruCache<K, V> { } /** + * Remove the eldest entries until the total of remaining entries is at or + * below the requested size. + * * @param maxSize the maximum size of the cache before returning. May be -1 - * to evict even 0-sized elements. + * to evict even 0-sized elements. */ - private void trimToSize(int maxSize) { + public void trimToSize(int maxSize) { while (true) { K key; V value; diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index 2179ff3..602a68c 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -59,10 +59,10 @@ public class NtpTrustedTime implements TrustedTime { final long defaultTimeout = res.getInteger( com.android.internal.R.integer.config_ntpTimeout); - final String secureServer = Settings.Secure.getString( - resolver, Settings.Secure.NTP_SERVER); - final long timeout = Settings.Secure.getLong( - resolver, Settings.Secure.NTP_TIMEOUT, defaultTimeout); + final String secureServer = Settings.Global.getString( + resolver, Settings.Global.NTP_SERVER); + final long timeout = Settings.Global.getLong( + resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout); final String server = secureServer != null ? secureServer : defaultServer; sSingleton = new NtpTrustedTime(server, timeout); @@ -71,7 +71,7 @@ public class NtpTrustedTime implements TrustedTime { return sSingleton; } - /** {@inheritDoc} */ + @Override public boolean forceRefresh() { if (mServer == null) { // missing server, so no trusted time available @@ -91,12 +91,12 @@ public class NtpTrustedTime implements TrustedTime { } } - /** {@inheritDoc} */ + @Override public boolean hasCache() { return mHasCache; } - /** {@inheritDoc} */ + @Override public long getCacheAge() { if (mHasCache) { return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime; @@ -105,7 +105,7 @@ public class NtpTrustedTime implements TrustedTime { } } - /** {@inheritDoc} */ + @Override public long getCacheCertainty() { if (mHasCache) { return mCachedNtpCertainty; @@ -114,7 +114,7 @@ public class NtpTrustedTime implements TrustedTime { } } - /** {@inheritDoc} */ + @Override public long currentTimeMillis() { if (!mHasCache) { throw new IllegalStateException("Missing authoritative time source"); diff --git a/core/java/android/util/Pair.java b/core/java/android/util/Pair.java index bf25306..6027d08 100644 --- a/core/java/android/util/Pair.java +++ b/core/java/android/util/Pair.java @@ -16,6 +16,8 @@ package android.util; +import libcore.util.Objects; + /** * Container to ease passing around a tuple of two objects. This object provides a sensible * implementation of equals(), returning true if equals() is true on each of the contained @@ -26,8 +28,8 @@ public class Pair<F, S> { public final S second; /** - * Constructor for a Pair. If either are null then equals() and hashCode() will throw - * a NullPointerException. + * Constructor for a Pair. + * * @param first the first object in the Pair * @param second the second object in the pair */ @@ -37,31 +39,30 @@ public class Pair<F, S> { } /** - * Checks the two objects for equality by delegating to their respective equals() methods. - * @param o the Pair to which this one is to be checked for equality - * @return true if the underlying objects of the Pair are both considered equals() + * Checks the two objects for equality by delegating to their respective + * {@link Object#equals(Object)} methods. + * + * @param o the {@link Pair} to which this one is to be checked for equality + * @return true if the underlying objects of the Pair are both considered + * equal */ + @Override public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof Pair)) return false; - final Pair<F, S> other; - try { - other = (Pair<F, S>) o; - } catch (ClassCastException e) { + if (!(o instanceof Pair)) { return false; } - return first.equals(other.first) && second.equals(other.second); + Pair<?, ?> p = (Pair<?, ?>) o; + return Objects.equal(p.first, first) && Objects.equal(p.second, second); } /** * Compute a hash code using the hash codes of the underlying objects + * * @return a hashcode of the Pair */ + @Override public int hashCode() { - int result = 17; - result = 31 * result + first.hashCode(); - result = 31 * result + second.hashCode(); - return result; + return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); } /** diff --git a/core/java/android/util/Spline.java b/core/java/android/util/Spline.java new file mode 100644 index 0000000..ed027eb --- /dev/null +++ b/core/java/android/util/Spline.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * Performs spline interpolation given a set of control points. + * @hide + */ +public final class Spline { + private final float[] mX; + private final float[] mY; + private final float[] mM; + + private Spline(float[] x, float[] y, float[] m) { + mX = x; + mY = y; + mM = m; + } + + /** + * Creates a monotone cubic spline from a given set of control points. + * + * The spline is guaranteed to pass through each control point exactly. + * Moreover, assuming the control points are monotonic (Y is non-decreasing or + * non-increasing) then the interpolated values will also be monotonic. + * + * This function uses the Fritsch-Carlson method for computing the spline parameters. + * http://en.wikipedia.org/wiki/Monotone_cubic_interpolation + * + * @param x The X component of the control points, strictly increasing. + * @param y The Y component of the control points, monotonic. + * @return + * + * @throws IllegalArgumentException if the X or Y arrays are null, have + * different lengths or have fewer than 2 values. + * @throws IllegalArgumentException if the control points are not monotonic. + */ + public static Spline createMonotoneCubicSpline(float[] x, float[] y) { + if (x == null || y == null || x.length != y.length || x.length < 2) { + throw new IllegalArgumentException("There must be at least two control " + + "points and the arrays must be of equal length."); + } + + final int n = x.length; + float[] d = new float[n - 1]; // could optimize this out + float[] m = new float[n]; + + // Compute slopes of secant lines between successive points. + for (int i = 0; i < n - 1; i++) { + float h = x[i + 1] - x[i]; + if (h <= 0f) { + throw new IllegalArgumentException("The control points must all " + + "have strictly increasing X values."); + } + d[i] = (y[i + 1] - y[i]) / h; + } + + // Initialize the tangents as the average of the secants. + m[0] = d[0]; + for (int i = 1; i < n - 1; i++) { + m[i] = (d[i - 1] + d[i]) * 0.5f; + } + m[n - 1] = d[n - 2]; + + // Update the tangents to preserve monotonicity. + for (int i = 0; i < n - 1; i++) { + if (d[i] == 0f) { // successive Y values are equal + m[i] = 0f; + m[i + 1] = 0f; + } else { + float a = m[i] / d[i]; + float b = m[i + 1] / d[i]; + if (a < 0f || b < 0f) { + throw new IllegalArgumentException("The control points must have " + + "monotonic Y values."); + } + float h = FloatMath.hypot(a, b); + if (h > 9f) { + float t = 3f / h; + m[i] = t * a * d[i]; + m[i + 1] = t * b * d[i]; + } + } + } + return new Spline(x, y, m); + } + + /** + * Interpolates the value of Y = f(X) for given X. + * Clamps X to the domain of the spline. + * + * @param x The X value. + * @return The interpolated Y = f(X) value. + */ + public float interpolate(float x) { + // Handle the boundary cases. + final int n = mX.length; + if (Float.isNaN(x)) { + return x; + } + if (x <= mX[0]) { + return mY[0]; + } + if (x >= mX[n - 1]) { + return mY[n - 1]; + } + + // Find the index 'i' of the last point with smaller X. + // We know this will be within the spline due to the boundary tests. + int i = 0; + while (x >= mX[i + 1]) { + i += 1; + if (x == mX[i]) { + return mY[i]; + } + } + + // Perform cubic Hermite spline interpolation. + float h = mX[i + 1] - mX[i]; + float t = (x - mX[i]) / h; + return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t) + + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t; + } + + // For debugging. + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + final int n = mX.length; + str.append("["); + for (int i = 0; i < n; i++) { + if (i != 0) { + str.append(", "); + } + str.append("(").append(mX[i]); + str.append(", ").append(mY[i]); + str.append(": ").append(mM[i]).append(")"); + } + str.append("]"); + return str.toString(); + } +} diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index c4ebec4..5a4f322 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -18,8 +18,11 @@ package android.util; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.os.SystemClock; +import android.text.format.DateUtils; + +import com.android.internal.util.XmlUtils; -import libcore.util.ZoneInfoDB; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -28,10 +31,10 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; -import java.util.TimeZone; import java.util.Date; +import java.util.TimeZone; -import com.android.internal.util.XmlUtils; +import libcore.util.ZoneInfoDB; /** * A class containing utility methods related to time zones. @@ -245,6 +248,8 @@ public class TimeUtils { private static final Object sFormatSync = new Object(); private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5]; + private static final long LARGEST_DURATION = (1000 * DateUtils.DAY_IN_MILLIS) - 1; + static private int accumField(int amt, int suffix, boolean always, int zeropad) { if (amt > 99 || (always && zeropad >= 3)) { return 3+suffix; @@ -307,6 +312,10 @@ public class TimeUtils { duration = -duration; } + if (duration > LARGEST_DURATION) { + duration = LARGEST_DURATION; + } + int millis = (int)(duration%1000); int seconds = (int) Math.floor(duration / 1000); int days = 0, hours = 0, minutes = 0; @@ -383,6 +392,18 @@ public class TimeUtils { formatDuration(time-now, pw, 0); } + /** @hide Just for debugging; not internationalized. */ + public static String formatUptime(long time) { + final long diff = time - SystemClock.uptimeMillis(); + if (diff > 0) { + return time + " (in " + diff + " ms)"; + } + if (diff < 0) { + return time + " (" + -diff + " ms ago)"; + } + return time + " (now)"; + } + /** * Convert a System.currentTimeMillis() value to a time of day value like * that printed in logs. MM-DD HH:MM:SS.MMM diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 0aabc44..9bee4bf 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -18,22 +18,21 @@ package android.view; import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS; +import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; -import android.util.Pool; -import android.util.Poolable; -import android.util.PoolableManager; -import android.util.Pools; import android.util.SparseLongArray; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import com.android.internal.os.SomeArgs; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -47,7 +46,6 @@ import java.util.Map; * UI thread. */ final class AccessibilityInteractionController { - private static final int POOL_SIZE = 5; private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = new ArrayList<AccessibilityNodeInfo>(); @@ -64,6 +62,8 @@ final class AccessibilityInteractionController { private final ArrayList<View> mTempArrayList = new ArrayList<View>(); + private final Rect mTempRect = new Rect(); + public AccessibilityInteractionController(ViewRootImpl viewRootImpl) { Looper looper = viewRootImpl.mHandler.getLooper(); mMyLooperThreadId = looper.getThread().getId(); @@ -73,60 +73,6 @@ final class AccessibilityInteractionController { mPrefetcher = new AccessibilityNodePrefetcher(); } - // Reusable poolable arguments for interacting with the view hierarchy - // to fit more arguments than Message and to avoid sharing objects between - // two messages since several threads can send messages concurrently. - private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool( - new PoolableManager<SomeArgs>() { - public SomeArgs newInstance() { - return new SomeArgs(); - } - - public void onAcquired(SomeArgs info) { - /* do nothing */ - } - - public void onReleased(SomeArgs info) { - info.clear(); - } - }, POOL_SIZE) - ); - - private class SomeArgs implements Poolable<SomeArgs> { - private SomeArgs mNext; - private boolean mIsPooled; - - public Object arg1; - public Object arg2; - public int argi1; - public int argi2; - public int argi3; - - public SomeArgs getNextPoolable() { - return mNext; - } - - public boolean isPooled() { - return mIsPooled; - } - - public void setNextPoolable(SomeArgs args) { - mNext = args; - } - - public void setPooled(boolean isPooled) { - mIsPooled = isPooled; - } - - private void clear() { - arg1 = null; - arg2 = null; - argi1 = 0; - argi2 = 0; - argi3 = 0; - } - } - private boolean isShown(View view) { // The first two checks are made also made by isShown() which // however traverses the tree up to the parent to catch that. @@ -138,24 +84,18 @@ final class AccessibilityInteractionController { } public void findAccessibilityNodeInfoByAccessibilityIdClientThread( - long accessibilityNodeId, int windowLeft, int windowTop, int interactionId, + long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID; message.arg1 = flags; - SomeArgs args = mPool.acquire(); + SomeArgs args = SomeArgs.obtain(); args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); args.argi3 = interactionId; args.arg1 = callback; - - SomeArgs moreArgs = mPool.acquire(); - moreArgs.argi1 = windowLeft; - moreArgs.argi2 = windowTop; - args.arg2 = moreArgs; - message.obj = args; // If the interrogation is performed by the same thread as the main UI @@ -180,12 +120,7 @@ final class AccessibilityInteractionController { final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; - SomeArgs moreArgs = (SomeArgs) args.arg2; - mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1; - mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2; - - mPool.release(moreArgs); - mPool.release(args); + args.recycle(); List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; infos.clear(); @@ -207,6 +142,7 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; + applyApplicationScaleIfNeeded(infos); callback.setFindAccessibilityNodeInfosResult(infos, interactionId); infos.clear(); } catch (RemoteException re) { @@ -216,24 +152,18 @@ final class AccessibilityInteractionController { } public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId, - int viewId, int windowLeft, int windowTop, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, - long interrogatingTid) { + int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, + int flags, int interrogatingPid, long interrogatingTid) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID; message.arg1 = flags; message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); - SomeArgs args = mPool.acquire(); + SomeArgs args = SomeArgs.obtain(); args.argi1 = viewId; args.argi2 = interactionId; args.arg1 = callback; - SomeArgs moreArgs = mPool.acquire(); - moreArgs.argi1 = windowLeft; - moreArgs.argi2 = windowTop; - args.arg2 = moreArgs; - message.obj = args; // If the interrogation is performed by the same thread as the main UI @@ -258,12 +188,7 @@ final class AccessibilityInteractionController { final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; - SomeArgs moreArgs = (SomeArgs) args.arg2; - mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1; - mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2; - - mPool.release(moreArgs); - mPool.release(args); + args.recycle(); AccessibilityNodeInfo info = null; try { @@ -287,6 +212,7 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; + applyApplicationScaleIfNeeded(info); callback.setFindAccessibilityNodeInfoResult(info, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -295,25 +221,19 @@ final class AccessibilityInteractionController { } public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, - String text, int windowLeft, int windowTop, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid) { + String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, + int flags, int interrogatingPid, long interrogatingTid) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT; message.arg1 = flags; - SomeArgs args = mPool.acquire(); + SomeArgs args = SomeArgs.obtain(); args.arg1 = text; + args.arg2 = callback; args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); args.argi3 = interactionId; - SomeArgs moreArgs = mPool.acquire(); - moreArgs.arg1 = callback; - moreArgs.argi1 = windowLeft; - moreArgs.argi2 = windowTop; - args.arg2 = moreArgs; - message.obj = args; // If the interrogation is performed by the same thread as the main UI @@ -333,18 +253,12 @@ final class AccessibilityInteractionController { SomeArgs args = (SomeArgs) message.obj; final String text = (String) args.arg1; + final IAccessibilityInteractionConnectionCallback callback = + (IAccessibilityInteractionConnectionCallback) args.arg2; final int accessibilityViewId = args.argi1; final int virtualDescendantId = args.argi2; final int interactionId = args.argi3; - - SomeArgs moreArgs = (SomeArgs) args.arg2; - final IAccessibilityInteractionConnectionCallback callback = - (IAccessibilityInteractionConnectionCallback) moreArgs.arg1; - mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1; - mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2; - - mPool.release(moreArgs); - mPool.release(args); + args.recycle(); List<AccessibilityNodeInfo> infos = null; try { @@ -396,6 +310,7 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; + applyApplicationScaleIfNeeded(infos); callback.setFindAccessibilityNodeInfosResult(infos, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -403,25 +318,20 @@ final class AccessibilityInteractionController { } } - public void findFocusClientThread(long accessibilityNodeId, int focusType, int windowLeft, - int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, - int flags, int interogatingPid, long interrogatingTid) { + public void findFocusClientThread(long accessibilityNodeId, int focusType, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, + long interrogatingTid) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FIND_FOCUS; message.arg1 = flags; message.arg2 = focusType; - SomeArgs args = mPool.acquire(); + SomeArgs args = SomeArgs.obtain(); args.argi1 = interactionId; args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); args.arg1 = callback; - SomeArgs moreArgs = mPool.acquire(); - moreArgs.argi1 = windowLeft; - moreArgs.argi2 = windowTop; - args.arg2 = moreArgs; - message.obj = args; // If the interrogation is performed by the same thread as the main UI @@ -447,12 +357,7 @@ final class AccessibilityInteractionController { final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; - SomeArgs moreArgs = (SomeArgs) args.arg2; - mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1; - mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2; - - mPool.release(moreArgs); - mPool.release(args); + args.recycle(); AccessibilityNodeInfo focused = null; try { @@ -502,6 +407,7 @@ final class AccessibilityInteractionController { } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; + applyApplicationScaleIfNeeded(focused); callback.setFindAccessibilityNodeInfoResult(focused, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -509,25 +415,19 @@ final class AccessibilityInteractionController { } } - public void focusSearchClientThread(long accessibilityNodeId, int direction, int windowLeft, - int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, - int flags, int interogatingPid, long interrogatingTid) { + public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, + long interrogatingTid) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_FOCUS_SEARCH; message.arg1 = flags; message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); - SomeArgs args = mPool.acquire(); - args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); + SomeArgs args = SomeArgs.obtain(); args.argi2 = direction; args.argi3 = interactionId; args.arg1 = callback; - SomeArgs moreArgs = mPool.acquire(); - moreArgs.argi1 = windowLeft; - moreArgs.argi2 = windowTop; - args.arg2 = moreArgs; - message.obj = args; // If the interrogation is performed by the same thread as the main UI @@ -547,18 +447,12 @@ final class AccessibilityInteractionController { final int accessibilityViewId = message.arg2; SomeArgs args = (SomeArgs) message.obj; - final int virtualDescendantId = args.argi1; final int direction = args.argi2; final int interactionId = args.argi3; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; - SomeArgs moreArgs = (SomeArgs) args.arg2; - mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1; - mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2; - - mPool.release(moreArgs); - mPool.release(args); + args.recycle(); AccessibilityNodeInfo next = null; try { @@ -574,43 +468,15 @@ final class AccessibilityInteractionController { root = mViewRootImpl.mView; } if (root != null && isShown(root)) { - if ((direction & View.FOCUS_ACCESSIBILITY) == View.FOCUS_ACCESSIBILITY) { - AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); - if (provider != null) { - next = provider.accessibilityFocusSearch(direction, virtualDescendantId); - if (next != null) { - return; - } - } - View nextView = root.focusSearch(direction); - while (nextView != null) { - // If the focus search reached a node with a provider - // we delegate to the provider to find the next one. - // If the provider does not return a virtual view to - // take accessibility focus we try the next view found - // by the focus search algorithm. - provider = nextView.getAccessibilityNodeProvider(); - if (provider != null) { - next = provider.accessibilityFocusSearch(direction, View.NO_ID); - if (next != null) { - break; - } - nextView = nextView.focusSearch(direction); - } else { - next = nextView.createAccessibilityNodeInfo(); - break; - } - } - } else { - View nextView = root.focusSearch(direction); - if (nextView != null) { - next = nextView.createAccessibilityNodeInfo(); - } + View nextView = root.focusSearch(direction); + if (nextView != null) { + next = nextView.createAccessibilityNodeInfo(); } } } finally { try { mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; + applyApplicationScaleIfNeeded(next); callback.setFindAccessibilityNodeInfoResult(next, interactionId); } catch (RemoteException re) { /* ignore - the other side will time out */ @@ -627,7 +493,7 @@ final class AccessibilityInteractionController { message.arg1 = flags; message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); - SomeArgs args = mPool.acquire(); + SomeArgs args = SomeArgs.obtain(); args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); args.argi2 = action; args.argi3 = interactionId; @@ -660,7 +526,7 @@ final class AccessibilityInteractionController { (IAccessibilityInteractionConnectionCallback) args.arg1; Bundle arguments = (Bundle) args.arg2; - mPool.release(args); + args.recycle(); boolean succeeded = false; try { @@ -706,6 +572,39 @@ final class AccessibilityInteractionController { return foundView; } + private void applyApplicationScaleIfNeeded(List<AccessibilityNodeInfo> infos) { + if (infos == null) { + return; + } + final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; + if (applicationScale != 1.0f) { + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + AccessibilityNodeInfo info = infos.get(i); + applyApplicationScaleIfNeeded(info); + } + } + } + + private void applyApplicationScaleIfNeeded(AccessibilityNodeInfo info) { + if (info == null) { + return; + } + final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; + if (applicationScale != 1.0f) { + Rect bounds = mTempRect; + + info.getBoundsInParent(bounds); + bounds.scale(applicationScale); + info.setBoundsInParent(bounds); + + info.getBoundsInScreen(bounds); + bounds.scale(applicationScale); + info.setBoundsInScreen(bounds); + } + } + + /** * This class encapsulates a prefetching strategy for the accessibility APIs for * querying window content. It is responsible to prefetch a batch of diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 78dc86f..b661748 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -16,6 +16,7 @@ package android.view; +import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -164,8 +165,8 @@ public final class Choreographer { mHandler = new FrameHandler(looper); mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; mLastFrameTimeNanos = Long.MIN_VALUE; - mFrameIntervalNanos = (long)(1000000000 / - new Display(Display.DEFAULT_DISPLAY, null).getRefreshRate()); + + mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { @@ -173,6 +174,12 @@ public final class Choreographer { } } + private static float getRefreshRate() { + DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo( + Display.DEFAULT_DISPLAY); + return di.refreshRate; + } + /** * Gets the choreographer for the calling thread. Must be called from * a thread that already has a {@link android.os.Looper} associated with it. @@ -677,7 +684,24 @@ public final class Choreographer { } @Override - public void onVsync(long timestampNanos, int frame) { + public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { + // Ignore vsync from secondary display. + // This can be problematic because the call to scheduleVsync() is a one-shot. + // We need to ensure that we will still receive the vsync from the primary + // display which is the one we really care about. Ideally we should schedule + // vsync for a particular display. + // At this time Surface Flinger won't send us vsyncs for secondary displays + // but that could change in the future so let's log a message to help us remember + // that we need to fix this. + if (builtInDisplayId != Surface.BUILT_IN_DISPLAY_ID_MAIN) { + Log.d(TAG, "Received vsync from secondary display, but we don't support " + + "this case yet. Choreographer needs a way to explicitly request " + + "vsync for a specific display to ensure it doesn't lose track " + + "of its scheduled vsync."); + scheduleVsync(); + return; + } + // Post the vsync event to the Handler. // The idea is to prevent incoming vsync events from completely starving // the message queue. If there are no messages in the queue with timestamps diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index 626f385..6c733f9 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -18,6 +18,7 @@ package android.view; import android.content.Context; import android.content.ContextWrapper; +import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; @@ -30,6 +31,8 @@ public class ContextThemeWrapper extends ContextWrapper { private int mThemeResource; private Resources.Theme mTheme; private LayoutInflater mInflater; + private Configuration mOverrideConfiguration; + private Resources mResources; public ContextThemeWrapper() { super(null); @@ -45,6 +48,41 @@ public class ContextThemeWrapper extends ContextWrapper { super.attachBaseContext(newBase); mBase = newBase; } + + /** + * Call to set an "override configuration" on this context -- this is + * a configuration that replies one or more values of the standard + * configuration that is applied to the context. See + * {@link Context#createConfigurationContext(Configuration)} for more + * information. + * + * <p>This method can only be called once, and must be called before any + * calls to {@link #getResources()} are made. + */ + public void applyOverrideConfiguration(Configuration overrideConfiguration) { + if (mResources != null) { + throw new IllegalStateException("getResources() has already been called"); + } + if (mOverrideConfiguration != null) { + throw new IllegalStateException("Override configuration has already been set"); + } + mOverrideConfiguration = new Configuration(overrideConfiguration); + } + + @Override + public Resources getResources() { + if (mResources != null) { + return mResources; + } + if (mOverrideConfiguration == null) { + mResources = super.getResources(); + return mResources; + } else { + Context resc = createConfigurationContext(mOverrideConfiguration); + mResources = resc.getResources(); + return mResources; + } + } @Override public void setTheme(int resid) { mThemeResource = resid; diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index c947312..cf58458 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -16,61 +16,213 @@ package android.view; -import android.content.res.CompatibilityInfo; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.os.RemoteException; -import android.os.ServiceManager; +import android.hardware.display.DisplayManagerGlobal; import android.os.SystemClock; import android.util.DisplayMetrics; -import android.util.Slog; +import android.util.Log; /** - * Provides information about the display size and density. + * Provides information about the size and density of a logical display. + * <p> + * The display area is described in two different ways. + * <ul> + * <li>The application display area specifies the part of the display that may contain + * an application window, excluding the system decorations. The application display area may + * be smaller than the real display area because the system subtracts the space needed + * for decor elements such as the status bar. Use the following methods to query the + * application display area: {@link #getSize}, {@link #getRectSize} and {@link #getMetrics}.</li> + * <li>The real display area specifies the part of the display that contains content + * including the system decorations. Even so, the real display area may be smaller than the + * physical size of the display if the window manager is emulating a smaller display + * using (adb shell am display-size). Use the following methods to query the + * real display area: {@link #getRealSize}, {@link #getRealMetrics}.</li> + * </ul> + * </p><p> + * A logical display does not necessarily represent a particular physical display device + * such as the built-in screen or an external monitor. The contents of a logical + * display may be presented on one or more physical displays according to the devices + * that are currently attached and whether mirroring has been enabled. + * </p> */ -public class Display { - static final String TAG = "Display"; - static final boolean DEBUG_DISPLAY_SIZE = false; +public final class Display { + private static final String TAG = "Display"; + private static final boolean DEBUG = false; + + private final DisplayManagerGlobal mGlobal; + private final int mDisplayId; + private final int mLayerStack; + private final String mName; + private final CompatibilityInfoHolder mCompatibilityInfo; + + private DisplayInfo mDisplayInfo; // never null + private boolean mIsValid; + + // Temporary display metrics structure used for compatibility mode. + private final DisplayMetrics mTempMetrics = new DisplayMetrics(); + + // We cache the app width and height properties briefly between calls + // to getHeight() and getWidth() to ensure that applications perceive + // consistent results when the size changes (most of the time). + // Applications should now be using getSize() instead. + private static final int CACHED_APP_SIZE_DURATION_MILLIS = 20; + private long mLastCachedAppSizeUpdate; + private int mCachedAppWidthCompat; + private int mCachedAppHeightCompat; /** - * The default Display id. + * The default Display id, which is the id of the built-in primary display + * assuming there is one. */ public static final int DEFAULT_DISPLAY = 0; /** - * Use {@link android.view.WindowManager#getDefaultDisplay() - * WindowManager.getDefaultDisplay()} to create a Display object. - * Display gives you access to some information about a particular display - * connected to the device. + * Display flag: Indicates that the display supports secure video output. + * <p> + * This flag is used to indicate that the display supports content protection + * mechanisms for secure video output at the display interface, such as HDCP. + * These mechanisms may be used to protect secure content as it leaves the device. + * </p><p> + * While mirroring content to multiple displays, it can happen that certain + * display devices support secure video output while other display devices do not. + * The secure content will be shown only on the display devices that support + * secure video output and will be blanked on other display devices that do + * not support secure video output. + * </p><p> + * This flag mainly applies to external display devices such as HDMI or + * Wifi display. Built-in display devices are usually considered secure. + * </p> + * + * @hide pending review */ - Display(int display, CompatibilityInfoHolder compatInfo) { - // initalize the statics when this class is first instansiated. This is - // done here instead of in the static block because Zygote - synchronized (sStaticInit) { - if (!sInitialized) { - nativeClassInit(); - sInitialized = true; - } - } - mCompatibilityInfo = compatInfo != null ? compatInfo : new CompatibilityInfoHolder(); - mDisplay = display; - init(display); + public static final int FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT = 1 << 0; + + /** + * Display flag: Indicates that the display supports secure in-memory video buffers. + * <p> + * This flag is used to indicate that the display supports content protection + * mechanisms for in-memory video buffers, such as secure memory areas. + * These mechanisms may be used to protect secure video buffers in memory from + * the video decoder to the display compositor and the video interface. + * </p> + * + * @hide pending review + */ + public static final int FLAG_SUPPORTS_SECURE_VIDEO_BUFFERS = 1 << 1; + + /** + * Internal method to create a display. + * Applications should use {@link android.view.WindowManager#getDefaultDisplay()} + * or {@link android.hardware.display.DisplayManager#getDisplay} + * to get a display object. + * + * @hide + */ + public Display(DisplayManagerGlobal global, + int displayId, DisplayInfo displayInfo /*not null*/, + CompatibilityInfoHolder compatibilityInfo) { + mGlobal = global; + mDisplayId = displayId; + mDisplayInfo = displayInfo; + mLayerStack = displayInfo.layerStack; // can never change as long as the display is valid + mName = displayInfo.name; // cannot change as long as the display is valid + mCompatibilityInfo = compatibilityInfo; + mIsValid = true; } /** - * Returns the index of this display. This is currently undefined; do - * not use. + * Gets the display id. + * <p> + * Each logical display has a unique id. + * The default display has id {@link #DEFAULT_DISPLAY}. + * </p> */ public int getDisplayId() { - return mDisplay; + return mDisplayId; + } + + /** + * Returns true if this display is still valid, false if the display has been removed. + * + * If the display is invalid, then the methods of this class will + * continue to report the most recently observed display information. + * However, it is unwise (and rather fruitless) to continue using a + * {@link Display} object after the display's demise. + * + * It's possible for a display that was previously invalid to become + * valid again if a display with the same id is reconnected. + * + * @return True if the display is still valid. + */ + public boolean isValid() { + synchronized (this) { + updateDisplayInfoLocked(); + return mIsValid; + } } /** - * Returns the number of displays connected to the device. This is - * currently undefined; do not use. + * Gets a full copy of the display information. + * + * @param outDisplayInfo The object to receive the copy of the display information. + * @return True if the display is still valid. + * @hide */ - native static int getDisplayCount(); - + public boolean getDisplayInfo(DisplayInfo outDisplayInfo) { + synchronized (this) { + updateDisplayInfoLocked(); + outDisplayInfo.copyFrom(mDisplayInfo); + return mIsValid; + } + } + + /** + * Gets the display's layer stack. + * + * Each display has its own independent layer stack upon which surfaces + * are placed to be managed by surface flinger. + * + * @return The display's layer stack number. + * @hide + */ + public int getLayerStack() { + return mLayerStack; + } + + /** + * Returns a combination of flags that describe the capabilities of the display. + * + * @return The display flags. + * + * @hide pending review + */ + public int getFlags() { + synchronized (this) { + updateDisplayInfoLocked(); + return mDisplayInfo.flags; + } + } + + /** + * Gets the compatibility info used by this display instance. + * + * @return The compatibility info holder, or null if none is required. + * @hide + */ + public CompatibilityInfoHolder getCompatibilityInfo() { + return mCompatibilityInfo; + } + + /** + * Gets the name of the display. + * @return The display's name. + */ + public String getName() { + return mName; + } + /** * Gets the size of the display, in pixels. * <p> @@ -84,7 +236,7 @@ public class Display { * </p><p> * The size returned by this method does not necessarily represent the * actual raw size (native resolution) of the display. The returned size may - * be adjusted to exclude certain system decor elements that are always visible. + * be adjusted to exclude certain system decoration elements that are always visible. * It may also be scaled to provide compatibility with older applications that * were originally designed for smaller displays. * </p> @@ -92,43 +244,14 @@ public class Display { * @param outSize A {@link Point} object to receive the size information. */ public void getSize(Point outSize) { - getSizeInternal(outSize, true); - } - - private void getSizeInternal(Point outSize, boolean doCompat) { - try { - IWindowManager wm = getWindowManager(); - if (wm != null) { - wm.getDisplaySize(outSize); - CompatibilityInfo ci; - if (doCompat && (ci=mCompatibilityInfo.getIfNeeded()) != null) { - synchronized (mTmpMetrics) { - mTmpMetrics.noncompatWidthPixels = outSize.x; - mTmpMetrics.noncompatHeightPixels = outSize.y; - mTmpMetrics.density = mDensity; - ci.applyToDisplayMetrics(mTmpMetrics); - outSize.x = mTmpMetrics.widthPixels; - outSize.y = mTmpMetrics.heightPixels; - } - } - } else { - // This is just for boot-strapping, initializing the - // system process before the window manager is up. - outSize.x = getRawWidth(); - outSize.y = getRawHeight(); - } - if (false) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.v(TAG, "Returning display size: " + outSize, here); - } - if (DEBUG_DISPLAY_SIZE && doCompat) Slog.v( - TAG, "Returning display size: " + outSize); - } catch (RemoteException e) { - Slog.w("Display", "Unable to get display size", e); + synchronized (this) { + updateDisplayInfoLocked(); + mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); + outSize.x = mTempMetrics.widthPixels; + outSize.y = mTempMetrics.heightPixels; } } - + /** * Gets the size of the display as a rectangle, in pixels. * @@ -136,9 +259,10 @@ public class Display { * @see #getSize(Point) */ public void getRectSize(Rect outSize) { - synchronized (mTmpPoint) { - getSizeInternal(mTmpPoint, true); - outSize.set(0, 0, mTmpPoint.x, mTmpPoint.y); + synchronized (this) { + updateDisplayInfoLocked(); + mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); + outSize.set(0, 0, mTempMetrics.widthPixels, mTempMetrics.heightPixels); } } @@ -173,15 +297,12 @@ public class Display { * for example, screen decorations like the status bar are being hidden. */ public void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) { - try { - IWindowManager wm = getWindowManager(); - wm.getCurrentSizeRange(outSmallestSize, outLargestSize); - } catch (RemoteException e) { - Slog.w("Display", "Unable to get display size range", e); - outSmallestSize.x = 0; - outSmallestSize.y = 0; - outLargestSize.x = 0; - outLargestSize.y = 0; + synchronized (this) { + updateDisplayInfoLocked(); + outSmallestSize.x = mDisplayInfo.smallestNominalAppWidth; + outSmallestSize.y = mDisplayInfo.smallestNominalAppHeight; + outLargestSize.x = mDisplayInfo.largestNominalAppWidth; + outLargestSize.y = mDisplayInfo.largestNominalAppHeight; } } @@ -191,12 +312,9 @@ public class Display { * @hide */ public int getMaximumSizeDimension() { - try { - IWindowManager wm = getWindowManager(); - return wm.getMaximumSizeDimension(); - } catch (RemoteException e) { - Slog.w("Display", "Unable to get display maximum size dimension", e); - return 0; + synchronized (this) { + updateDisplayInfoLocked(); + return Math.max(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); } } @@ -205,13 +323,9 @@ public class Display { */ @Deprecated public int getWidth() { - synchronized (mTmpPoint) { - long now = SystemClock.uptimeMillis(); - if (now > (mLastGetTime+20)) { - getSizeInternal(mTmpPoint, true); - mLastGetTime = now; - } - return mTmpPoint.x; + synchronized (this) { + updateCachedAppSizeIfNeededLocked(); + return mCachedAppWidthCompat; } } @@ -220,76 +334,13 @@ public class Display { */ @Deprecated public int getHeight() { - synchronized (mTmpPoint) { - long now = SystemClock.uptimeMillis(); - if (now > (mLastGetTime+20)) { - getSizeInternal(mTmpPoint, true); - mLastGetTime = now; - } - return mTmpPoint.y; - } - } - - /** - * Gets the real size of the display without subtracting any window decor or - * applying any compatibility scale factors. - * <p> - * The real size may be smaller than the raw size when the window manager - * is emulating a smaller display (using adb shell am display-size). - * </p><p> - * The size is adjusted based on the current rotation of the display. - * </p> - * @hide - */ - public void getRealSize(Point outSize) { - try { - IWindowManager wm = getWindowManager(); - if (wm != null) { - wm.getRealDisplaySize(outSize); - } else { - // This is just for boot-strapping, initializing the - // system process before the window manager is up. - outSize.x = getRawWidth(); - outSize.y = getRawHeight(); - } - if (DEBUG_DISPLAY_SIZE) Slog.v( - TAG, "Returning real display size: " + outSize); - } catch (RemoteException e) { - Slog.w("Display", "Unable to get real display size", e); + synchronized (this) { + updateCachedAppSizeIfNeededLocked(); + return mCachedAppHeightCompat; } } /** - * Gets the raw width of the display, in pixels. - * <p> - * The size is adjusted based on the current rotation of the display. - * </p> - * @hide - */ - public int getRawWidth() { - int w = getRawWidthNative(); - if (DEBUG_DISPLAY_SIZE) Slog.v( - TAG, "Returning raw display width: " + w); - return w; - } - private native int getRawWidthNative(); - - /** - * Gets the raw height of the display, in pixels. - * <p> - * The size is adjusted based on the current rotation of the display. - * </p> - * @hide - */ - public int getRawHeight() { - int h = getRawHeightNative(); - if (DEBUG_DISPLAY_SIZE) Slog.v( - TAG, "Returning raw display height: " + h); - return h; - } - private native int getRawHeightNative(); - - /** * Returns the rotation of the screen from its "natural" orientation. * The returned value may be {@link Surface#ROTATION_0 Surface.ROTATION_0} * (no rotation), {@link Surface#ROTATION_90 Surface.ROTATION_90}, @@ -307,30 +358,43 @@ public class Display { * {@link Surface#ROTATION_90 Surface.ROTATION_90}. */ public int getRotation() { - return getOrientation(); + synchronized (this) { + updateDisplayInfoLocked(); + return mDisplayInfo.rotation; + } } - + /** * @deprecated use {@link #getRotation} * @return orientation of this display. */ - @Deprecated native public int getOrientation(); + @Deprecated + public int getOrientation() { + return getRotation(); + } /** - * Return the native pixel format of the display. The returned value - * may be one of the constants int {@link android.graphics.PixelFormat}. + * Gets the pixel format of the display. + * @return One of the constants defined in {@link android.graphics.PixelFormat}. + * + * @deprecated This method is no longer supported. + * The result is always {@link PixelFormat#RGBA_8888}. */ + @Deprecated public int getPixelFormat() { - return mPixelFormat; + return PixelFormat.RGBA_8888; } - + /** - * Return the refresh rate of this display in frames per second. + * Gets the refresh rate of this display in frames per second. */ public float getRefreshRate() { - return mRefreshRate; + synchronized (this) { + updateDisplayInfoLocked(); + return mDisplayInfo.refreshRate; + } } - + /** * Gets display metrics that describe the size and density of this display. * <p> @@ -346,117 +410,93 @@ public class Display { * @param outMetrics A {@link DisplayMetrics} object to receive the metrics. */ public void getMetrics(DisplayMetrics outMetrics) { - synchronized (mTmpPoint) { - getSizeInternal(mTmpPoint, false); - getMetricsWithSize(outMetrics, mTmpPoint.x, mTmpPoint.y); + synchronized (this) { + updateDisplayInfoLocked(); + mDisplayInfo.getAppMetrics(outMetrics, mCompatibilityInfo); } - - CompatibilityInfo ci = mCompatibilityInfo.getIfNeeded(); - if (ci != null) { - ci.applyToDisplayMetrics(outMetrics); - } - - if (DEBUG_DISPLAY_SIZE) Slog.v(TAG, "Returning DisplayMetrics: " - + outMetrics.widthPixels + "x" + outMetrics.heightPixels - + " " + outMetrics.density); } /** - * Gets display metrics based on the real size of this display. - * @hide + * Gets the real size of the display without subtracting any window decor or + * applying any compatibility scale factors. + * <p> + * The size is adjusted based on the current rotation of the display. + * </p><p> + * The real size may be smaller than the physical size of the screen when the + * window manager is emulating a smaller display (using adb shell am display-size). + * </p> + * + * @param outSize Set to the real size of the display. */ - public void getRealMetrics(DisplayMetrics outMetrics) { - synchronized (mTmpPoint) { - getRealSize(mTmpPoint); - getMetricsWithSize(outMetrics, mTmpPoint.x, mTmpPoint.y); + public void getRealSize(Point outSize) { + synchronized (this) { + updateDisplayInfoLocked(); + outSize.x = mDisplayInfo.logicalWidth; + outSize.y = mDisplayInfo.logicalHeight; } } /** - * If the display is mirrored to an external HDMI display, returns the - * width of that display. - * @hide - */ - public int getRawExternalWidth() { - return 1280; - } - - /** - * If the display is mirrored to an external HDMI display, returns the - * height of that display. - * @hide - */ - public int getRawExternalHeight() { - return 720; - } - - /** - * If the display is mirrored to an external HDMI display, returns the - * rotation of that display relative to its natural orientation. - * @hide - */ - public int getExternalRotation() { - return Surface.ROTATION_0; - } - - /** - * Gets display metrics based on an explicit assumed display size. - * @hide + * Gets display metrics based on the real size of this display. + * <p> + * The size is adjusted based on the current rotation of the display. + * </p><p> + * The real size may be smaller than the physical size of the screen when the + * window manager is emulating a smaller display (using adb shell am display-size). + * </p> + * + * @param outMetrics A {@link DisplayMetrics} object to receive the metrics. */ - public void getMetricsWithSize(DisplayMetrics outMetrics, - int width, int height) { - outMetrics.densityDpi = (int)((mDensity*DisplayMetrics.DENSITY_DEFAULT)+.5f); - - outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width; - outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height; - - outMetrics.density = outMetrics.noncompatDensity = mDensity; - outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density; - outMetrics.xdpi = outMetrics.noncompatXdpi = mDpiX; - outMetrics.ydpi = outMetrics.noncompatYdpi = mDpiY; + public void getRealMetrics(DisplayMetrics outMetrics) { + synchronized (this) { + updateDisplayInfoLocked(); + mDisplayInfo.getLogicalMetrics(outMetrics, null); + } } - static IWindowManager getWindowManager() { - synchronized (sStaticInit) { - if (sWindowManager == null) { - sWindowManager = IWindowManager.Stub.asInterface( - ServiceManager.getService("window")); + private void updateDisplayInfoLocked() { + // Note: The display manager caches display info objects on our behalf. + DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId); + if (newInfo == null) { + // Preserve the old mDisplayInfo after the display is removed. + if (mIsValid) { + mIsValid = false; + if (DEBUG) { + Log.d(TAG, "Logical display " + mDisplayId + " was removed."); + } + } + } else { + // Use the new display info. (It might be the same object if nothing changed.) + mDisplayInfo = newInfo; + if (!mIsValid) { + mIsValid = true; + if (DEBUG) { + Log.d(TAG, "Logical display " + mDisplayId + " was recreated."); + } } - return sWindowManager; } } - /* - * We use a class initializer to allow the native code to cache some - * field offsets. - */ - native private static void nativeClassInit(); - - private native void init(int display); - - private final CompatibilityInfoHolder mCompatibilityInfo; - private final int mDisplay; - // Following fields are initialized from native code - private int mPixelFormat; - private float mRefreshRate; - /*package*/ float mDensity; - /*package*/ float mDpiX; - /*package*/ float mDpiY; - - private final Point mTmpPoint = new Point(); - private final DisplayMetrics mTmpMetrics = new DisplayMetrics(); - private float mLastGetTime; - - private static final Object sStaticInit = new Object(); - private static boolean sInitialized = false; - private static IWindowManager sWindowManager; + private void updateCachedAppSizeIfNeededLocked() { + long now = SystemClock.uptimeMillis(); + if (now > mLastCachedAppSizeUpdate + CACHED_APP_SIZE_DURATION_MILLIS) { + updateDisplayInfoLocked(); + mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); + mCachedAppWidthCompat = mTempMetrics.widthPixels; + mCachedAppHeightCompat = mTempMetrics.heightPixels; + mLastCachedAppSizeUpdate = now; + } + } - /** - * Returns a display object which uses the metric's width/height instead. - * @hide - */ - public static Display createCompatibleDisplay(int displayId, CompatibilityInfoHolder compat) { - return new Display(displayId, compat); + // For debugging purposes + @Override + public String toString() { + synchronized (this) { + updateDisplayInfoLocked(); + mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo); + return "Display id " + mDisplayId + ": " + mDisplayInfo + + ", " + mTempMetrics + ", isValid=" + mIsValid; + } } } diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 6c2e540..a919ffc 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -66,7 +66,7 @@ public abstract class DisplayEventReceiver { @Override protected void finalize() throws Throwable { try { - dispose(); + dispose(true); } finally { super.finalize(); } @@ -76,9 +76,17 @@ public abstract class DisplayEventReceiver { * Disposes the receiver. */ public void dispose() { + dispose(false); + } + + private void dispose(boolean finalized) { if (mCloseGuard != null) { + if (finalized) { + mCloseGuard.warnIfOpen(); + } mCloseGuard.close(); } + if (mReceiverPtr != 0) { nativeDispose(mReceiverPtr); mReceiverPtr = 0; @@ -93,9 +101,23 @@ public abstract class DisplayEventReceiver { * * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()} * timebase. + * @param builtInDisplayId The surface flinger built-in display id such as + * {@link Surface#BUILT_IN_DISPLAY_ID_MAIN}. * @param frame The frame number. Increases by one for each vertical sync interval. */ - public void onVsync(long timestampNanos, int frame) { + public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { + } + + /** + * Called when a display hotplug event is received. + * + * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()} + * timebase. + * @param builtInDisplayId The surface flinger built-in display id such as + * {@link Surface#BUILT_IN_DISPLAY_ID_HDMI}. + * @param connected True if the display is connected, false if it disconnected. + */ + public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) { } /** @@ -113,7 +135,13 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") - private void dispatchVsync(long timestampNanos, int frame) { - onVsync(timestampNanos, frame); + private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { + onVsync(timestampNanos, builtInDisplayId, frame); + } + + // Called from native code. + @SuppressWarnings("unused") + private void dispatchHotplug(long timestampNanos, int builtInDisplayId, boolean connected) { + onHotplug(timestampNanos, builtInDisplayId, connected); } } diff --git a/core/java/android/view/DisplayInfo.aidl b/core/java/android/view/DisplayInfo.aidl new file mode 100644 index 0000000..e679208 --- /dev/null +++ b/core/java/android/view/DisplayInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +parcelable DisplayInfo; diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java new file mode 100644 index 0000000..c968ec5 --- /dev/null +++ b/core/java/android/view/DisplayInfo.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.content.res.CompatibilityInfo; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DisplayMetrics; + +import libcore.util.Objects; + +/** + * Describes the characteristics of a particular logical display. + * @hide + */ +public final class DisplayInfo implements Parcelable { + /** + * The surface flinger layer stack associated with this logical display. + */ + public int layerStack; + + /** + * Display flags. + */ + public int flags; + + /** + * The human-readable name of the display. + */ + public String name; + + /** + * The width of the portion of the display that is available to applications, in pixels. + * Represents the size of the display minus any system decorations. + */ + public int appWidth; + + /** + * The height of the portion of the display that is available to applications, in pixels. + * Represents the size of the display minus any system decorations. + */ + public int appHeight; + + /** + * The smallest value of {@link #appWidth} that an application is likely to encounter, + * in pixels, excepting cases where the width may be even smaller due to the presence + * of a soft keyboard, for example. + */ + public int smallestNominalAppWidth; + + /** + * The smallest value of {@link #appHeight} that an application is likely to encounter, + * in pixels, excepting cases where the height may be even smaller due to the presence + * of a soft keyboard, for example. + */ + public int smallestNominalAppHeight; + + /** + * The largest value of {@link #appWidth} that an application is likely to encounter, + * in pixels, excepting cases where the width may be even larger due to system decorations + * such as the status bar being hidden, for example. + */ + public int largestNominalAppWidth; + + /** + * The largest value of {@link #appHeight} that an application is likely to encounter, + * in pixels, excepting cases where the height may be even larger due to system decorations + * such as the status bar being hidden, for example. + */ + public int largestNominalAppHeight; + + /** + * The logical width of the display, in pixels. + * Represents the usable size of the display which may be smaller than the + * physical size when the system is emulating a smaller display. + */ + public int logicalWidth; + + /** + * The logical height of the display, in pixels. + * Represents the usable size of the display which may be smaller than the + * physical size when the system is emulating a smaller display. + */ + public int logicalHeight; + + /** + * The rotation of the display relative to its natural orientation. + * May be one of {@link android.view.Surface#ROTATION_0}, + * {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180}, + * {@link android.view.Surface#ROTATION_270}. + * <p> + * The value of this field is indeterminate if the logical display is presented on + * more than one physical display. + * </p> + */ + public int rotation; + + /** + * The refresh rate of this display in frames per second. + * <p> + * The value of this field is indeterminate if the logical display is presented on + * more than one physical display. + * </p> + */ + public float refreshRate; + + /** + * The logical display density which is the basis for density-independent + * pixels. + */ + public int logicalDensityDpi; + + /** + * The exact physical pixels per inch of the screen in the X dimension. + * <p> + * The value of this field is indeterminate if the logical display is presented on + * more than one physical display. + * </p> + */ + public float physicalXDpi; + + /** + * The exact physical pixels per inch of the screen in the Y dimension. + * <p> + * The value of this field is indeterminate if the logical display is presented on + * more than one physical display. + * </p> + */ + public float physicalYDpi; + + public static final Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() { + public DisplayInfo createFromParcel(Parcel source) { + return new DisplayInfo(source); + } + + public DisplayInfo[] newArray(int size) { + return new DisplayInfo[size]; + } + }; + + public DisplayInfo() { + } + + public DisplayInfo(DisplayInfo other) { + copyFrom(other); + } + + private DisplayInfo(Parcel source) { + readFromParcel(source); + } + + @Override + public boolean equals(Object o) { + return o instanceof DisplayInfo && equals((DisplayInfo)o); + } + + public boolean equals(DisplayInfo other) { + return other != null + && layerStack == other.layerStack + && Objects.equal(name, other.name) + && appWidth == other.appWidth + && appHeight == other.appHeight + && smallestNominalAppWidth == other.smallestNominalAppWidth + && smallestNominalAppHeight == other.smallestNominalAppHeight + && largestNominalAppWidth == other.largestNominalAppWidth + && largestNominalAppHeight == other.largestNominalAppHeight + && logicalWidth == other.logicalWidth + && logicalHeight == other.logicalHeight + && rotation == other.rotation + && refreshRate == other.refreshRate + && logicalDensityDpi == other.logicalDensityDpi + && physicalXDpi == other.physicalXDpi + && physicalYDpi == other.physicalYDpi; + } + + @Override + public int hashCode() { + return 0; // don't care + } + + public void copyFrom(DisplayInfo other) { + layerStack = other.layerStack; + flags = other.flags; + name = other.name; + appWidth = other.appWidth; + appHeight = other.appHeight; + smallestNominalAppWidth = other.smallestNominalAppWidth; + smallestNominalAppHeight = other.smallestNominalAppHeight; + largestNominalAppWidth = other.largestNominalAppWidth; + largestNominalAppHeight = other.largestNominalAppHeight; + logicalWidth = other.logicalWidth; + logicalHeight = other.logicalHeight; + rotation = other.rotation; + refreshRate = other.refreshRate; + logicalDensityDpi = other.logicalDensityDpi; + physicalXDpi = other.physicalXDpi; + physicalYDpi = other.physicalYDpi; + } + + public void readFromParcel(Parcel source) { + layerStack = source.readInt(); + flags = source.readInt(); + name = source.readString(); + appWidth = source.readInt(); + appHeight = source.readInt(); + smallestNominalAppWidth = source.readInt(); + smallestNominalAppHeight = source.readInt(); + largestNominalAppWidth = source.readInt(); + largestNominalAppHeight = source.readInt(); + logicalWidth = source.readInt(); + logicalHeight = source.readInt(); + rotation = source.readInt(); + refreshRate = source.readFloat(); + logicalDensityDpi = source.readInt(); + physicalXDpi = source.readFloat(); + physicalYDpi = source.readFloat(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(layerStack); + dest.writeInt(flags); + dest.writeString(name); + dest.writeInt(appWidth); + dest.writeInt(appHeight); + dest.writeInt(smallestNominalAppWidth); + dest.writeInt(smallestNominalAppHeight); + dest.writeInt(largestNominalAppWidth); + dest.writeInt(largestNominalAppHeight); + dest.writeInt(logicalWidth); + dest.writeInt(logicalHeight); + dest.writeInt(rotation); + dest.writeFloat(refreshRate); + dest.writeInt(logicalDensityDpi); + dest.writeFloat(physicalXDpi); + dest.writeFloat(physicalYDpi); + } + + @Override + public int describeContents() { + return 0; + } + + public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) { + getMetricsWithSize(outMetrics, cih, appWidth, appHeight); + } + + public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) { + getMetricsWithSize(outMetrics, cih, logicalWidth, logicalHeight); + } + + private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfoHolder cih, + int width, int height) { + outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi; + outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width; + outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height; + + outMetrics.density = outMetrics.noncompatDensity = + logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density; + outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi; + outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi; + + if (cih != null) { + CompatibilityInfo ci = cih.getIfNeeded(); + if (ci != null) { + ci.applyToDisplayMetrics(outMetrics); + } + } + } + + // For debugging purposes + @Override + public String toString() { + return "DisplayInfo{\"" + name + "\", app " + appWidth + " x " + appHeight + + ", real " + logicalWidth + " x " + logicalHeight + + ", largest app " + largestNominalAppWidth + " x " + largestNominalAppHeight + + ", smallest app " + smallestNominalAppWidth + " x " + smallestNominalAppHeight + + ", " + refreshRate + " fps" + + ", rotation " + rotation + + ", density " + logicalDensityDpi + + ", " + physicalXDpi + " x " + physicalYDpi + " dpi" + + ", layerStack " + layerStack + flagsToString(flags) + "}"; + } + + private static String flagsToString(int flags) { + StringBuilder result = new StringBuilder(); + if ((flags & Display.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT) != 0) { + result.append(", FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT"); + } + if ((flags & Display.FLAG_SUPPORTS_SECURE_VIDEO_BUFFERS) != 0) { + result.append(", FLAG_SUPPORTS_SECURE_VIDEO_BUFFERS"); + } + return result.toString(); + } +} diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index a42e156..5e34a36 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -332,4 +332,11 @@ public abstract class DisplayList { * @see View#offsetTopAndBottom(int) */ public abstract void offsetTopBottom(int offset); + + /** + * Reset native resources. This is called when cleaning up the state of DisplayLists + * during destruction of hardware resources, to ensure that we do not hold onto + * obsolete resources after related resources are gone. + */ + public abstract void reset(); } diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index 31a9f05..b2988ed 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -79,17 +79,9 @@ public class FocusFinder { } private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) { - if ((direction & View.FOCUS_ACCESSIBILITY) != View.FOCUS_ACCESSIBILITY) { - return findNextInputFocus(root, focused, focusedRect, direction); - } else { - return findNextAccessibilityFocus(root, focused, focusedRect, direction); - } - } - - private View findNextInputFocus(ViewGroup root, View focused, Rect focusedRect, int direction) { View next = null; if (focused != null) { - next = findNextUserSpecifiedInputFocus(root, focused, direction); + next = findNextUserSpecifiedFocus(root, focused, direction); } if (next != null) { return next; @@ -107,7 +99,7 @@ public class FocusFinder { return next; } - private View findNextUserSpecifiedInputFocus(ViewGroup root, View focused, int direction) { + private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) { // check for user specified next focus View userSetNextFocus = focused.findUserSetNextFocus(root, direction); if (userSetNextFocus != null && userSetNextFocus.isFocusable() @@ -120,7 +112,6 @@ public class FocusFinder { private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction, ArrayList<View> focusables) { - final int directionMasked = (direction & ~View.FOCUS_ACCESSIBILITY); if (focused != null) { if (focusedRect == null) { focusedRect = mFocusedRect; @@ -132,7 +123,7 @@ public class FocusFinder { if (focusedRect == null) { focusedRect = mFocusedRect; // make up a rect at top left or bottom right of root - switch (directionMasked) { + switch (direction) { case View.FOCUS_RIGHT: case View.FOCUS_DOWN: setFocusTopLeft(root, focusedRect); @@ -160,37 +151,23 @@ public class FocusFinder { } } - switch (directionMasked) { + switch (direction) { case View.FOCUS_FORWARD: case View.FOCUS_BACKWARD: - return findNextInputFocusInRelativeDirection(focusables, root, focused, focusedRect, - directionMasked); + return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect, + direction); case View.FOCUS_UP: case View.FOCUS_DOWN: case View.FOCUS_LEFT: case View.FOCUS_RIGHT: - return findNextInputFocusInAbsoluteDirection(focusables, root, focused, - focusedRect, directionMasked); + return findNextFocusInAbsoluteDirection(focusables, root, focused, + focusedRect, direction); default: - throw new IllegalArgumentException("Unknown direction: " + directionMasked); - } - } - - private View findNextAccessibilityFocus(ViewGroup root, View focused, - Rect focusedRect, int direction) { - ArrayList<View> focusables = mTempList; - try { - focusables.clear(); - root.addFocusables(focusables, direction, View.FOCUSABLES_ACCESSIBILITY); - View next = findNextFocus(root, focused, focusedRect, direction, - focusables); - return next; - } finally { - focusables.clear(); + throw new IllegalArgumentException("Unknown direction: " + direction); } } - private View findNextInputFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root, + private View findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root, View focused, Rect focusedRect, int direction) { try { // Note: This sort is stable. @@ -222,7 +199,7 @@ public class FocusFinder { focusedRect.set(rootLeft, rootTop, rootLeft, rootTop); } - View findNextInputFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused, + View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused, Rect focusedRect, int direction) { // initialize the best candidate to something impossible // (so the first plausible view will become the best choice) @@ -251,7 +228,7 @@ public class FocusFinder { if (focusable == focused || focusable == root) continue; // get focus bounds of other view in same coordinate system - focusable.getFocusRect(mOtherRect); + focusable.getFocusedRect(mOtherRect); root.offsetDescendantRectToMyCoords(focusable, mOtherRect); if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) { diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 055aee3..b64a06e 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -147,20 +147,36 @@ class GLES20Canvas extends HardwareCanvas { /////////////////////////////////////////////////////////////////////////// // Hardware layers /////////////////////////////////////////////////////////////////////////// - + + @Override + void pushLayerUpdate(HardwareLayer layer) { + nPushLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer); + } + + @Override + void clearLayerUpdates() { + nClearLayerUpdates(mRenderer); + } + static native int nCreateTextureLayer(boolean opaque, int[] layerInfo); static native int nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo); - static native void nResizeLayer(int layerId, int width, int height, int[] layerInfo); + static native boolean nResizeLayer(int layerId, int width, int height, int[] layerInfo); + static native void nSetOpaqueLayer(int layerId, boolean isOpaque); + static native void nSetLayerPaint(int layerId, int nativePaint); + static native void nSetLayerColorFilter(int layerId, int nativeColorFilter); static native void nUpdateTextureLayer(int layerId, int width, int height, boolean opaque, SurfaceTexture surface); + static native void nClearLayerTexture(int layerId); static native void nSetTextureLayerTransform(int layerId, int matrix); static native void nDestroyLayer(int layerId); static native void nDestroyLayerDeferred(int layerId); - static native void nFlushLayer(int layerId); static native void nUpdateRenderLayer(int layerId, int renderer, int displayList, int left, int top, int right, int bottom); static native boolean nCopyLayer(int layerId, int bitmap); + private static native void nClearLayerUpdates(int renderer); + private static native void nPushLayerUpdate(int renderer, int layer); + /////////////////////////////////////////////////////////////////////////// // Canvas management /////////////////////////////////////////////////////////////////////////// @@ -394,13 +410,8 @@ class GLES20Canvas extends HardwareCanvas { void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) { final GLES20Layer glLayer = (GLES20Layer) layer; - int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; - try { - final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint); - } finally { - if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); - } + final int nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint); } private static native void nDrawLayer(int renderer, int layer, float x, float y, int paint); diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index 2d2e8e4..e9bd0c4 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -25,9 +25,12 @@ import java.util.ArrayList; * An implementation of display list for OpenGL ES 2.0. */ class GLES20DisplayList extends DisplayList { - // These lists ensure that any Bitmaps recorded by a DisplayList are kept alive as long - // as the DisplayList is alive. The Bitmaps are populated by the GLES20RecordingCanvas. + // These lists ensure that any Bitmaps and DisplayLists recorded by a DisplayList are kept + // alive as long as the DisplayList is alive. The Bitmap and DisplayList lists + // are populated by the GLES20RecordingCanvas during appropriate drawing calls and are + // cleared at the start of a new drawing frame or when the view is detached from the window. final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(5); + final ArrayList<DisplayList> mChildDisplayLists = new ArrayList<DisplayList>(); private GLES20RecordingCanvas mCanvas; private boolean mValid; @@ -79,6 +82,14 @@ class GLES20DisplayList extends DisplayList { public void clear() { if (!mValid) { mBitmaps.clear(); + mChildDisplayLists.clear(); + } + } + + @Override + public void reset() { + if (hasNativeDisplayList()) { + nReset(mFinalizer.mNativeDisplayList); } } @@ -290,6 +301,7 @@ class GLES20DisplayList extends DisplayList { } } + private static native void nReset(int displayList); private static native void nOffsetTopBottom(int displayList, int offset); private static native void nOffsetLeftRight(int displayList, int offset); private static native void nSetLeftTopRightBottom(int displayList, int left, int top, diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java index 4f25792..812fb97 100644 --- a/core/java/android/view/GLES20Layer.java +++ b/core/java/android/view/GLES20Layer.java @@ -18,6 +18,7 @@ package android.view; import android.graphics.Bitmap; +import android.graphics.Paint; /** * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. @@ -43,13 +44,17 @@ abstract class GLES20Layer extends HardwareLayer { } @Override - boolean copyInto(Bitmap bitmap) { - return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap); + void setLayerPaint(Paint paint) { + if (paint != null) { + GLES20Canvas.nSetLayerPaint(mLayer, paint.mNativePaint); + GLES20Canvas.nSetLayerColorFilter(mLayer, paint.getColorFilter() != null ? + paint.getColorFilter().nativeColorFilter : 0); + } } - + @Override - void update(int width, int height, boolean isOpaque) { - super.update(width, height, isOpaque); + boolean copyInto(Bitmap bitmap) { + return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap); } @Override @@ -60,12 +65,10 @@ abstract class GLES20Layer extends HardwareLayer { } mLayer = 0; } - + @Override - void flush() { - if (mLayer != 0) { - GLES20Canvas.nFlushLayer(mLayer); - } + void clearStorage() { + if (mLayer != 0) GLES20Canvas.nClearLayerTexture(mLayer); } static class Finalizer { diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index c9ba65f..ba8be6c 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -76,6 +76,7 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor void start() { mDisplayList.mBitmaps.clear(); + mDisplayList.mChildDisplayLists.clear(); } int end(int nativeDisplayList) { @@ -156,6 +157,13 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor } @Override + public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) { + int status = super.drawDisplayList(displayList, dirty, flags); + mDisplayList.mChildDisplayLists.add(displayList); + return status; + } + + @Override public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { super.drawLine(startX, startY, stopX, stopY, paint); recordShaderBitmap(paint); diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java index c727a36..44d4719 100644 --- a/core/java/android/view/GLES20RenderLayer.java +++ b/core/java/android/view/GLES20RenderLayer.java @@ -54,8 +54,8 @@ class GLES20RenderLayer extends GLES20Layer { } @Override - void resize(int width, int height) { - if (!isValid() || width <= 0 || height <= 0) return; + boolean resize(int width, int height) { + if (!isValid() || width <= 0 || height <= 0) return false; mWidth = width; mHeight = height; @@ -63,11 +63,23 @@ class GLES20RenderLayer extends GLES20Layer { if (width != mLayerWidth || height != mLayerHeight) { int[] layerInfo = new int[2]; - GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo); - - mLayerWidth = layerInfo[0]; - mLayerHeight = layerInfo[1]; + if (GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo)) { + mLayerWidth = layerInfo[0]; + mLayerHeight = layerInfo[1]; + } else { + // Failure: not enough GPU resources for requested size + mLayer = 0; + mLayerWidth = 0; + mLayerHeight = 0; + } } + return isValid(); + } + + @Override + void setOpaque(boolean isOpaque) { + mOpaque = isOpaque; + GLES20Canvas.nSetOpaqueLayer(mLayer, isOpaque); } @Override @@ -98,7 +110,7 @@ class GLES20RenderLayer extends GLES20Layer { } @Override - void redraw(DisplayList displayList, Rect dirtyRect) { + void redrawLater(DisplayList displayList, Rect dirtyRect) { GLES20Canvas.nUpdateRenderLayer(mLayer, mCanvas.getRenderer(), ((GLES20DisplayList) displayList).getNativeDisplayList(), dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java index 16a13cf..e863e49 100644 --- a/core/java/android/view/GLES20TextureLayer.java +++ b/core/java/android/view/GLES20TextureLayer.java @@ -39,13 +39,7 @@ class GLES20TextureLayer extends GLES20Layer { mFinalizer = new Finalizer(mLayer); } else { mFinalizer = null; - } - } - - GLES20TextureLayer(SurfaceTexture surface, boolean isOpaque) { - this(isOpaque); - mSurface = surface; - mSurface.attachToGLContext(mTexture); + } } @Override @@ -54,7 +48,8 @@ class GLES20TextureLayer extends GLES20Layer { } @Override - void resize(int width, int height) { + boolean resize(int width, int height) { + return isValid(); } @Override @@ -93,11 +88,16 @@ class GLES20TextureLayer extends GLES20Layer { } @Override + void setOpaque(boolean isOpaque) { + throw new UnsupportedOperationException("Use update(int, int, boolean) instead"); + } + + @Override void setTransform(Matrix matrix) { GLES20Canvas.nSetTextureLayerTransform(mLayer, matrix.native_instance); } @Override - void redraw(DisplayList displayList, Rect dirtyRect) { + void redrawLater(DisplayList displayList, Rect dirtyRect) { } } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 4bbdd4e..9ddb32e 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -482,6 +482,27 @@ public class GestureDetector { case MotionEvent.ACTION_POINTER_UP: mDownFocusX = mLastFocusX = focusX; mDownFocusY = mLastFocusY = focusY; + + // Check the dot product of current velocities. + // If the pointer that left was opposing another velocity vector, clear. + mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); + final int upIndex = ev.getActionIndex(); + final int id1 = ev.getPointerId(upIndex); + final float x1 = mVelocityTracker.getXVelocity(id1); + final float y1 = mVelocityTracker.getYVelocity(id1); + for (int i = 0; i < count; i++) { + if (i == upIndex) continue; + + final int id2 = ev.getPointerId(i); + final float x = x1 * mVelocityTracker.getXVelocity(id2); + final float y = y1 * mVelocityTracker.getYVelocity(id2); + + final float dot = x + y; + if (dot < 0) { + mVelocityTracker.clear(); + break; + } + } break; case MotionEvent.ACTION_DOWN: diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java index 4547aa6..9a89fa5 100644 --- a/core/java/android/view/Gravity.java +++ b/core/java/android/view/Gravity.java @@ -153,9 +153,8 @@ public class Gravity * container. * @param layoutDirection The layout direction. * - * @see {@link View#LAYOUT_DIRECTION_LTR} - * @see {@link View#LAYOUT_DIRECTION_RTL} - * @hide + * @see View#LAYOUT_DIRECTION_LTR + * @see View#LAYOUT_DIRECTION_RTL */ public static void apply(int gravity, int w, int h, Rect container, Rect outRect, int layoutDirection) { @@ -291,9 +290,8 @@ public class Gravity * container. * @param layoutDirection The layout direction. * - * @see {@link View#LAYOUT_DIRECTION_LTR} - * @see {@link View#LAYOUT_DIRECTION_RTL} - * @hide + * @see View#LAYOUT_DIRECTION_LTR + * @see View#LAYOUT_DIRECTION_RTL */ public static void apply(int gravity, int w, int h, Rect container, int xAdj, int yAdj, Rect outRect, int layoutDirection) { @@ -372,9 +370,8 @@ public class Gravity * modified if needed to fit in the display. * @param layoutDirection The layout direction. * - * @see {@link View#LAYOUT_DIRECTION_LTR} - * @see {@link View#LAYOUT_DIRECTION_RTL} - * @hide + * @see View#LAYOUT_DIRECTION_LTR + * @see View#LAYOUT_DIRECTION_RTL */ public static void applyDisplay(int gravity, Rect display, Rect inoutObj, int layoutDirection) { int absGravity = getAbsoluteGravity(gravity, layoutDirection); @@ -411,7 +408,6 @@ public class Gravity * @param gravity The gravity to convert to absolute (horizontal) values. * @param layoutDirection The layout direction. * @return gravity converted to absolute (horizontal) values. - * @hide */ public static int getAbsoluteGravity(int gravity, int layoutDirection) { int result = gravity; diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 777552a..eeae3ed 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -132,4 +132,20 @@ public abstract class HardwareCanvas extends Canvas { * @see #detachFunctor(int) */ abstract void attachFunctor(int functor); + + /** + * Indicates that the specified layer must be updated as soon as possible. + * + * @param layer The layer to update + * + * @see #clearLayerUpdates() + */ + abstract void pushLayerUpdate(HardwareLayer layer); + + /** + * Removes all enqueued layer updates. + * + * @see #pushLayerUpdate(HardwareLayer) + */ + abstract void clearLayerUpdates(); } diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index e73f7bf..d3bc35a 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -19,6 +19,7 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.Paint; import android.graphics.Rect; /** @@ -62,6 +63,14 @@ abstract class HardwareLayer { } /** + * Update the paint used when drawing this layer. + * + * @param paint The paint used when the layer is drawn into the destination canvas. + * @see View#setLayerPaint(android.graphics.Paint) + */ + void setLayerPaint(Paint paint) {} + + /** * Returns the minimum width of the layer. * * @return The minimum desired width of the hardware layer @@ -107,6 +116,13 @@ abstract class HardwareLayer { } /** + * Sets whether or not this layer should be considered opaque. + * + * @param isOpaque True if the layer is opaque, false otherwise + */ + abstract void setOpaque(boolean isOpaque); + + /** * Indicates whether this layer can be rendered. * * @return True if the layer can be rendered into, false otherwise @@ -119,8 +135,9 @@ abstract class HardwareLayer { * * @param width The new desired minimum width for this layer * @param height The new desired minimum height for this layer + * @return True if the resulting layer is valid, false otherwise */ - abstract void resize(int width, int height); + abstract boolean resize(int width, int height); /** * Returns a hardware canvas that can be used to render onto @@ -136,11 +153,6 @@ abstract class HardwareLayer { abstract void destroy(); /** - * Flush the render queue associated with this layer. - */ - abstract void flush(); - - /** * This must be invoked before drawing onto this layer. * @param currentCanvas */ @@ -191,5 +203,10 @@ abstract class HardwareLayer { * execute in this layer * @param dirtyRect The dirty region of the layer that needs to be redrawn */ - abstract void redraw(DisplayList displayList, Rect dirtyRect); + abstract void redrawLater(DisplayList displayList, Rect dirtyRect); + + /** + * Indicates that this layer has lost its underlying storage. + */ + abstract void clearStorage(); } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index dab48b1..99987bf 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -21,6 +21,7 @@ import android.content.ComponentCallbacks2; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.opengl.EGL14; import android.opengl.GLUtils; import android.opengl.ManagedEGLContext; import android.os.Handler; @@ -135,7 +136,30 @@ public abstract class HardwareRenderer { * @hide */ public static final String DEBUG_DIRTY_REGIONS_PROPERTY = "debug.hwui.show_dirty_regions"; - + + /** + * Turn on to flash hardware layers when they update. + * + * Possible values: + * "true", to enable hardware layers updates debugging + * "false", to disable hardware layers updates debugging + * + * @hide + */ + public static final String DEBUG_SHOW_LAYERS_UPDATES_PROPERTY = + "debug.hwui.show_layers_updates"; + + /** + * Turn on to show overdraw level. + * + * Possible values: + * "true", to enable overdraw debugging + * "false", to disable overdraw debugging + * + * @hide + */ + public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw"; + /** * A process can set this flag to false to prevent the use of hardware * rendering. @@ -197,18 +221,18 @@ public abstract class HardwareRenderer { /** * Initializes the hardware renderer for the specified surface. * - * @param holder The holder for the surface to hardware accelerate. + * @param surface The surface to hardware accelerate * * @return True if the initialization was successful, false otherwise. */ - abstract boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException; + abstract boolean initialize(Surface surface) throws Surface.OutOfResourcesException; /** * Updates the hardware renderer for the specified surface. - * - * @param holder The holder for the surface to hardware accelerate + * + * @param surface The surface to hardware accelerate */ - abstract void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException; + abstract void updateSurface(Surface surface) throws Surface.OutOfResourcesException; /** * Destroys the layers used by the specified view hierarchy. @@ -228,10 +252,10 @@ public abstract class HardwareRenderer { /** * This method should be invoked whenever the current hardware renderer * context should be reset. - * - * @param holder The holder for the surface to hardware accelerate + * + * @param surface The surface to hardware accelerate */ - abstract void invalidate(SurfaceHolder holder); + abstract void invalidate(Surface surface); /** * This method should be invoked to ensure the hardware renderer is in @@ -358,6 +382,14 @@ public abstract class HardwareRenderer { private static native void nDisableVsync(); /** + * Indicates that the specified hardware layer needs to be updated + * as soon as possible. + * + * @param layer The hardware layer that needs an update + */ + abstract void pushLayerUpdate(HardwareLayer layer); + + /** * Interface used to receive callbacks whenever a view is drawn by * a hardware renderer instance. */ @@ -474,14 +506,14 @@ public abstract class HardwareRenderer { * * @param width The width of the drawing surface. * @param height The height of the drawing surface. - * @param holder The target surface + * @param surface The surface to hardware accelerate */ - void initializeIfNeeded(int width, int height, SurfaceHolder holder) + void initializeIfNeeded(int width, int height, Surface surface) throws Surface.OutOfResourcesException { if (isRequested()) { // We lost the gl context, so recreate it. if (!isEnabled()) { - if (initialize(holder)) { + if (initialize(surface)) { setup(width, height); } } @@ -577,12 +609,6 @@ public abstract class HardwareRenderer { @SuppressWarnings({"deprecation"}) static abstract class GlRenderer extends HardwareRenderer { - // These values are not exposed in our EGL APIs - static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - static final int EGL_OPENGL_ES2_BIT = 4; - static final int EGL_SURFACE_TYPE = 0x3033; - static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400; - static final int SURFACE_STATE_ERROR = 0; static final int SURFACE_STATE_SUCCESS = 1; static final int SURFACE_STATE_UPDATED = 2; @@ -629,6 +655,7 @@ public abstract class HardwareRenderer { int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; final boolean mDebugDirtyRegions; + final boolean mShowOverdraw; final int mGlVersion; final boolean mTranslucent; @@ -678,6 +705,9 @@ public abstract class HardwareRenderer { if (mDebugDirtyRegions) { Log.d(LOG_TAG, "Debugging dirty regions"); } + + mShowOverdraw = SystemProperties.getBoolean( + HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false); } @Override @@ -721,13 +751,17 @@ public abstract class HardwareRenderer { */ void checkEglErrors() { if (isEnabled()) { - int error = sEgl.eglGetError(); - if (error != EGL_SUCCESS) { - // something bad has happened revert to - // normal rendering. - Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error)); - fallback(error != EGL11.EGL_CONTEXT_LOST); - } + checkEglErrorsForced(); + } + } + + private void checkEglErrorsForced() { + int error = sEgl.eglGetError(); + if (error != EGL_SUCCESS) { + // something bad has happened revert to + // normal rendering. + Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error)); + fallback(error != EGL11.EGL_CONTEXT_LOST); } } @@ -742,10 +776,10 @@ public abstract class HardwareRenderer { } @Override - boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException { + boolean initialize(Surface surface) throws Surface.OutOfResourcesException { if (isRequested() && !isEnabled()) { initializeEgl(); - mGl = createEglSurface(holder); + mGl = createEglSurface(surface); mDestroyed = false; if (mGl != null) { @@ -771,9 +805,9 @@ public abstract class HardwareRenderer { } @Override - void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException { + void updateSurface(Surface surface) throws Surface.OutOfResourcesException { if (isRequested() && isEnabled()) { - createEglSurface(holder); + createEglSurface(surface); } } @@ -800,7 +834,9 @@ public abstract class HardwareRenderer { throw new RuntimeException("eglInitialize failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } - + + checkEglErrorsForced(); + sEglConfig = chooseEglConfig(); if (sEglConfig == null) { // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without @@ -888,7 +924,7 @@ public abstract class HardwareRenderer { Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0])); } - GL createEglSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException { + GL createEglSurface(Surface surface) throws Surface.OutOfResourcesException { // Check preconditions. if (sEgl == null) { throw new RuntimeException("egl not initialized"); @@ -908,23 +944,12 @@ public abstract class HardwareRenderer { destroySurface(); // Create an EGL surface we can render into. - if (!createSurface(holder)) { + if (!createSurface(surface)) { return null; } - /* - * Before we can issue GL commands, we need to make sure - * the context is current and bound to a surface. - */ - if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - throw new Surface.OutOfResourcesException("eglMakeCurrent failed " - + GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - initCaches(); - enableDirtyRegions(); - return mEglContext.getGL(); } @@ -949,10 +974,17 @@ public abstract class HardwareRenderer { abstract void initCaches(); EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { - int[] attribs = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; - - return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, - mGlVersion != 0 ? attribs : null); + int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; + + EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, + mGlVersion != 0 ? attribs : null); + if (context == null || context == EGL_NO_CONTEXT) { + //noinspection ConstantConditions + throw new IllegalStateException( + "Could not create an EGL context. eglCreateContext failed with error: " + + GLUtils.getEGLErrorString(sEgl.eglGetError())); + } + return context; } @Override @@ -982,7 +1014,7 @@ public abstract class HardwareRenderer { } @Override - void invalidate(SurfaceHolder holder) { + void invalidate(Surface surface) { // Cancels any existing buffer to ensure we'll get a buffer // of the right size before we call eglSwapBuffers sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); @@ -993,8 +1025,8 @@ public abstract class HardwareRenderer { setEnabled(false); } - if (holder.getSurface().isValid()) { - if (!createSurface(holder)) { + if (surface.isValid()) { + if (!createSurface(surface)) { return; } @@ -1006,8 +1038,8 @@ public abstract class HardwareRenderer { } } - private boolean createSurface(SurfaceHolder holder) { - mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null); + private boolean createSurface(Surface surface) { + mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null); if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { int error = sEgl.eglGetError(); @@ -1018,6 +1050,14 @@ public abstract class HardwareRenderer { throw new RuntimeException("createWindowSurface failed " + GLUtils.getEGLErrorString(error)); } + + if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + throw new IllegalStateException("eglMakeCurrent failed " + + GLUtils.getEGLErrorString(sEgl.eglGetError())); + } + + enableDirtyRegions(); + return true; } @@ -1089,7 +1129,7 @@ public abstract class HardwareRenderer { attachInfo.mIgnoreDirtyState = true; attachInfo.mDrawingTime = SystemClock.uptimeMillis(); - view.mPrivateFlags |= View.DRAWN; + view.mPrivateFlags |= View.PFLAG_DRAWN; final int surfaceState = checkCurrent(); if (surfaceState != SURFACE_STATE_ERROR) { @@ -1118,14 +1158,13 @@ public abstract class HardwareRenderer { } } - int status = onPreDraw(dirty); - int saveCount = canvas.save(); - callbacks.onHardwarePreDraw(canvas); + int saveCount = 0; + int status = DisplayList.STATUS_DONE; try { - view.mRecreateDisplayList = - (view.mPrivateFlags & View.INVALIDATED) == View.INVALIDATED; - view.mPrivateFlags &= ~View.INVALIDATED; + view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) + == View.PFLAG_INVALIDATED; + view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; long getDisplayListStartTime = 0; if (mProfileEnabled) { @@ -1137,8 +1176,9 @@ public abstract class HardwareRenderer { getDisplayListStartTime = System.nanoTime(); } - DisplayList displayList; + canvas.clearLayerUpdates(); + DisplayList displayList; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); try { displayList = view.getDisplayList(); @@ -1146,6 +1186,10 @@ public abstract class HardwareRenderer { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + status = onPreDraw(dirty); + saveCount = canvas.save(); + callbacks.onHardwarePreDraw(canvas); + if (mProfileEnabled) { long now = System.nanoTime(); float total = (now - getDisplayListStartTime) * 0.000001f; @@ -1378,15 +1422,16 @@ public abstract class HardwareRenderer { @Override int[] getConfig(boolean dirtyRegions) { return new int[] { - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, - EGL_STENCIL_SIZE, GLES20Canvas.getStencilSize(), + // TODO: Find a better way to choose the stencil size + EGL_STENCIL_SIZE, mShowOverdraw ? GLES20Canvas.getStencilSize() : 0, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | - (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0), + (dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0), EGL_NONE }; } @@ -1431,6 +1476,11 @@ public abstract class HardwareRenderer { } @Override + void pushLayerUpdate(HardwareLayer layer) { + mGlCanvas.pushLayerUpdate(layer); + } + + @Override public DisplayList createDisplayList(String name) { return new GLES20DisplayList(name); } @@ -1458,6 +1508,9 @@ public abstract class HardwareRenderer { @Override void destroyLayers(View view) { if (view != null && isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) { + if (mCanvas != null) { + mCanvas.clearLayerUpdates(); + } destroyHardwareLayer(view); GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); } @@ -1506,6 +1559,9 @@ public abstract class HardwareRenderer { safelyRun(new Runnable() { @Override public void run() { + if (mCanvas != null) { + mCanvas.clearLayerUpdates(); + } destroyResources(view); GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); } diff --git a/core/java/android/view/IDisplayContentChangeListener.aidl b/core/java/android/view/IDisplayContentChangeListener.aidl new file mode 100644 index 0000000..8f23ff6 --- /dev/null +++ b/core/java/android/view/IDisplayContentChangeListener.aidl @@ -0,0 +1,32 @@ +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.view; + +import android.os.IBinder; +import android.view.WindowInfo; +import android.graphics.Rect; + +/** + * Interface for observing content changes on a display. + * + * {@hide} + */ +oneway interface IDisplayContentChangeListener { + void onWindowTransition(int displayId, int transition, in WindowInfo info); + void onRectangleOnScreenRequested(int displayId, in Rect rectangle, boolean immediate); + void onRotationChanged(int rotation); +} diff --git a/core/java/android/view/IInputFilter.aidl b/core/java/android/view/IInputFilter.aidl new file mode 100644 index 0000000..fead5f6 --- /dev/null +++ b/core/java/android/view/IInputFilter.aidl @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.view.IInputFilterHost; +import android.view.InputEvent; + +/** + * Interface for implementing an filter which observes and + * potentially transforms the input event stream in the system. + * + * @hide + */ +oneway interface IInputFilter { + void install(IInputFilterHost host); + void uninstall(); + void filterInputEvent(in InputEvent event, int policyFlags); +} diff --git a/core/java/android/view/IInputFilterHost.aidl b/core/java/android/view/IInputFilterHost.aidl new file mode 100644 index 0000000..93b8239 --- /dev/null +++ b/core/java/android/view/IInputFilterHost.aidl @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.view.InputEvent; + +/** + * Interface for calls from an input filter to its host. + * + * @hide + */ +oneway interface IInputFilterHost { + void sendInputEvent(in InputEvent event, int policyFlags); +} diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index b4caad3..15bd46c 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -45,8 +45,9 @@ oneway interface IWindow { */ void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor); - void resized(int w, int h, in Rect contentInsets, + void resized(in Rect frame, in Rect contentInsets, in Rect visibleInsets, boolean reportDraw, in Configuration newConfig); + void moved(int newX, int newY); void dispatchAppVisibility(boolean visible); void dispatchGetNewSurface(); void dispatchScreenState(boolean on); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 4d4eec7..a64cbf7 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -23,8 +23,10 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Point; +import android.graphics.Rect; import android.os.IRemoteCallback; import android.view.IApplicationToken; +import android.view.IDisplayContentChangeListener; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindowSession; @@ -33,6 +35,8 @@ import android.view.InputEvent; import android.view.MotionEvent; import android.view.InputChannel; import android.view.InputDevice; +import android.view.IInputFilter; +import android.view.WindowInfo; /** * System private interface to the window manager. @@ -54,14 +58,11 @@ interface IWindowManager IWindowSession openSession(in IInputMethodClient client, in IInputContext inputContext); boolean inputMethodClientHasFocus(IInputMethodClient client); - - void getDisplaySize(out Point size); - void getRealDisplaySize(out Point size); - int getMaximumSizeDimension(); - void getCurrentSizeRange(out Point smallestSize, out Point largestSize); - void setForcedDisplaySize(int longDimen, int shortDimen); - void clearForcedDisplaySize(); + void setForcedDisplaySize(int displayId, int width, int height); + void clearForcedDisplaySize(int displayId); + void setForcedDisplayDensity(int displayId, int density); + void clearForcedDisplayDensity(int displayId); // Is the device configured to have a full system bar for larger screens? boolean hasSystemNavBar(); @@ -85,7 +86,7 @@ interface IWindowManager void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth, int startHeight); void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY, - IRemoteCallback startedCallback, boolean delayed); + IRemoteCallback startedCallback, boolean scaleUp); void executeAppTransition(); void setAppStartingWindow(IBinder token, String pkg, int theme, in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, @@ -106,6 +107,9 @@ interface IWindowManager IBinder freezeThisOneIfNeeded); void setNewConfiguration(in Configuration config); + void startFreezingScreen(int exitAnim, int enterAnim); + void stopFreezingScreen(); + // these require DISABLE_KEYGUARD permission void disableKeyguard(IBinder token, String tag); void reenableKeyguard(IBinder token); @@ -187,7 +191,7 @@ interface IWindowManager /** * Create a screenshot of the applications currently displayed. */ - Bitmap screenshotApplications(IBinder appToken, int maxWidth, int maxHeight); + Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight); /** * Called by the status bar to notify Views of changes to System UI visiblity. @@ -208,4 +212,44 @@ interface IWindowManager * Lock the device immediately. */ void lockNow(); + + /** + * Gets the token for the focused window. + */ + IBinder getFocusedWindowToken(); + + /** + * Gets the compatibility scale of e window given its token. + */ + float getWindowCompatibilityScale(IBinder windowToken); + + /** + * Sets an input filter for manipulating the input event stream. + */ + void setInputFilter(in IInputFilter filter); + + /** + * Sets the scale and offset for implementing accessibility magnification. + */ + void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY); + + /** + * Adds a listener for display content changes. + */ + void addDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener); + + /** + * Removes a listener for display content changes. + */ + void removeDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener); + + /** + * Gets the info for a window given its token. + */ + WindowInfo getWindowInfo(IBinder token); + + /** + * Gets the infos for all visible windows. + */ + void getVisibleWindowsForDisplay(int displayId, out List<WindowInfo> outInfos); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index d4a03ce..ff9dcce 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -37,8 +37,13 @@ interface IWindowSession { int add(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, out Rect outContentInsets, out InputChannel outInputChannel); + int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs, + in int viewVisibility, in int layerStackId, out Rect outContentInsets, + out InputChannel outInputChannel); int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, out Rect outContentInsets); + int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, + in int viewVisibility, in int layerStackId, out Rect outContentInsets); void remove(IWindow window); /** @@ -54,8 +59,8 @@ interface IWindowSession { * @param requestedWidth The width the window wants to be. * @param requestedHeight The height the window wants to be. * @param viewVisibility Window root view's visibility. - * @param flags Request flags: {@link WindowManagerImpl#RELAYOUT_INSETS_PENDING}, - * {@link WindowManagerImpl#RELAYOUT_DEFER_SURFACE_DESTROY}. + * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}, + * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}. * @param outFrame Rect in which is placed the new position/size on * screen. * @param outContentInsets Rect in which is placed the offsets from @@ -74,8 +79,8 @@ interface IWindowSession { * was last displayed. * @param outSurface Object in which is placed the new display surface. * - * @return int Result flags: {@link WindowManagerImpl#RELAYOUT_SHOW_FOCUS}, - * {@link WindowManagerImpl#RELAYOUT_FIRST_TIME}. + * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS}, + * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}. */ int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, @@ -172,4 +177,12 @@ interface IWindowSession { int z, in Bundle extras, boolean sync); void wallpaperCommandComplete(IBinder window, in Bundle result); + + void setUniverseTransform(IBinder window, float alpha, float offx, float offy, + float dsdx, float dtdx, float dsdy, float dtdy); + + /** + * Notifies that a rectangle on the screen has been requested. + */ + void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate); } diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java index fafe416..5dda934 100644 --- a/core/java/android/view/InputEventConsistencyVerifier.java +++ b/core/java/android/view/InputEventConsistencyVerifier.java @@ -322,7 +322,7 @@ public final class InputEventConsistencyVerifier { final int action = event.getAction(); final boolean newStream = action == MotionEvent.ACTION_DOWN - || action == MotionEvent.ACTION_CANCEL; + || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE; if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) { mTouchEventStreamIsTainted = false; mTouchEventStreamUnhandled = false; diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java index 9c56782..117c101 100644 --- a/core/java/android/view/InputEventReceiver.java +++ b/core/java/android/view/InputEventReceiver.java @@ -73,7 +73,7 @@ public abstract class InputEventReceiver { @Override protected void finalize() throws Throwable { try { - dispose(); + dispose(true); } finally { super.finalize(); } @@ -83,9 +83,17 @@ public abstract class InputEventReceiver { * Disposes the receiver. */ public void dispose() { + dispose(false); + } + + private void dispose(boolean finalized) { if (mCloseGuard != null) { + if (finalized) { + mCloseGuard.warnIfOpen(); + } mCloseGuard.close(); } + if (mReceiverPtr != 0) { nativeDispose(mReceiverPtr); mReceiverPtr = 0; diff --git a/core/java/android/view/InputFilter.java b/core/java/android/view/InputFilter.java new file mode 100644 index 0000000..c25b87b --- /dev/null +++ b/core/java/android/view/InputFilter.java @@ -0,0 +1,261 @@ +/* + * 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.view; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.view.IInputFilter; +import android.view.InputEvent; +import android.view.InputEventConsistencyVerifier; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Filters input events before they are dispatched to the system. + * <p> + * At most one input filter can be installed by calling + * {@link WindowManagerService#setInputFilter}. When an input filter is installed, the + * system's behavior changes as follows: + * <ul> + * <li>Input events are first delivered to the {@link WindowManagerPolicy} + * interception methods before queuing as usual. This critical step takes care of managing + * the power state of the device and handling wake keys.</li> + * <li>Input events are then asynchronously delivered to the input filter's + * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to + * applications as usual. The input filter only receives input events that were + * generated by input device; the input filter will not receive input events that were + * injected into the system by other means, such as by instrumentation.</li> + * <li>The input filter processes and optionally transforms the stream of events. For example, + * it may transform a sequence of motion events representing an accessibility gesture into + * a different sequence of motion events, key presses or other system-level interactions. + * The input filter can send events to be dispatched by calling + * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the + * input event.</li> + * </ul> + * </p> + * <h3>The importance of input event consistency</h3> + * <p> + * The input filter mechanism is very low-level. At a minimum, it needs to ensure that it + * sends an internally consistent stream of input events to the dispatcher. There are + * very important invariants to be maintained. + * </p><p> + * For example, if a key down is sent, a corresponding key up should also be sent eventually. + * Likewise, for touch events, each pointer must individually go down with + * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then + * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP} + * and the sequence of pointer ids used must be consistent throughout the gesture. + * </p><p> + * Sometimes a filter may wish to cancel a previously dispatched key or motion. It should + * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly. + * </p><p> + * The input filter must take into account the fact that the input events coming from different + * devices or even different sources all consist of distinct streams of input. + * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify + * the source of the event and its semantics. There are be multiple sources of keys, + * touches and other input: they must be kept separate. + * </p> + * <h3>Policy flags</h3> + * <p> + * Input events received from the dispatcher and sent to the dispatcher have policy flags + * associated with them. Policy flags control some functions of the dispatcher. + * </p><p> + * The early policy interception decides whether an input event should be delivered + * to applications or dropped. The policy indicates its decision by setting the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may + * sometimes receive events that do not have this flag set. It should take note of + * the fact that the policy intends to drop the event, clean up its state, and + * then send appropriate cancellation events to the dispatcher if needed. + * </p><p> + * For example, suppose the input filter is processing a gesture and one of the touch events + * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set. + * The input filter should clear its internal state about the gesture and then send key or + * motion events to the dispatcher to cancel any keys or pointers that are down. + * </p><p> + * Corollary: Events that set sent to the dispatcher should usually include the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! + * </p><p> + * It may be prudent to disable automatic key repeating for synthetic key events + * by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag. + * </p> + * + * @hide + */ +public abstract class InputFilter extends IInputFilter.Stub { + private static final int MSG_INSTALL = 1; + private static final int MSG_UNINSTALL = 2; + private static final int MSG_INPUT_EVENT = 3; + + // Consistency verifiers for debugging purposes. + private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() ? + new InputEventConsistencyVerifier(this, + InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT, + "InputFilter#InboundInputEventConsistencyVerifier") : null; + private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() ? + new InputEventConsistencyVerifier(this, + InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT, + "InputFilter#OutboundInputEventConsistencyVerifier") : null; + + private final H mH; + + private IInputFilterHost mHost; + + /** + * Creates the input filter. + * + * @param looper The looper to run callbacks on. + */ + public InputFilter(Looper looper) { + mH = new H(looper); + } + + /** + * Called when the input filter is installed. + * This method is guaranteed to be non-reentrant. + * + * @param host The input filter host environment. + */ + public final void install(IInputFilterHost host) { + mH.obtainMessage(MSG_INSTALL, host).sendToTarget(); + } + + /** + * Called when the input filter is uninstalled. + * This method is guaranteed to be non-reentrant. + */ + public final void uninstall() { + mH.obtainMessage(MSG_UNINSTALL).sendToTarget(); + } + + /** + * Called to enqueue the input event for filtering. + * The event will be recycled after the input filter processes it. + * This method is guaranteed to be non-reentrant. + * + * @param event The input event to enqueue. + */ + final public void filterInputEvent(InputEvent event, int policyFlags) { + mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget(); + } + + /** + * Sends an input event to the dispatcher. + * + * @param event The input event to publish. + * @param policyFlags The input event policy flags. + */ + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mHost == null) { + throw new IllegalStateException("Cannot send input event because the input filter " + + "is not installed."); + } + if (mOutboundInputEventConsistencyVerifier != null) { + mOutboundInputEventConsistencyVerifier.onInputEvent(event, 0); + } + try { + mHost.sendInputEvent(event, policyFlags); + } catch (RemoteException re) { + /* ignore */ + } + } + + /** + * Called when an input event has been received from the dispatcher. + * <p> + * The default implementation sends the input event back to the dispatcher, unchanged. + * </p><p> + * The event will be recycled when this method returns. If you want to keep it around, + * make a copy! + * </p> + * + * @param event The input event that was received. + * @param policyFlags The input event policy flags. + */ + public void onInputEvent(InputEvent event, int policyFlags) { + sendInputEvent(event, policyFlags); + } + + /** + * Called when the filter is installed into the dispatch pipeline. + * <p> + * This method is called before the input filter receives any input events. + * The input filter should take this opportunity to prepare itself. + * </p> + */ + public void onInstalled() { + } + + /** + * Called when the filter is uninstalled from the dispatch pipeline. + * <p> + * This method is called after the input filter receives its last input event. + * The input filter should take this opportunity to clean up. + * </p> + */ + public void onUninstalled() { + } + + private final class H extends Handler { + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_INSTALL: + mHost = (IInputFilterHost) msg.obj; + if (mInboundInputEventConsistencyVerifier != null) { + mInboundInputEventConsistencyVerifier.reset(); + } + if (mOutboundInputEventConsistencyVerifier != null) { + mOutboundInputEventConsistencyVerifier.reset(); + } + onInstalled(); + break; + + case MSG_UNINSTALL: + try { + onUninstalled(); + } finally { + mHost = null; + } + break; + + case MSG_INPUT_EVENT: { + final InputEvent event = (InputEvent)msg.obj; + try { + if (mInboundInputEventConsistencyVerifier != null) { + mInboundInputEventConsistencyVerifier.onInputEvent(event, 0); + } + onInputEvent(event, msg.arg1); + } finally { + event.recycle(); + } + break; + } + } + } + } +} diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 26a5b26..f692e05 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -20,6 +20,7 @@ import android.graphics.Canvas; import android.os.Handler; import android.os.Message; import android.widget.FrameLayout; +import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -43,20 +44,20 @@ import java.util.HashMap; * * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService * (Context.LAYOUT_INFLATER_SERVICE);</pre> - * + * * <p> * To create a new LayoutInflater with an additional {@link Factory} for your * own views, you can use {@link #cloneInContext} to clone an existing * ViewFactory, and then call {@link #setFactory} on it to include your * Factory. - * + * * <p> * For performance reasons, view inflation relies heavily on pre-processing of * XML files that is done at build time. Therefore, it is not currently possible * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime; * it only works with an XmlPullParser returned from a compiled resource * (R.<em>something</em> file.) - * + * * @see Context#getSystemService */ public abstract class LayoutInflater { @@ -82,7 +83,7 @@ public abstract class LayoutInflater { private static final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<String, Constructor<? extends View>>(); - + private HashMap<String, Boolean> mFilterMap; private static final String TAG_MERGE = "merge"; @@ -93,36 +94,36 @@ public abstract class LayoutInflater { /** * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed * to be inflated. - * + * */ public interface Filter { /** * Hook to allow clients of the LayoutInflater to restrict the set of Views * that are allowed to be inflated. - * + * * @param clazz The class object for the View that is about to be inflated - * + * * @return True if this class is allowed to be inflated, or false otherwise */ @SuppressWarnings("unchecked") boolean onLoadClass(Class clazz); } - + public interface Factory { /** * Hook you can supply that is called when inflating from a LayoutInflater. * You can use this to customize the tag names available in your XML * layout files. - * + * * <p> * Note that it is good practice to prefix these custom names with your * package (i.e., com.coolcompany.apps) to avoid conflicts with system * names. - * + * * @param name Tag name to be inflated. * @param context The context the view is being created in. * @param attrs Inflation attributes as specified in XML file. - * + * * @return View Newly created view. Return null for the default * behavior. */ @@ -150,14 +151,14 @@ public abstract class LayoutInflater { private static class FactoryMerger implements Factory2 { private final Factory mF1, mF2; private final Factory2 mF12, mF22; - + FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) { mF1 = f1; mF2 = f2; mF12 = f12; mF22 = f22; } - + public View onCreateView(String name, Context context, AttributeSet attrs) { View v = mF1.onCreateView(name, context, attrs); if (v != null) return v; @@ -172,13 +173,13 @@ public abstract class LayoutInflater { : mF2.onCreateView(name, context, attrs); } } - + /** * Create a new LayoutInflater instance associated with a particular Context. * Applications will almost always want to use * {@link Context#getSystemService Context.getSystemService()} to retrieve * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}. - * + * * @param context The Context in which this LayoutInflater will create its * Views; most importantly, this supplies the theme from which the default * values for their attributes are retrieved. @@ -191,7 +192,7 @@ public abstract class LayoutInflater { * Create a new LayoutInflater instance that is a copy of an existing * LayoutInflater, optionally with its Context changed. For use in * implementing {@link #cloneInContext}. - * + * * @param original The original LayoutInflater to copy. * @param newContext The new Context to use. */ @@ -202,7 +203,7 @@ public abstract class LayoutInflater { mPrivateFactory = original.mPrivateFactory; mFilter = original.mFilter; } - + /** * Obtains the LayoutInflater from the given context. */ @@ -220,15 +221,15 @@ public abstract class LayoutInflater { * pointing to a different Context than the original. This is used by * {@link ContextThemeWrapper} to create a new LayoutInflater to go along * with the new Context theme. - * + * * @param newContext The new Context to associate with the new LayoutInflater. * May be the same as the original Context if desired. - * + * * @return Returns a brand spanking new LayoutInflater object associated with * the given Context. */ public abstract LayoutInflater cloneInContext(Context newContext); - + /** * Return the context we are running in, for access to resources, class * loader, etc. @@ -264,7 +265,7 @@ public abstract class LayoutInflater { * called on each element name as the xml is parsed. If the factory returns * a View, that is added to the hierarchy. If it returns null, the next * factory default {@link #onCreateView} method is called. - * + * * <p>If you have an existing * LayoutInflater and want to add your own factory to it, use * {@link #cloneInContext} to clone the existing instance and then you @@ -320,13 +321,13 @@ public abstract class LayoutInflater { public Filter getFilter() { return mFilter; } - + /** * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will * throw an {@link InflateException}. This filter will replace any previous filter set on this * LayoutInflater. - * + * * @param filter The Filter which restricts the set of Views that are allowed to be inflated. * This filter will replace any previous filter set on this LayoutInflater. */ @@ -340,7 +341,7 @@ public abstract class LayoutInflater { /** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. - * + * * @param resource ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param root Optional view to be the parent of the generated hierarchy. @@ -360,7 +361,7 @@ public abstract class LayoutInflater { * reasons, view inflation relies heavily on pre-processing of XML files * that is done at build time. Therefore, it is not currently possible to * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. - * + * * @param parser XML dom node containing the description of the view * hierarchy. * @param root Optional view to be the parent of the generated hierarchy. @@ -375,7 +376,7 @@ public abstract class LayoutInflater { /** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. - * + * * @param resource ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param root Optional view to be the parent of the generated hierarchy (if @@ -407,7 +408,7 @@ public abstract class LayoutInflater { * reasons, view inflation relies heavily on pre-processing of XML files * that is done at build time. Therefore, it is not currently possible to * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. - * + * * @param parser XML dom node containing the description of the view * hierarchy. * @param root Optional view to be the parent of the generated hierarchy (if @@ -442,7 +443,7 @@ public abstract class LayoutInflater { } final String name = parser.getName(); - + if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " @@ -528,17 +529,17 @@ public abstract class LayoutInflater { * Low-level function for instantiating a view by name. This attempts to * instantiate a view class of the given <var>name</var> found in this * LayoutInflater's ClassLoader. - * + * * <p> * There are two things that can happen in an error case: either the * exception describing the error will be thrown, or a null will be * returned. You must deal with both possibilities -- the former will happen * the first time createView() is called for a class of a particular name, * the latter every time there-after for that class name. - * + * * @param name The full name of the class to be instantiated. * @param attrs The XML attributes supplied for this instance. - * + * * @return View The newly instantiated view, or null. */ public final View createView(String name, String prefix, AttributeSet attrs) @@ -551,7 +552,7 @@ public abstract class LayoutInflater { // Class not found in the cache, see if it's real, and try to add it clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); - + if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { @@ -569,7 +570,7 @@ public abstract class LayoutInflater { // New class -- remember whether it is allowed clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); - + boolean allowed = clazz != null && mFilter.onLoadClass(clazz); mFilterMap.put(name, allowed); if (!allowed) { @@ -632,10 +633,10 @@ public abstract class LayoutInflater { * given the xml element name. Override it to handle custom view objects. If * you override this in your subclass be sure to call through to * super.onCreateView(name) for names you do not recognize. - * + * * @param name The fully qualified class name of the View to be create. * @param attrs An AttributeSet of attributes to apply to the View. - * + * * @return View The View created. */ protected View onCreateView(String name, AttributeSet attrs) @@ -679,7 +680,7 @@ public abstract class LayoutInflater { if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, mContext, attrs); } - + if (view == null) { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); @@ -726,7 +727,7 @@ public abstract class LayoutInflater { } final String name = parser.getName(); - + if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_INCLUDE.equals(name)) { @@ -741,7 +742,7 @@ public abstract class LayoutInflater { final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true); - viewGroup.addView(view, params); + viewGroup.addView(view, params); } else { final View view = createViewFromTag(parent, name, attrs); final ViewGroup viewGroup = (ViewGroup) parent; @@ -810,21 +811,14 @@ public abstract class LayoutInflater { // We try to load the layout params set in the <include /> tag. If // they don't exist, we will rely on the layout params set in the // included XML file. - // During a layoutparams generation, a runtime exception is thrown - // if either layout_width or layout_height is missing. We catch - // this exception and set localParams accordingly: true means we - // successfully loaded layout params from the <include /> tag, - // false means we need to rely on the included layout params. - ViewGroup.LayoutParams params = null; - try { - params = group.generateLayoutParams(attrs); - } catch (RuntimeException e) { - params = group.generateLayoutParams(childAttrs); - } finally { - if (params != null) { - view.setLayoutParams(params); - } - } + TypedArray ta = getContext().obtainStyledAttributes(attrs, + R.styleable.ViewGroup_Layout); + boolean definesBothWidthAndHeight = + ta.hasValue(R.styleable.ViewGroup_Layout_layout_width) && + ta.hasValue(R.styleable.ViewGroup_Layout_layout_height); + AttributeSet attributes = definesBothWidthAndHeight ? attrs : childAttrs; + view.setLayoutParams(group.generateLayoutParams(attributes)); + ta.recycle(); // Inflate all children. rInflate(childParser, view, childAttrs, true); diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index bcb8800..4873860 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -17,7 +17,11 @@ package android.view; import android.content.Context; +import android.os.SystemClock; import android.util.FloatMath; +import android.util.Log; + +import java.util.Arrays; /** * Detects scaling transformation gestures using the supplied {@link MotionEvent}s. @@ -137,6 +141,13 @@ public class ScaleGestureDetector { private long mPrevTime; private boolean mInProgress; private int mSpanSlop; + private int mMinSpan; + + private float[] mTouchHistoryLastAccepted; + private int[] mTouchHistoryDirection; + private long[] mTouchHistoryLastAcceptedTime; + + private static final long TOUCH_STABILIZE_TIME = 128; // ms /** * Consistency verifier for debugging purposes. @@ -149,6 +160,135 @@ public class ScaleGestureDetector { mContext = context; mListener = listener; mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2; + mMinSpan = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.config_minScalingSpan); + } + + /** + * The touchMajor/touchMinor elements of a MotionEvent can flutter/jitter on + * some hardware/driver combos. Smooth it out to get kinder, gentler behavior. + * @param ev MotionEvent to add to the ongoing history + */ + private void addTouchHistory(MotionEvent ev) { + final long currentTime = SystemClock.uptimeMillis(); + final int count = ev.getPointerCount(); + for (int i = 0; i < count; i++) { + final int id = ev.getPointerId(i); + ensureTouchHistorySize(id); + + final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted[id]); + boolean accept = true; + final int historySize = ev.getHistorySize(); + for (int h = 0; h < historySize + 1; h++) { + final float major; + final float minor; + if (h < historySize) { + major = ev.getHistoricalTouchMajor(i, h); + minor = ev.getHistoricalTouchMinor(i, h); + } else { + major = ev.getTouchMajor(i); + minor = ev.getTouchMinor(i); + } + final float avg = (major + minor) / 2; + + if (hasLastAccepted) { + final int directionSig = (int) Math.signum(avg - mTouchHistoryLastAccepted[id]); + if (directionSig != mTouchHistoryDirection[id] || + (directionSig == 0 && mTouchHistoryDirection[id] == 0)) { + mTouchHistoryDirection[id] = directionSig; + final long time = h < historySize ? ev.getHistoricalEventTime(h) + : ev.getEventTime(); + mTouchHistoryLastAcceptedTime[id] = time; + accept = false; + } + if (currentTime - mTouchHistoryLastAcceptedTime[id] < TOUCH_STABILIZE_TIME) { + accept = false; + } + } + } + + if (accept) { + float newAccepted = (ev.getTouchMajor(i) + ev.getTouchMinor(i)) / 2; + if (hasLastAccepted) { + newAccepted = (mTouchHistoryLastAccepted[id] + newAccepted) / 2; + } + mTouchHistoryLastAccepted[id] = newAccepted; + mTouchHistoryDirection[id] = 0; + mTouchHistoryLastAcceptedTime[id] = ev.getEventTime(); + } + } + } + + /** + * Clear out the touch history for a given pointer id. + * @param id pointer id to clear + * @see #addTouchHistory(MotionEvent) + */ + private boolean removeTouchHistoryForId(int id) { + if (id >= mTouchHistoryLastAccepted.length) { + return false; + } + mTouchHistoryLastAccepted[id] = Float.NaN; + mTouchHistoryDirection[id] = 0; + mTouchHistoryLastAcceptedTime[id] = 0; + return true; + } + + /** + * Get the adjusted combined touchMajor/touchMinor value for a given pointer id + * @param id the pointer id of the data to obtain + * @return the adjusted major/minor value for the point at id + * @see #addTouchHistory(MotionEvent) + */ + private float getAdjustedTouchHistory(int id) { + if (id >= mTouchHistoryLastAccepted.length) { + Log.e(TAG, "Error retrieving adjusted touch history for id=" + id + + " - incomplete event stream?"); + return 0; + } + return mTouchHistoryLastAccepted[id]; + } + + /** + * Clear all touch history tracking. Useful in ACTION_CANCEL or ACTION_UP. + * @see #addTouchHistory(MotionEvent) + */ + private void clearTouchHistory() { + if (mTouchHistoryLastAccepted == null) { + // All three arrays will be null if this is the case; nothing to do. + return; + } + Arrays.fill(mTouchHistoryLastAccepted, Float.NaN); + Arrays.fill(mTouchHistoryDirection, 0); + Arrays.fill(mTouchHistoryLastAcceptedTime, 0); + } + + private void ensureTouchHistorySize(int id) { + final int requiredSize = id + 1; + if (mTouchHistoryLastAccepted == null || mTouchHistoryLastAccepted.length < requiredSize) { + final float[] newLastAccepted = new float[requiredSize]; + final int[] newDirection = new int[requiredSize]; + final long[] newLastAcceptedTime = new long[requiredSize]; + + int oldLength = 0; + if (mTouchHistoryLastAccepted != null) { + System.arraycopy(mTouchHistoryLastAccepted, 0, newLastAccepted, 0, + mTouchHistoryLastAccepted.length); + System.arraycopy(mTouchHistoryDirection, 0, newDirection, 0, + mTouchHistoryDirection.length); + System.arraycopy(mTouchHistoryLastAcceptedTime, 0, newLastAcceptedTime, 0, + mTouchHistoryLastAcceptedTime.length); + oldLength = mTouchHistoryLastAccepted.length; + } + + Arrays.fill(newLastAccepted, oldLength, newLastAccepted.length, Float.NaN); + Arrays.fill(newDirection, oldLength, newDirection.length, 0); + Arrays.fill(newLastAcceptedTime, oldLength, newLastAcceptedTime.length, 0); + + mTouchHistoryLastAccepted = newLastAccepted; + mTouchHistoryDirection = newDirection; + mTouchHistoryLastAcceptedTime = newLastAcceptedTime; + } } /** @@ -183,11 +323,12 @@ public class ScaleGestureDetector { } if (streamComplete) { + clearTouchHistory(); return true; } } - final boolean configChanged = + final boolean configChanged = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN; final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; @@ -205,12 +346,25 @@ public class ScaleGestureDetector { final float focusX = sumX / div; final float focusY = sumY / div; + if (pointerUp) { + final int id = event.getPointerId(event.getActionIndex()); + if (!removeTouchHistoryForId(id)) { + Log.e(TAG, "Got ACTION_POINTER_UP for previously unknown id=" + id + + " - incomplete event stream?"); + } + } else { + addTouchHistory(event); + } + // Determine average deviation from focal point float devSumX = 0, devSumY = 0; for (int i = 0; i < count; i++) { if (skipIndex == i) continue; - devSumX += Math.abs(event.getX(i) - focusX); - devSumY += Math.abs(event.getY(i) - focusY); + + // Average touch major and touch minor and convert the resulting diameter into a radius. + final float touchSize = getAdjustedTouchHistory(event.getPointerId(i)) / 2; + devSumX += Math.abs(event.getX(i) - focusX) + touchSize; + devSumY += Math.abs(event.getY(i) - focusY) + touchSize; } final float devX = devSumX / div; final float devY = devSumY / div; @@ -228,7 +382,7 @@ public class ScaleGestureDetector { final boolean wasInProgress = mInProgress; mFocusX = focusX; mFocusY = focusY; - if (mInProgress && (span == 0 || configChanged)) { + if (mInProgress && (span < mMinSpan || configChanged)) { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = span; @@ -238,7 +392,7 @@ public class ScaleGestureDetector { mPrevSpanY = mCurrSpanY = spanY; mInitialSpan = mPrevSpan = mCurrSpan = span; } - if (!mInProgress && span != 0 && + if (!mInProgress && span >= mMinSpan && (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) { mPrevSpanX = mCurrSpanX = spanX; mPrevSpanY = mCurrSpanY = spanY; diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index a968768..8f4626f 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -16,8 +16,16 @@ package android.view; +import dalvik.system.CloseGuard; + import android.content.res.CompatibilityInfo.Translator; -import android.graphics.*; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.SurfaceTexture; +import android.os.IBinder; import android.os.Parcelable; import android.os.Parcel; import android.os.SystemProperties; @@ -27,212 +35,187 @@ import android.util.Log; * Handle onto a raw buffer that is being managed by the screen compositor. */ public class Surface implements Parcelable { - private static final String LOG_TAG = "Surface"; - private static final boolean DEBUG_RELEASE = false; - - /* orientations for setOrientation() */ - public static final int ROTATION_0 = 0; - public static final int ROTATION_90 = 1; - public static final int ROTATION_180 = 2; - public static final int ROTATION_270 = 3; + private static final String TAG = "Surface"; - private static final boolean headless = "1".equals( + private static final boolean HEADLESS = "1".equals( SystemProperties.get("ro.config.headless", "0")); - private static void checkHeadless() { - if(headless) { - throw new UnsupportedOperationException("Device is headless"); + public static final Parcelable.Creator<Surface> CREATOR = + new Parcelable.Creator<Surface>() { + public Surface createFromParcel(Parcel source) { + try { + Surface s = new Surface(); + s.readFromParcel(source); + return s; + } catch (Exception e) { + Log.e(TAG, "Exception creating surface from parcel", e); + return null; + } } - } - - /** - * Create Surface from a {@link SurfaceTexture}. - * - * Images drawn to the Surface will be made available to the {@link - * SurfaceTexture}, which can attach them an OpenGL ES texture via {@link - * SurfaceTexture#updateTexImage}. - * - * @param surfaceTexture The {@link SurfaceTexture} that is updated by this - * Surface. - */ - public Surface(SurfaceTexture surfaceTexture) { - checkHeadless(); - if (DEBUG_RELEASE) { - mCreationStack = new Exception(); + public Surface[] newArray(int size) { + return new Surface[size]; } - mCanvas = new CompatibleCanvas(); - initFromSurfaceTexture(surfaceTexture); - } + }; /** - * Does this object hold a valid surface? Returns true if it holds - * a physical surface, so lockCanvas() will succeed. Otherwise - * returns false. + * Rotation constant: 0 degree rotation (natural orientation) */ - public native boolean isValid(); + public static final int ROTATION_0 = 0; - /** Release the local reference to the server-side surface. - * Always call release() when you're done with a Surface. This will - * make the surface invalid. + /** + * Rotation constant: 90 degree rotation. */ - public native void release(); - - /** draw into a surface */ - public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException, IllegalArgumentException { - /* - * the dirty rectangle may be expanded to the surface's size, if for - * instance it has been resized or if the bits were lost, since the last - * call. - */ - return lockCanvasNative(dirty); - } + public static final int ROTATION_90 = 1; - /** unlock the surface and asks a page flip */ - public native void unlockCanvasAndPost(Canvas canvas); - - /** - * unlock the surface. the screen won't be updated until - * post() or postAll() is called + /** + * Rotation constant: 180 degree rotation. */ - public native void unlockCanvas(Canvas canvas); - - @Override - public String toString() { - return "Surface(name=" + mName + ", identity=" + getIdentity() + ")"; - } + public static final int ROTATION_180 = 2; - public int describeContents() { - return 0; - } + /** + * Rotation constant: 270 degree rotation. + */ + public static final int ROTATION_270 = 3; - public native void readFromParcel(Parcel source); - public native void writeToParcel(Parcel dest, int flags); + /* built-in physical display ids (keep in sync with ISurfaceComposer.h) + * these are different from the logical display ids used elsewhere in the framework */ /** - * Exception thrown when a surface couldn't be created or resized + * Built-in physical display id: Main display. + * Use only with {@link #getBuiltInDisplay()}. + * @hide */ - public static class OutOfResourcesException extends Exception { - public OutOfResourcesException() { - } - public OutOfResourcesException(String name) { - super(name); - } - } - - /* - * ----------------------------------------------------------------------- - * No user serviceable parts beyond this point - * ----------------------------------------------------------------------- + public static final int BUILT_IN_DISPLAY_ID_MAIN = 0; + + /** + * Built-in physical display id: Attached HDMI display. + * Use only with {@link #getBuiltInDisplay()}. + * @hide */ + public static final int BUILT_IN_DISPLAY_ID_HDMI = 1; - /* flags used in constructor (keep in sync with ISurfaceComposer.h) */ + /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */ - /** Surface is created hidden @hide */ - public static final int HIDDEN = 0x00000004; + /** + * Surface creation flag: Surface is created hidden + * @hide */ + public static final int HIDDEN = 0x00000004; - /** The surface contains secure content, special measures will - * be taken to disallow the surface's content to be copied from - * another process. In particular, screenshots and VNC servers will + /** + * Surface creation flag: The surface contains secure content, special + * measures will be taken to disallow the surface's content to be copied + * from another process. In particular, screenshots and VNC servers will * be disabled, but other measures can take place, for instance the * surface might not be hardware accelerated. - * @hide*/ - public static final int SECURE = 0x00000080; - - /** Creates a surface where color components are interpreted as - * "non pre-multiplied" by their alpha channel. Of course this flag is - * meaningless for surfaces without an alpha channel. By default - * surfaces are pre-multiplied, which means that each color component is - * already multiplied by its alpha value. In this case the blending - * equation used is: - * + * @hide + */ + public static final int SECURE = 0x00000080; + + /** + * Surface creation flag: Creates a surface where color components are interpreted + * as "non pre-multiplied" by their alpha channel. Of course this flag is + * meaningless for surfaces without an alpha channel. By default + * surfaces are pre-multiplied, which means that each color component is + * already multiplied by its alpha value. In this case the blending + * equation used is: + * * DEST = SRC + DEST * (1-SRC_ALPHA) - * - * By contrast, non pre-multiplied surfaces use the following equation: - * + * + * By contrast, non pre-multiplied surfaces use the following equation: + * * DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA) - * - * pre-multiplied surfaces must always be used if transparent pixels are - * composited on top of each-other into the surface. A pre-multiplied - * surface can never lower the value of the alpha component of a given - * pixel. - * - * In some rare situations, a non pre-multiplied surface is preferable. - * - * @hide - */ - public static final int NON_PREMULTIPLIED = 0x00000100; - + * + * pre-multiplied surfaces must always be used if transparent pixels are + * composited on top of each-other into the surface. A pre-multiplied + * surface can never lower the value of the alpha component of a given + * pixel. + * + * In some rare situations, a non pre-multiplied surface is preferable. + * @hide + */ + public static final int NON_PREMULTIPLIED = 0x00000100; + /** - * Indicates that the surface must be considered opaque, even if its - * pixel format is set to translucent. This can be useful if an + * Surface creation flag: Indicates that the surface must be considered opaque, + * even if its pixel format is set to translucent. This can be useful if an * application needs full RGBA 8888 support for instance but will * still draw every pixel opaque. - * * @hide */ - public static final int OPAQUE = 0x00000400; - + public static final int OPAQUE = 0x00000400; + /** - * Application requires a hardware-protected path to an + * Surface creation flag: Application requires a hardware-protected path to an * external display sink. If a hardware-protected path is not available, * then this surface will not be displayed on the external sink. - * * @hide */ - public static final int PROTECTED_APP = 0x00000800; + public static final int PROTECTED_APP = 0x00000800; // 0x1000 is reserved for an independent DRM protected flag in framework - /** Creates a normal surface. This is the default. @hide */ + /** + * Surface creation flag: Creates a normal surface. + * This is the default. + * @hide + */ public static final int FX_SURFACE_NORMAL = 0x00000000; - - /** Creates a Blur surface. Everything behind this surface is blurred - * by some amount. The quality and refresh speed of the blur effect - * is not settable or guaranteed. - * It is an error to lock a Blur surface, since it doesn't have - * a backing store. + + /** + * Surface creation flag: Creates a Blur surface. + * Everything behind this surface is blurred by some amount. + * The quality and refresh speed of the blur effect is not settable or guaranteed. + * It is an error to lock a Blur surface, since it doesn't have a backing store. * @hide * @deprecated */ @Deprecated - public static final int FX_SURFACE_BLUR = 0x00010000; - - /** Creates a Dim surface. Everything behind this surface is dimmed - * by the amount specified in {@link #setAlpha}. - * It is an error to lock a Dim surface, since it doesn't have - * a backing store. + public static final int FX_SURFACE_BLUR = 0x00010000; + + /** + * Surface creation flag: Creates a Dim surface. + * Everything behind this surface is dimmed by the amount specified + * in {@link #setAlpha}. It is an error to lock a Dim surface, since it + * doesn't have a backing store. * @hide */ - public static final int FX_SURFACE_DIM = 0x00020000; + public static final int FX_SURFACE_DIM = 0x00020000; - /** @hide */ - public static final int FX_SURFACE_SCREENSHOT = 0x00030000; + /** + * @hide + */ + public static final int FX_SURFACE_SCREENSHOT = 0x00030000; - /** Mask used for FX values above @hide */ - public static final int FX_SURFACE_MASK = 0x000F0000; + /** + * Mask used for FX values above. + * @hide + */ + public static final int FX_SURFACE_MASK = 0x000F0000; /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */ - /** Hide the surface. Equivalent to calling hide(). @hide */ - public static final int SURFACE_HIDDEN = 0x01; - - /** Freeze the surface. Equivalent to calling freeze(). @hide */ - public static final int SURFACE_FROZEN = 0x02; + /** + * Surface flag: Hide the surface. + * Equivalent to calling hide(). + * @hide + */ + public static final int SURFACE_HIDDEN = 0x01; + - /** Enable dithering when compositing this surface @hide */ - public static final int SURFACE_DITHER = 0x04; + private final CloseGuard mCloseGuard = CloseGuard.get(); + private String mName; + // Note: These fields are accessed by native code. // The mSurfaceControl will only be present for Surfaces used by the window // server or system processes. When this class is parceled we defer to the // mSurfaceControl to do the parceling. Otherwise we parcel the // mNativeSurface. - private int mSurfaceControl; - private int mSaveCount; - private Canvas mCanvas; - private int mNativeSurface; - private int mSurfaceGenerationId; - private String mName; + private int mNativeSurface; // Surface* + private int mNativeSurfaceControl; // SurfaceControl* + private int mGenerationId; // incremented each time mNativeSurface changes + private final Canvas mCanvas = new CompatibleCanvas(); + private int mCanvasSaveCount; // Canvas save count at time of lockCanvas() // The Translator for density compatibility mode. This is used for scaling // the canvas to perform the appropriate density transformation. @@ -242,154 +225,241 @@ public class Surface implements Parcelable { // non compatibility mode. private Matrix mCompatibleMatrix; - private Exception mCreationStack; + private native void nativeCreate(SurfaceSession session, String name, + int w, int h, int format, int flags) + throws OutOfResourcesException; + private native void nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture) + throws OutOfResourcesException; + private native void nativeRelease(); + private native void nativeDestroy(); + + private native boolean nativeIsValid(); + private native int nativeGetIdentity(); + private native boolean nativeIsConsumerRunningBehind(); + + private native Canvas nativeLockCanvas(Rect dirty); + private native void nativeUnlockCanvasAndPost(Canvas canvas); + + private static native Bitmap nativeScreenshot(IBinder displayToken, + int width, int height, int minLayer, int maxLayer, boolean allLayers); + + private static native void nativeOpenTransaction(); + private static native void nativeCloseTransaction(); + + private native void nativeSetLayer(int zorder); + private native void nativeSetPosition(float x, float y); + private native void nativeSetSize(int w, int h); + private native void nativeSetTransparentRegionHint(Region region); + private native void nativeSetAlpha(float alpha); + private native void nativeSetMatrix(float dsdx, float dtdx, float dsdy, float dtdy); + private native void nativeSetFlags(int flags, int mask); + private native void nativeSetWindowCrop(Rect crop); + private native void nativeSetLayerStack(int layerStack); + + private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId); + private static native IBinder nativeCreateDisplay(String name); + private static native void nativeSetDisplaySurface( + IBinder displayToken, Surface surface); + private static native void nativeSetDisplayLayerStack( + IBinder displayToken, int layerStack); + private static native void nativeSetDisplayProjection( + IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect); + private static native boolean nativeGetDisplayInfo( + IBinder displayToken, PhysicalDisplayInfo outInfo); + + private native void nativeCopyFrom(Surface other); + private native void nativeTransferFrom(Surface other); + private native void nativeReadFromParcel(Parcel source); + private native void nativeWriteToParcel(Parcel dest); - /* - * We use a class initializer to allow the native code to cache some - * field offsets. + /** + * Create an empty surface, which will later be filled in by readFromParcel(). + * @hide */ - native private static void nativeClassInit(); - static { nativeClassInit(); } - - /** create a surface @hide */ - public Surface(SurfaceSession s, - int pid, int display, int w, int h, int format, int flags) - throws OutOfResourcesException { + public Surface() { checkHeadless(); - if (DEBUG_RELEASE) { - mCreationStack = new Exception(); - } - mCanvas = new CompatibleCanvas(); - init(s,pid,null,display,w,h,format,flags); + mCloseGuard.open("release"); } - /** create a surface with a name @hide */ - public Surface(SurfaceSession s, - int pid, String name, int display, int w, int h, int format, int flags) - throws OutOfResourcesException { - checkHeadless(); + /** + * Create a surface with a name. + * + * The surface creation flags specify what kind of surface to create and + * certain options such as whether the surface can be assumed to be opaque + * and whether it should be initially hidden. Surfaces should always be + * created with the {@link #HIDDEN} flag set to ensure that they are not + * made visible prematurely before all of the surface's properties have been + * configured. + * + * Good practice is to first create the surface with the {@link #HIDDEN} flag + * specified, open a transaction, set the surface layer, layer stack, alpha, + * and position, call {@link #show} if appropriate, and close the transaction. + * + * @param session The surface session, must not be null. + * @param name The surface name, must not be null. + * @param w The surface initial width. + * @param h The surface initial height. + * @param flags The surface creation flags. Should always include {@link #HIDDEN} + * in the creation flags. + * @hide + */ + public Surface(SurfaceSession session, + String name, int w, int h, int format, int flags) + throws OutOfResourcesException { + if (session == null) { + throw new IllegalArgumentException("session must not be null"); + } + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } - if (DEBUG_RELEASE) { - mCreationStack = new Exception(); + if ((flags & HIDDEN) == 0) { + Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set " + + "to ensure that they are not made visible prematurely before " + + "all of the surface's properties have been configured. " + + "Set the other properties and make the surface visible within " + + "a transaction. New surface name: " + name, + new Throwable()); } - mCanvas = new CompatibleCanvas(); - init(s,pid,name,display,w,h,format,flags); + + checkHeadless(); + mName = name; + nativeCreate(session, name, w, h, format, flags); + + mCloseGuard.open("release"); } /** - * Create an empty surface, which will later be filled in by - * readFromParcel(). - * @hide + * Create Surface from a {@link SurfaceTexture}. + * + * Images drawn to the Surface will be made available to the {@link + * SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link + * SurfaceTexture#updateTexImage}. + * + * @param surfaceTexture The {@link SurfaceTexture} that is updated by this + * Surface. */ - public Surface() { + public Surface(SurfaceTexture surfaceTexture) { + if (surfaceTexture == null) { + throw new IllegalArgumentException("surfaceTexture must not be null"); + } + checkHeadless(); - if (DEBUG_RELEASE) { - mCreationStack = new Exception(); + mName = surfaceTexture.toString(); + try { + nativeCreateFromSurfaceTexture(surfaceTexture); + } catch (OutOfResourcesException ex) { + // We can't throw OutOfResourcesException because it would be an API change. + throw new RuntimeException(ex); } - mCanvas = new CompatibleCanvas(); + + mCloseGuard.open("release"); } - private Surface(Parcel source) throws OutOfResourcesException { - init(source); + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + nativeRelease(); + } finally { + super.finalize(); + } } /** - * Copy another surface to this one. This surface now holds a reference - * to the same data as the original surface, and is -not- the owner. - * This is for use by the window manager when returning a window surface - * back from a client, converting it from the representation being managed - * by the window manager to the representation the client uses to draw - * in to it. + * Release the local reference to the server-side surface. + * Always call release() when you're done with a Surface. + * This will make the surface invalid. + */ + public void release() { + nativeRelease(); + mCloseGuard.close(); + } + + /** + * Free all server-side state associated with this surface and + * release this object's reference. This method can only be + * called from the process that created the service. * @hide */ - public native void copyFrom(Surface o); + public void destroy() { + nativeDestroy(); + mCloseGuard.close(); + } /** - * Transfer the native state from 'o' to this surface, releasing it - * from 'o'. This is for use in the client side for drawing into a - * surface; not guaranteed to work on the window manager side. - * This is for use by the client to move the underlying surface from - * one Surface object to another, in particular in SurfaceFlinger. - * @hide. + * Returns true if this object holds a valid surface. + * + * @return True if it holds a physical surface, so lockCanvas() will succeed. + * Otherwise returns false. */ - public native void transferFrom(Surface o); + public boolean isValid() { + return nativeIsValid(); + } - /** @hide */ + /** + * Gets the generation number of this surface, incremented each time + * the native surface contained within this object changes. + * + * @return The current generation number. + * @hide + */ public int getGenerationId() { - return mSurfaceGenerationId; + return mGenerationId; } - /** - * Whether the consumer of this Surface is running behind the producer; - * that is, isConsumerRunningBehind() returns true if the consumer is more - * than one buffer ahead of the producer. + * Returns true if the consumer of this Surface is running behind the producer. + * + * @return True if the consumer is more than one buffer ahead of the producer. * @hide */ - public native boolean isConsumerRunningBehind(); + public boolean isConsumerRunningBehind() { + return nativeIsConsumerRunningBehind(); + } /** - * A Canvas class that can handle the compatibility mode. This does two - * things differently. - * <ul> - * <li>Returns the width and height of the target metrics, rather than - * native. For example, the canvas returns 320x480 even if an app is running - * in WVGA high density. - * <li>Scales the matrix in setMatrix by the application scale, except if - * the matrix looks like obtained from getMatrix. This is a hack to handle - * the case that an application uses getMatrix to keep the original matrix, - * set matrix of its own, then set the original matrix back. There is no - * perfect solution that works for all cases, and there are a lot of cases - * that this model does not work, but we hope this works for many apps. - * </ul> + * Gets a {@link Canvas} for drawing into this surface. + * + * After drawing into the provided {@link Canvas}, the caller should + * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. + * + * @param dirty A rectangle that represents the dirty region that the caller wants + * to redraw. This function may choose to expand the dirty rectangle if for example + * the surface has been resized or if the previous contents of the surface were + * not available. The caller should redraw the entire dirty region as represented + * by the contents of the dirty rect upon return from this function. + * The caller may also pass <code>null</code> instead, in the case where the + * entire surface should be redrawn. + * @return A canvas for drawing into the surface. */ - private class CompatibleCanvas extends Canvas { - // A temp matrix to remember what an application obtained via {@link getMatrix} - private Matrix mOrigMatrix = null; - - @Override - public int getWidth() { - int w = super.getWidth(); - if (mCompatibilityTranslator != null) { - w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f); - } - return w; - } - - @Override - public int getHeight() { - int h = super.getHeight(); - if (mCompatibilityTranslator != null) { - h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f); - } - return h; - } + public Canvas lockCanvas(Rect dirty) + throws OutOfResourcesException, IllegalArgumentException { + return nativeLockCanvas(dirty); + } - @Override - public void setMatrix(Matrix matrix) { - if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) { - // don't scale the matrix if it's not compatibility mode, or - // the matrix was obtained from getMatrix. - super.setMatrix(matrix); - } else { - Matrix m = new Matrix(mCompatibleMatrix); - m.preConcat(matrix); - super.setMatrix(m); - } - } + /** + * Posts the new contents of the {@link Canvas} to the surface and + * releases the {@link Canvas}. + * + * @param canvas The canvas previously obtained from {@link #lockCanvas}. + */ + public void unlockCanvasAndPost(Canvas canvas) { + nativeUnlockCanvasAndPost(canvas); + } - @Override - public void getMatrix(Matrix m) { - super.getMatrix(m); - if (mOrigMatrix == null) { - mOrigMatrix = new Matrix(); - } - mOrigMatrix.set(m); - } + /** + * @deprecated This API has been removed and is not supported. Do not use. + */ + @Deprecated + public void unlockCanvas(Canvas canvas) { + throw new UnsupportedOperationException(); } /** @@ -403,60 +473,19 @@ public class Surface implements Parcelable { mCompatibleMatrix.setScale(appScale, appScale); } } - - /** Free all server-side state associated with this surface and - * release this object's reference. @hide */ - public native void destroy(); - - private native Canvas lockCanvasNative(Rect dirty); - - /* - * set display parameters & screenshots - */ - - /** - * Freezes the specified display, No updating of the screen will occur - * until unfreezeDisplay() is called. Everything else works as usual though, - * in particular transactions. - * @param display - * @hide - */ - public static native void freezeDisplay(int display); - - /** - * resume updating the specified display. - * @param display - * @hide - */ - public static native void unfreezeDisplay(int display); /** - * set the orientation of the given display. - * @param display - * @param orientation - * @param flags Currently unused, set to 0. - * @hide - */ - public static native void setOrientation(int display, int orientation, int flags); - - /** - * set the orientation of the given display. - * @param display - * @param orientation - * @hide - */ - public static void setOrientation(int display, int orientation) { - setOrientation(display, orientation, 0); - } - - /** * Like {@link #screenshot(int, int, int, int)} but includes all * Surfaces in the screenshot. * * @hide */ - public static native Bitmap screenshot(int width, int height); - + public static Bitmap screenshot(int width, int height) { + // TODO: should take the display as a parameter + IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN); + return nativeScreenshot(displayToken, width, height, 0, 0, true); + } + /** * Copy the current screen contents into a bitmap and return it. * @@ -468,95 +497,345 @@ public class Surface implements Parcelable { * include in the screenshot. * @param maxLayer The highest (top-most Z order) surface layer to * include in the screenshot. - * @return Returns a Bitmap containing the screen contents. + * @return Returns a Bitmap containing the screen contents, or null + * if an error occurs. * * @hide */ - public static native Bitmap screenshot(int width, int height, int minLayer, int maxLayer); + public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) { + // TODO: should take the display as a parameter + IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN); + return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false); + } - /* * set surface parameters. * needs to be inside open/closeTransaction block */ - + /** start a transaction @hide */ - public static native void openTransaction(); + public static void openTransaction() { + nativeOpenTransaction(); + } + /** end a transaction @hide */ - public static native void closeTransaction(); + public static void closeTransaction() { + nativeCloseTransaction(); + } + /** @hide */ - public native void setLayer(int zorder); + public void setLayer(int zorder) { + nativeSetLayer(zorder); + } + /** @hide */ - public void setPosition(int x, int y) { setPosition((float)x, (float)y); } + public void setPosition(int x, int y) { + nativeSetPosition((float)x, (float)y); + } + /** @hide */ - public native void setPosition(float x, float y); + public void setPosition(float x, float y) { + nativeSetPosition(x, y); + } + /** @hide */ - public native void setSize(int w, int h); + public void setSize(int w, int h) { + nativeSetSize(w, h); + } + /** @hide */ - public native void hide(); + public void hide() { + nativeSetFlags(SURFACE_HIDDEN, SURFACE_HIDDEN); + } + /** @hide */ - public native void show(); + public void show() { + nativeSetFlags(0, SURFACE_HIDDEN); + } + + /** @hide */ + public void setTransparentRegionHint(Region region) { + nativeSetTransparentRegionHint(region); + } + + /** @hide */ + public void setAlpha(float alpha) { + nativeSetAlpha(alpha); + } + + /** @hide */ + public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { + nativeSetMatrix(dsdx, dtdx, dsdy, dtdy); + } + /** @hide */ - public native void setTransparentRegionHint(Region region); + public void setFlags(int flags, int mask) { + nativeSetFlags(flags, mask); + } + /** @hide */ - public native void setAlpha(float alpha); + public void setWindowCrop(Rect crop) { + nativeSetWindowCrop(crop); + } + /** @hide */ - public native void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy); + public void setLayerStack(int layerStack) { + nativeSetLayerStack(layerStack); + } + /** @hide */ - public native void freeze(); + public static IBinder getBuiltInDisplay(int builtInDisplayId) { + return nativeGetBuiltInDisplay(builtInDisplayId); + } + /** @hide */ - public native void unfreeze(); + public static IBinder createDisplay(String name) { + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } + return nativeCreateDisplay(name); + } + /** @hide */ - public native void setFreezeTint(int tint); + public static void setDisplaySurface(IBinder displayToken, Surface surface) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + nativeSetDisplaySurface(displayToken, surface); + } + /** @hide */ - public native void setFlags(int flags, int mask); + public static void setDisplayLayerStack(IBinder displayToken, int layerStack) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + nativeSetDisplayLayerStack(displayToken, layerStack); + } + /** @hide */ - public native void setWindowCrop(Rect crop); + public static void setDisplayProjection(IBinder displayToken, + int orientation, Rect layerStackRect, Rect displayRect) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (layerStackRect == null) { + throw new IllegalArgumentException("layerStackRect must not be null"); + } + if (displayRect == null) { + throw new IllegalArgumentException("displayRect must not be null"); + } + nativeSetDisplayProjection(displayToken, orientation, layerStackRect, displayRect); + } + /** @hide */ + public static boolean getDisplayInfo(IBinder displayToken, PhysicalDisplayInfo outInfo) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (outInfo == null) { + throw new IllegalArgumentException("outInfo must not be null"); + } + return nativeGetDisplayInfo(displayToken, outInfo); + } - - public static final Parcelable.Creator<Surface> CREATOR - = new Parcelable.Creator<Surface>() - { - public Surface createFromParcel(Parcel source) { - try { - return new Surface(source); - } catch (Exception e) { - Log.e(LOG_TAG, "Exception creating surface from parcel", e); - } - return null; + /** + * Copy another surface to this one. This surface now holds a reference + * to the same data as the original surface, and is -not- the owner. + * This is for use by the window manager when returning a window surface + * back from a client, converting it from the representation being managed + * by the window manager to the representation the client uses to draw + * in to it. + * @hide + */ + public void copyFrom(Surface other) { + if (other == null) { + throw new IllegalArgumentException("other must not be null"); } + if (other != this) { + nativeCopyFrom(other); + } + } - public Surface[] newArray(int size) { - return new Surface[size]; + /** + * Transfer the native state from 'other' to this surface, releasing it + * from 'other'. This is for use in the client side for drawing into a + * surface; not guaranteed to work on the window manager side. + * This is for use by the client to move the underlying surface from + * one Surface object to another, in particular in SurfaceFlinger. + * @hide. + */ + public void transferFrom(Surface other) { + if (other == null) { + throw new IllegalArgumentException("other must not be null"); } - }; + if (other != this) { + nativeTransferFrom(other); + } + } @Override - protected void finalize() throws Throwable { - try { - super.finalize(); - } finally { - if (mNativeSurface != 0 || mSurfaceControl != 0) { - if (DEBUG_RELEASE) { - Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() (" - + mNativeSurface + ", " + mSurfaceControl + ")", mCreationStack); - } else { - Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() (" - + mNativeSurface + ", " + mSurfaceControl + ")"); - } - } - release(); + public int describeContents() { + return 0; + } + + public void readFromParcel(Parcel source) { + if (source == null) { + throw new IllegalArgumentException("source must not be null"); + } + + mName = source.readString(); + nativeReadFromParcel(source); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (dest == null) { + throw new IllegalArgumentException("dest must not be null"); + } + + dest.writeString(mName); + nativeWriteToParcel(dest); + if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { + release(); + } + } + + @Override + public String toString() { + return "Surface(name=" + mName + ", identity=" + nativeGetIdentity() + ")"; + } + + private static void checkHeadless() { + if (HEADLESS) { + throw new UnsupportedOperationException("Device is headless"); + } + } + + /** + * Exception thrown when a surface couldn't be created or resized. + */ + public static class OutOfResourcesException extends Exception { + public OutOfResourcesException() { + } + + public OutOfResourcesException(String name) { + super(name); + } + } + + /** + * Describes the properties of a physical display known to surface flinger. + * @hide + */ + public static final class PhysicalDisplayInfo { + public int width; + public int height; + public float refreshRate; + public float density; + public float xDpi; + public float yDpi; + + public PhysicalDisplayInfo() { + } + + public PhysicalDisplayInfo(PhysicalDisplayInfo other) { + copyFrom(other); + } + + @Override + public boolean equals(Object o) { + return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o); + } + + public boolean equals(PhysicalDisplayInfo other) { + return other != null + && width == other.width + && height == other.height + && refreshRate == other.refreshRate + && density == other.density + && xDpi == other.xDpi + && yDpi == other.yDpi; + } + + @Override + public int hashCode() { + return 0; // don't care + } + + public void copyFrom(PhysicalDisplayInfo other) { + width = other.width; + height = other.height; + refreshRate = other.refreshRate; + density = other.density; + xDpi = other.xDpi; + yDpi = other.yDpi; + } + + // For debugging purposes + @Override + public String toString() { + return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, " + + "density " + density + ", " + xDpi + " x " + yDpi + " dpi}"; } } - - private native void init(SurfaceSession s, - int pid, String name, int display, int w, int h, int format, int flags) - throws OutOfResourcesException; - private native void init(Parcel source); + /** + * A Canvas class that can handle the compatibility mode. + * This does two things differently. + * <ul> + * <li>Returns the width and height of the target metrics, rather than + * native. For example, the canvas returns 320x480 even if an app is running + * in WVGA high density. + * <li>Scales the matrix in setMatrix by the application scale, except if + * the matrix looks like obtained from getMatrix. This is a hack to handle + * the case that an application uses getMatrix to keep the original matrix, + * set matrix of its own, then set the original matrix back. There is no + * perfect solution that works for all cases, and there are a lot of cases + * that this model does not work, but we hope this works for many apps. + * </ul> + */ + private final class CompatibleCanvas extends Canvas { + // A temp matrix to remember what an application obtained via {@link getMatrix} + private Matrix mOrigMatrix = null; - private native void initFromSurfaceTexture(SurfaceTexture surfaceTexture); + @Override + public int getWidth() { + int w = super.getWidth(); + if (mCompatibilityTranslator != null) { + w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f); + } + return w; + } - private native int getIdentity(); + @Override + public int getHeight() { + int h = super.getHeight(); + if (mCompatibilityTranslator != null) { + h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f); + } + return h; + } + + @Override + public void setMatrix(Matrix matrix) { + if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) { + // don't scale the matrix if it's not compatibility mode, or + // the matrix was obtained from getMatrix. + super.setMatrix(matrix); + } else { + Matrix m = new Matrix(mCompatibleMatrix); + m.preConcat(matrix); + super.setMatrix(m); + } + } + + @Override + public void getMatrix(Matrix m) { + super.getMatrix(m); + if (mOrigMatrix == null) { + mOrigMatrix = new Matrix(); + } + mOrigMatrix.set(m); + } + } } diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java index 2a04675..0dfd94a 100644 --- a/core/java/android/view/SurfaceSession.java +++ b/core/java/android/view/SurfaceSession.java @@ -16,34 +16,44 @@ package android.view; - /** * An instance of this class represents a connection to the surface - * flinger, in which you can create one or more Surface instances that will + * flinger, from which you can create one or more Surface instances that will * be composited to the screen. * {@hide} */ -public class SurfaceSession { +public final class SurfaceSession { + // Note: This field is accessed by native code. + private int mNativeClient; // SurfaceComposerClient* + + private static native int nativeCreate(); + private static native void nativeDestroy(int ptr); + private static native void nativeKill(int ptr); + /** Create a new connection with the surface flinger. */ public SurfaceSession() { - init(); + mNativeClient = nativeCreate(); } - /** Forcibly detach native resources associated with this object. - * Unlike destroy(), after this call any surfaces that were created - * from the session will no longer work. The session itself is destroyed. - */ - public native void kill(); - /* no user serviceable parts here ... */ @Override protected void finalize() throws Throwable { - destroy(); + try { + if (mNativeClient != 0) { + nativeDestroy(mNativeClient); + } + } finally { + super.finalize(); + } + } + + /** + * Forcibly detach native resources associated with this object. + * Unlike destroy(), after this call any surfaces that were created + * from the session will no longer work. + */ + public void kill() { + nativeKill(mNativeClient); } - - private native void init(); - private native void destroy(); - - private int mClient; } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index ed4c75c..0d16dd3 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -46,13 +46,18 @@ import java.util.concurrent.locks.ReentrantLock; * * <p>The surface is Z ordered so that it is behind the window holding its * SurfaceView; the SurfaceView punches a hole in its window to allow its - * surface to be displayed. The view hierarchy will take care of correctly + * surface to be displayed. The view hierarchy will take care of correctly * compositing with the Surface any siblings of the SurfaceView that would - * normally appear on top of it. This can be used to place overlays such as + * normally appear on top of it. This can be used to place overlays such as * buttons on top of the Surface, though note however that it can have an * impact on performance since a full alpha-blended composite will be performed * each time the Surface changes. * + * <p> The transparent region that makes the surface visible is based on the + * layout positions in the view hierarchy. If the post-layout transform + * properties are used to draw a sibling view on top of the SurfaceView, the + * view may not be properly composited with the surface. + * * <p>Access to the underlying surface is provided via the SurfaceHolder interface, * which can be retrieved by calling {@link #getHolder}. * @@ -62,14 +67,14 @@ import java.util.concurrent.locks.ReentrantLock; * Surface is created and destroyed as the window is shown and hidden. * * <p>One of the purposes of this class is to provide a surface in which a - * secondary thread can render into the screen. If you are going to use it + * secondary thread can render into the screen. If you are going to use it * this way, you need to be aware of some threading semantics: * * <ul> * <li> All SurfaceView and * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called * from the thread running the SurfaceView's window (typically the main thread - * of the application). They thus need to correctly synchronize with any + * of the application). They thus need to correctly synchronize with any * state that is also touched by the drawing thread. * <li> You must ensure that the drawing thread only touches the underlying * Surface while it is valid -- between @@ -296,7 +301,7 @@ public class SurfaceView extends View { } boolean opaque = true; - if ((mPrivateFlags & SKIP_DRAW) == 0) { + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { // this view draws, remove it from the transparent region opaque = super.gatherTransparentRegion(region); } else if (region != null) { @@ -320,7 +325,7 @@ public class SurfaceView extends View { public void draw(Canvas canvas) { if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { // draw() is not called when SKIP_DRAW is set - if ((mPrivateFlags & SKIP_DRAW) == 0) { + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { // punch a whole in the view-hierarchy below us canvas.drawColor(0, PorterDuff.Mode.CLEAR); } @@ -332,7 +337,7 @@ public class SurfaceView extends View { protected void dispatchDraw(Canvas canvas) { if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { // if SKIP_DRAW is cleared, draw() has already punched a hole - if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // punch a whole in the view-hierarchy below us canvas.drawColor(0, PorterDuff.Mode.CLEAR); } @@ -456,11 +461,12 @@ public class SurfaceView extends View { } if (mWindow == null) { + Display display = getDisplay(); mWindow = new MyWindow(this); mLayout.type = mWindowType; - mLayout.gravity = Gravity.LEFT|Gravity.TOP; - mSession.addWithoutInputChannel(mWindow, mWindow.mSeq, mLayout, - mVisible ? VISIBLE : GONE, mContentInsets); + mLayout.gravity = Gravity.START|Gravity.TOP; + mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout, + mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets); } boolean realSizeChanged; @@ -480,10 +486,10 @@ public class SurfaceView extends View { relayoutResult = mSession.relayout( mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, visible ? VISIBLE : GONE, - WindowManagerImpl.RELAYOUT_DEFER_SURFACE_DESTROY, + WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY, mWinFrame, mContentInsets, mVisibleInsets, mConfiguration, mNewSurface); - if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { + if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { mReportDrawNeeded = true; } @@ -516,8 +522,8 @@ public class SurfaceView extends View { SurfaceHolder.Callback callbacks[] = null; - final boolean surfaceChanged = - (relayoutResult&WindowManagerImpl.RELAYOUT_RES_SURFACE_CHANGED) != 0; + final boolean surfaceChanged = (relayoutResult + & WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0; if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) { mSurfaceCreated = false; if (mSurface.isValid()) { @@ -615,21 +621,22 @@ public class SurfaceView extends View { mSurfaceView = new WeakReference<SurfaceView>(surfaceView); } - public void resized(int w, int h, Rect contentInsets, + @Override + public void resized(Rect frame, Rect contentInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig) { SurfaceView surfaceView = mSurfaceView.get(); if (surfaceView != null) { if (DEBUG) Log.v( - "SurfaceView", surfaceView + " got resized: w=" + - w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight); + "SurfaceView", surfaceView + " got resized: w=" + frame.width() + + " h=" + frame.height() + ", cur w=" + mCurWidth + " h=" + mCurHeight); surfaceView.mSurfaceLock.lock(); try { if (reportDraw) { surfaceView.mUpdateWindowNeeded = true; surfaceView.mReportDrawNeeded = true; surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG); - } else if (surfaceView.mWinFrame.width() != w - || surfaceView.mWinFrame.height() != h) { + } else if (surfaceView.mWinFrame.width() != frame.width() + || surfaceView.mWinFrame.height() != frame.height()) { surfaceView.mUpdateWindowNeeded = true; surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG); } diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index a719a01..876b7d84 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -224,6 +224,7 @@ public class TextureView extends View { private void destroySurface() { if (mLayer != null) { mSurface.detachFromGLContext(); + mLayer.clearStorage(); boolean shouldRelease = true; if (mListener != null) { @@ -367,6 +368,7 @@ public class TextureView extends View { if (mListener != null && !mUpdateSurface) { mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight()); } + mLayer.setLayerPaint(mLayerPaint); } if (mUpdateSurface) { @@ -534,7 +536,8 @@ public class TextureView extends View { */ public Bitmap getBitmap(int width, int height) { if (isAvailable() && width > 0 && height > 0) { - return getBitmap(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)); + return getBitmap(Bitmap.createBitmap(getResources().getDisplayMetrics(), + width, height, Bitmap.Config.ARGB_8888)); } return null; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f2a80d0..3ed47ea 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -39,6 +39,7 @@ import android.graphics.Region; import android.graphics.Shader; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.hardware.display.DisplayManagerGlobal; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -47,9 +48,9 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; -import android.util.LocaleUtil; import android.util.Log; import android.util.Pool; import android.util.Poolable; @@ -82,14 +83,21 @@ import static java.lang.Math.max; import com.android.internal.R; import com.android.internal.util.Predicate; import com.android.internal.view.menu.MenuBuilder; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; import java.lang.ref.WeakReference; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; /** * <p> @@ -343,9 +351,10 @@ import java.util.concurrent.CopyOnWriteArrayList; * Padding can be used to offset the content of the view by a specific amount of * pixels. For instance, a left padding of 2 will push the view's content by * 2 pixels to the right of the left edge. Padding can be set using the - * {@link #setPadding(int, int, int, int)} method and queried by calling - * {@link #getPaddingLeft()}, {@link #getPaddingTop()}, {@link #getPaddingRight()}, - * {@link #getPaddingBottom()}. + * {@link #setPadding(int, int, int, int)} or {@link #setPaddingRelative(int, int, int, int)} + * method and queried by calling {@link #getPaddingLeft()}, {@link #getPaddingTop()}, + * {@link #getPaddingRight()}, {@link #getPaddingBottom()}, {@link #getPaddingStart()}, + * {@link #getPaddingEnd()}. * </p> * * <p> @@ -627,6 +636,8 @@ import java.util.concurrent.CopyOnWriteArrayList; * @attr ref android.R.styleable#View_paddingLeft * @attr ref android.R.styleable#View_paddingRight * @attr ref android.R.styleable#View_paddingTop + * @attr ref android.R.styleable#View_paddingStart + * @attr ref android.R.styleable#View_paddingEnd * @attr ref android.R.styleable#View_saveEnabled * @attr ref android.R.styleable#View_rotation * @attr ref android.R.styleable#View_rotationX @@ -648,6 +659,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack * @attr ref android.R.styleable#View_soundEffectsEnabled * @attr ref android.R.styleable#View_tag + * @attr ref android.R.styleable#View_textAlignment * @attr ref android.R.styleable#View_transformPivotX * @attr ref android.R.styleable#View_transformPivotY * @attr ref android.R.styleable#View_translationX @@ -656,7 +668,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * * @see android.view.ViewGroup */ -public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback, +public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { private static final boolean DBG = false; @@ -1003,14 +1015,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int FOCUSABLES_TOUCH_MODE = 0x00000001; /** - * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} - * should add only accessibility focusable Views. - * - * @hide - */ - public static final int FOCUSABLES_ACCESSIBILITY = 0x00000002; - - /** * Use with {@link #focusSearch(int)}. Move focus to the previous selectable * item. */ @@ -1042,58 +1046,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public static final int FOCUS_DOWN = 0x00000082; - // Accessibility focus directions. - - /** - * The accessibility focus which is the current user position when - * interacting with the accessibility framework. - * - * @hide - */ - public static final int FOCUS_ACCESSIBILITY = 0x00001000; - - /** - * Use with {@link #focusSearch(int)}. Move acessibility focus left. - * - * @hide - */ - public static final int ACCESSIBILITY_FOCUS_LEFT = FOCUS_LEFT | FOCUS_ACCESSIBILITY; - - /** - * Use with {@link #focusSearch(int)}. Move acessibility focus up. - * - * @hide - */ - public static final int ACCESSIBILITY_FOCUS_UP = FOCUS_UP | FOCUS_ACCESSIBILITY; - - /** - * Use with {@link #focusSearch(int)}. Move acessibility focus right. - * - * @hide - */ - public static final int ACCESSIBILITY_FOCUS_RIGHT = FOCUS_RIGHT | FOCUS_ACCESSIBILITY; - - /** - * Use with {@link #focusSearch(int)}. Move acessibility focus down. - * - * @hide - */ - public static final int ACCESSIBILITY_FOCUS_DOWN = FOCUS_DOWN | FOCUS_ACCESSIBILITY; - - /** - * Use with {@link #focusSearch(int)}. Move acessibility focus forward. - * - * @hide - */ - public static final int ACCESSIBILITY_FOCUS_FORWARD = FOCUS_FORWARD | FOCUS_ACCESSIBILITY; - - /** - * Use with {@link #focusSearch(int)}. Move acessibility focus backward. - * - * @hide - */ - public static final int ACCESSIBILITY_FOCUS_BACKWARD = FOCUS_BACKWARD | FOCUS_ACCESSIBILITY; - /** * Bits of {@link #getMeasuredWidthAndState()} and * {@link #getMeasuredWidthAndState()} that provide the actual measured size. @@ -1557,7 +1509,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private SparseArray<Object> mKeyedTags; /** - * The next available accessiiblity id. + * The next available accessibility id. */ private static int sNextAccessibilityViewId; @@ -1623,17 +1575,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // for mPrivateFlags: /** {@hide} */ - static final int WANTS_FOCUS = 0x00000001; + static final int PFLAG_WANTS_FOCUS = 0x00000001; /** {@hide} */ - static final int FOCUSED = 0x00000002; + static final int PFLAG_FOCUSED = 0x00000002; /** {@hide} */ - static final int SELECTED = 0x00000004; + static final int PFLAG_SELECTED = 0x00000004; /** {@hide} */ - static final int IS_ROOT_NAMESPACE = 0x00000008; + static final int PFLAG_IS_ROOT_NAMESPACE = 0x00000008; /** {@hide} */ - static final int HAS_BOUNDS = 0x00000010; + static final int PFLAG_HAS_BOUNDS = 0x00000010; /** {@hide} */ - static final int DRAWN = 0x00000020; + static final int PFLAG_DRAWN = 0x00000020; /** * When this flag is set, this view is running an animation on behalf of its * children and should therefore not cancel invalidate requests, even if they @@ -1641,58 +1593,58 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * {@hide} */ - static final int DRAW_ANIMATION = 0x00000040; + static final int PFLAG_DRAW_ANIMATION = 0x00000040; /** {@hide} */ - static final int SKIP_DRAW = 0x00000080; + static final int PFLAG_SKIP_DRAW = 0x00000080; /** {@hide} */ - static final int ONLY_DRAWS_BACKGROUND = 0x00000100; + static final int PFLAG_ONLY_DRAWS_BACKGROUND = 0x00000100; /** {@hide} */ - static final int REQUEST_TRANSPARENT_REGIONS = 0x00000200; + static final int PFLAG_REQUEST_TRANSPARENT_REGIONS = 0x00000200; /** {@hide} */ - static final int DRAWABLE_STATE_DIRTY = 0x00000400; + static final int PFLAG_DRAWABLE_STATE_DIRTY = 0x00000400; /** {@hide} */ - static final int MEASURED_DIMENSION_SET = 0x00000800; + static final int PFLAG_MEASURED_DIMENSION_SET = 0x00000800; /** {@hide} */ - static final int FORCE_LAYOUT = 0x00001000; + static final int PFLAG_FORCE_LAYOUT = 0x00001000; /** {@hide} */ - static final int LAYOUT_REQUIRED = 0x00002000; + static final int PFLAG_LAYOUT_REQUIRED = 0x00002000; - private static final int PRESSED = 0x00004000; + private static final int PFLAG_PRESSED = 0x00004000; /** {@hide} */ - static final int DRAWING_CACHE_VALID = 0x00008000; + static final int PFLAG_DRAWING_CACHE_VALID = 0x00008000; /** * Flag used to indicate that this view should be drawn once more (and only once * more) after its animation has completed. * {@hide} */ - static final int ANIMATION_STARTED = 0x00010000; + static final int PFLAG_ANIMATION_STARTED = 0x00010000; - private static final int SAVE_STATE_CALLED = 0x00020000; + private static final int PFLAG_SAVE_STATE_CALLED = 0x00020000; /** * Indicates that the View returned true when onSetAlpha() was called and that * the alpha must be restored. * {@hide} */ - static final int ALPHA_SET = 0x00040000; + static final int PFLAG_ALPHA_SET = 0x00040000; /** * Set by {@link #setScrollContainer(boolean)}. */ - static final int SCROLL_CONTAINER = 0x00080000; + static final int PFLAG_SCROLL_CONTAINER = 0x00080000; /** * Set by {@link #setScrollContainer(boolean)}. */ - static final int SCROLL_CONTAINER_ADDED = 0x00100000; + static final int PFLAG_SCROLL_CONTAINER_ADDED = 0x00100000; /** * View flag indicating whether this view was invalidated (fully or partially.) * * @hide */ - static final int DIRTY = 0x00200000; + static final int PFLAG_DIRTY = 0x00200000; /** * View flag indicating whether this view was invalidated by an opaque @@ -1700,35 +1652,35 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @hide */ - static final int DIRTY_OPAQUE = 0x00400000; + static final int PFLAG_DIRTY_OPAQUE = 0x00400000; /** - * Mask for {@link #DIRTY} and {@link #DIRTY_OPAQUE}. + * Mask for {@link #PFLAG_DIRTY} and {@link #PFLAG_DIRTY_OPAQUE}. * * @hide */ - static final int DIRTY_MASK = 0x00600000; + static final int PFLAG_DIRTY_MASK = 0x00600000; /** * Indicates whether the background is opaque. * * @hide */ - static final int OPAQUE_BACKGROUND = 0x00800000; + static final int PFLAG_OPAQUE_BACKGROUND = 0x00800000; /** * Indicates whether the scrollbars are opaque. * * @hide */ - static final int OPAQUE_SCROLLBARS = 0x01000000; + static final int PFLAG_OPAQUE_SCROLLBARS = 0x01000000; /** * Indicates whether the view is opaque. * * @hide */ - static final int OPAQUE_MASK = 0x01800000; + static final int PFLAG_OPAQUE_MASK = 0x01800000; /** * Indicates a prepressed state; @@ -1738,27 +1690,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @hide */ - private static final int PREPRESSED = 0x02000000; + private static final int PFLAG_PREPRESSED = 0x02000000; /** * Indicates whether the view is temporarily detached. * * @hide */ - static final int CANCEL_NEXT_UP_EVENT = 0x04000000; + static final int PFLAG_CANCEL_NEXT_UP_EVENT = 0x04000000; /** * Indicates that we should awaken scroll bars once attached * * @hide */ - private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000; + private static final int PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000; /** * Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT. * @hide */ - private static final int HOVERED = 0x10000000; + private static final int PFLAG_HOVERED = 0x10000000; /** * Indicates that pivotX or pivotY were explicitly set and we should not assume the center @@ -1766,10 +1718,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @hide */ - private static final int PIVOT_EXPLICITLY_SET = 0x20000000; + private static final int PFLAG_PIVOT_EXPLICITLY_SET = 0x20000000; /** {@hide} */ - static final int ACTIVATED = 0x40000000; + static final int PFLAG_ACTIVATED = 0x40000000; /** * Indicates that this view was specifically invalidated, not just dirtied because some @@ -1779,16 +1731,60 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @hide */ - static final int INVALIDATED = 0x80000000; - - /* Masks for mPrivateFlags2 */ + static final int PFLAG_INVALIDATED = 0x80000000; + + /** + * Masks for mPrivateFlags2, as generated by dumpFlags(): + * + * -------|-------|-------|-------| + * PFLAG2_TEXT_ALIGNMENT_FLAGS[0] + * PFLAG2_TEXT_DIRECTION_FLAGS[0] + * 1 PFLAG2_DRAG_CAN_ACCEPT + * 1 PFLAG2_DRAG_HOVERED + * 1 PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT + * 11 PFLAG2_TEXT_DIRECTION_MASK_SHIFT + * 1 1 PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT + * 11 PFLAG2_LAYOUT_DIRECTION_MASK + * 11 1 PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT + * 1 PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL + * 1 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT + * 1 1 PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT + * 1 PFLAG2_LAYOUT_DIRECTION_RESOLVED + * 11 PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK + * 1 PFLAG2_TEXT_DIRECTION_FLAGS[1] + * 1 PFLAG2_TEXT_DIRECTION_FLAGS[2] + * 11 PFLAG2_TEXT_DIRECTION_FLAGS[3] + * 1 PFLAG2_TEXT_DIRECTION_FLAGS[4] + * 1 1 PFLAG2_TEXT_DIRECTION_FLAGS[5] + * 111 PFLAG2_TEXT_DIRECTION_MASK + * 1 PFLAG2_TEXT_DIRECTION_RESOLVED + * 1 PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT + * 111 PFLAG2_TEXT_DIRECTION_RESOLVED_MASK + * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[1] + * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[2] + * 11 PFLAG2_TEXT_ALIGNMENT_FLAGS[3] + * 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[4] + * 1 1 PFLAG2_TEXT_ALIGNMENT_FLAGS[5] + * 11 PFLAG2_TEXT_ALIGNMENT_FLAGS[6] + * 111 PFLAG2_TEXT_ALIGNMENT_MASK + * 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED + * 1 PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT + * 111 PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK + * 11 PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK + * 1 PFLAG2_HAS_TRANSIENT_STATE + * 1 PFLAG2_ACCESSIBILITY_FOCUSED + * 1 PFLAG2_ACCESSIBILITY_STATE_CHANGED + * 1 PFLAG2_VIEW_QUICK_REJECTED + * 1 PFLAG2_PADDING_RESOLVED + * -------|-------|-------|-------| + */ /** * Indicates that this view has reported that it can accept the current drag's content. * Cleared when the drag operation concludes. * @hide */ - static final int DRAG_CAN_ACCEPT = 0x00000001; + static final int PFLAG2_DRAG_CAN_ACCEPT = 0x00000001; /** * Indicates that this view is currently directly under the drag location in a @@ -1796,33 +1792,29 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * the drag exits the view, or when the drag operation concludes. * @hide */ - static final int DRAG_HOVERED = 0x00000002; + static final int PFLAG2_DRAG_HOVERED = 0x00000002; /** * Horizontal layout direction of this view is from Left to Right. * Use with {@link #setLayoutDirection}. - * @hide */ public static final int LAYOUT_DIRECTION_LTR = 0; /** * Horizontal layout direction of this view is from Right to Left. * Use with {@link #setLayoutDirection}. - * @hide */ public static final int LAYOUT_DIRECTION_RTL = 1; /** * Horizontal layout direction of this view is inherited from its parent. * Use with {@link #setLayoutDirection}. - * @hide */ public static final int LAYOUT_DIRECTION_INHERIT = 2; /** * Horizontal layout direction of this view is from deduced from the default language * script for the locale. Use with {@link #setLayoutDirection}. - * @hide */ public static final int LAYOUT_DIRECTION_LOCALE = 3; @@ -1830,32 +1822,33 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) * @hide */ - static final int LAYOUT_DIRECTION_MASK_SHIFT = 2; + static final int PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT = 2; /** * Mask for use with private flags indicating bits used for horizontal layout direction. * @hide */ - static final int LAYOUT_DIRECTION_MASK = 0x00000003 << LAYOUT_DIRECTION_MASK_SHIFT; + static final int PFLAG2_LAYOUT_DIRECTION_MASK = 0x00000003 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT; /** * Indicates whether the view horizontal layout direction has been resolved and drawn to the * right-to-left direction. * @hide */ - static final int LAYOUT_DIRECTION_RESOLVED_RTL = 4 << LAYOUT_DIRECTION_MASK_SHIFT; + static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL = 4 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT; /** * Indicates whether the view horizontal layout direction has been resolved. * @hide */ - static final int LAYOUT_DIRECTION_RESOLVED = 8 << LAYOUT_DIRECTION_MASK_SHIFT; + static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED = 8 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT; /** * Mask for use with private flags indicating bits used for resolved horizontal layout direction. * @hide */ - static final int LAYOUT_DIRECTION_RESOLVED_MASK = 0x0000000C << LAYOUT_DIRECTION_MASK_SHIFT; + static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK = 0x0000000C + << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT; /* * Array of horizontal layout direction flags for mapping attribute "layoutDirection" to correct @@ -1882,12 +1875,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @hide */ - static final int HAS_TRANSIENT_STATE = 0x00000100; - + static final int PFLAG2_HAS_TRANSIENT_STATE = 0x1 << 22; /** * Text direction is inherited thru {@link ViewGroup} - * @hide */ public static final int TEXT_DIRECTION_INHERIT = 0; @@ -1895,7 +1886,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Text direction is using "first strong algorithm". The first strong directional character * determines the paragraph direction. If there is no strong directional character, the * paragraph direction is the view's resolved layout direction. - * @hide */ public static final int TEXT_DIRECTION_FIRST_STRONG = 1; @@ -1903,89 +1893,86 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains * any strong RTL character, otherwise it is LTR if it contains any strong LTR characters. * If there are neither, the paragraph direction is the view's resolved layout direction. - * @hide */ public static final int TEXT_DIRECTION_ANY_RTL = 2; /** * Text direction is forced to LTR. - * @hide */ public static final int TEXT_DIRECTION_LTR = 3; /** * Text direction is forced to RTL. - * @hide */ public static final int TEXT_DIRECTION_RTL = 4; /** * Text direction is coming from the system Locale. - * @hide */ public static final int TEXT_DIRECTION_LOCALE = 5; /** * Default text direction is inherited - * @hide */ - protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; + public static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; /** * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) * @hide */ - static final int TEXT_DIRECTION_MASK_SHIFT = 6; + static final int PFLAG2_TEXT_DIRECTION_MASK_SHIFT = 6; /** * Mask for use with private flags indicating bits used for text direction. * @hide */ - static final int TEXT_DIRECTION_MASK = 0x00000007 << TEXT_DIRECTION_MASK_SHIFT; + static final int PFLAG2_TEXT_DIRECTION_MASK = 0x00000007 + << PFLAG2_TEXT_DIRECTION_MASK_SHIFT; /** * Array of text direction flags for mapping attribute "textDirection" to correct * flag value. * @hide */ - private static final int[] TEXT_DIRECTION_FLAGS = { - TEXT_DIRECTION_INHERIT << TEXT_DIRECTION_MASK_SHIFT, - TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_MASK_SHIFT, - TEXT_DIRECTION_ANY_RTL << TEXT_DIRECTION_MASK_SHIFT, - TEXT_DIRECTION_LTR << TEXT_DIRECTION_MASK_SHIFT, - TEXT_DIRECTION_RTL << TEXT_DIRECTION_MASK_SHIFT, - TEXT_DIRECTION_LOCALE << TEXT_DIRECTION_MASK_SHIFT + private static final int[] PFLAG2_TEXT_DIRECTION_FLAGS = { + TEXT_DIRECTION_INHERIT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_FIRST_STRONG << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_ANY_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_LOCALE << PFLAG2_TEXT_DIRECTION_MASK_SHIFT }; /** * Indicates whether the view text direction has been resolved. * @hide */ - static final int TEXT_DIRECTION_RESOLVED = 0x00000008 << TEXT_DIRECTION_MASK_SHIFT; + static final int PFLAG2_TEXT_DIRECTION_RESOLVED = 0x00000008 + << PFLAG2_TEXT_DIRECTION_MASK_SHIFT; /** * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) * @hide */ - static final int TEXT_DIRECTION_RESOLVED_MASK_SHIFT = 10; + static final int PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT = 10; /** * Mask for use with private flags indicating bits used for resolved text direction. * @hide */ - static final int TEXT_DIRECTION_RESOLVED_MASK = 0x00000007 << TEXT_DIRECTION_RESOLVED_MASK_SHIFT; + static final int PFLAG2_TEXT_DIRECTION_RESOLVED_MASK = 0x00000007 + << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT; /** * Indicates whether the view text direction has been resolved to the "first strong" heuristic. * @hide */ - static final int TEXT_DIRECTION_RESOLVED_DEFAULT = - TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_RESOLVED_MASK_SHIFT; + static final int PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT = + TEXT_DIRECTION_FIRST_STRONG << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT; /* * Default text alignment. The text alignment of this View is inherited from its parent. * Use with {@link #setTextAlignment(int)} - * @hide */ public static final int TEXT_ALIGNMENT_INHERIT = 0; @@ -1994,7 +1981,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s text direction. * * Use with {@link #setTextAlignment(int)} - * @hide */ public static final int TEXT_ALIGNMENT_GRAVITY = 1; @@ -2002,7 +1988,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Align to the start of the paragraph, e.g. ALIGN_NORMAL. * * Use with {@link #setTextAlignment(int)} - * @hide */ public static final int TEXT_ALIGNMENT_TEXT_START = 2; @@ -2010,7 +1995,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Align to the end of the paragraph, e.g. ALIGN_OPPOSITE. * * Use with {@link #setTextAlignment(int)} - * @hide */ public static final int TEXT_ALIGNMENT_TEXT_END = 3; @@ -2018,7 +2002,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Center the paragraph, e.g. ALIGN_CENTER. * * Use with {@link #setTextAlignment(int)} - * @hide */ public static final int TEXT_ALIGNMENT_CENTER = 4; @@ -2027,7 +2010,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * layoutDirection is LTR, and ALIGN_RIGHT otherwise. * * Use with {@link #setTextAlignment(int)} - * @hide */ public static final int TEXT_ALIGNMENT_VIEW_START = 5; @@ -2036,66 +2018,65 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * layoutDirection is LTR, and ALIGN_LEFT otherwise. * * Use with {@link #setTextAlignment(int)} - * @hide */ public static final int TEXT_ALIGNMENT_VIEW_END = 6; /** * Default text alignment is inherited - * @hide */ - protected static int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY; + public static int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY; /** * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) * @hide */ - static final int TEXT_ALIGNMENT_MASK_SHIFT = 13; + static final int PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT = 13; /** * Mask for use with private flags indicating bits used for text alignment. * @hide */ - static final int TEXT_ALIGNMENT_MASK = 0x00000007 << TEXT_ALIGNMENT_MASK_SHIFT; + static final int PFLAG2_TEXT_ALIGNMENT_MASK = 0x00000007 << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT; /** * Array of text direction flags for mapping attribute "textAlignment" to correct * flag value. * @hide */ - private static final int[] TEXT_ALIGNMENT_FLAGS = { - TEXT_ALIGNMENT_INHERIT << TEXT_ALIGNMENT_MASK_SHIFT, - TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_MASK_SHIFT, - TEXT_ALIGNMENT_TEXT_START << TEXT_ALIGNMENT_MASK_SHIFT, - TEXT_ALIGNMENT_TEXT_END << TEXT_ALIGNMENT_MASK_SHIFT, - TEXT_ALIGNMENT_CENTER << TEXT_ALIGNMENT_MASK_SHIFT, - TEXT_ALIGNMENT_VIEW_START << TEXT_ALIGNMENT_MASK_SHIFT, - TEXT_ALIGNMENT_VIEW_END << TEXT_ALIGNMENT_MASK_SHIFT + private static final int[] PFLAG2_TEXT_ALIGNMENT_FLAGS = { + TEXT_ALIGNMENT_INHERIT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_GRAVITY << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_TEXT_START << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_TEXT_END << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_CENTER << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_VIEW_START << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_VIEW_END << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT }; /** * Indicates whether the view text alignment has been resolved. * @hide */ - static final int TEXT_ALIGNMENT_RESOLVED = 0x00000008 << TEXT_ALIGNMENT_MASK_SHIFT; + static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED = 0x00000008 << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT; /** * Bit shift to get the resolved text alignment. * @hide */ - static final int TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17; + static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17; /** * Mask for use with private flags indicating bits used for text alignment. * @hide */ - static final int TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007 << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007 + << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; /** * Indicates whether if the view text alignment has been resolved to gravity */ - public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = - TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + private static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT = + TEXT_ALIGNMENT_GRAVITY << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; // Accessiblity constants for mPrivateFlags2 @@ -2103,7 +2084,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Shift for the bits in {@link #mPrivateFlags2} related to the * "importantForAccessibility" attribute. */ - static final int IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20; + static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20; /** * Automatically determine whether a view is important for accessibility. @@ -2121,7 +2102,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002; /** - * The default whether the view is important for accessiblity. + * The default whether the view is important for accessibility. */ static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO; @@ -2129,92 +2110,52 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Mask for obtainig the bits which specify how to determine * whether a view is important for accessibility. */ - static final int IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO + static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO | IMPORTANT_FOR_ACCESSIBILITY_YES | IMPORTANT_FOR_ACCESSIBILITY_NO) - << IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; /** * Flag indicating whether a view has accessibility focus. */ - static final int ACCESSIBILITY_FOCUSED = 0x00000040 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + static final int PFLAG2_ACCESSIBILITY_FOCUSED = 0x00000040 << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; /** * Flag indicating whether a view state for accessibility has changed. */ - static final int ACCESSIBILITY_STATE_CHANGED = 0x00000080 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + static final int PFLAG2_ACCESSIBILITY_STATE_CHANGED = 0x00000080 + << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; /** * Flag indicating whether a view failed the quickReject() check in draw(). This condition * is used to check whether later changes to the view's transform should invalidate the * view to force the quickReject test to run again. */ - static final int VIEW_QUICK_REJECTED = 0x10000000; - - // Accessiblity constants for mPrivateFlags2 + static final int PFLAG2_VIEW_QUICK_REJECTED = 0x10000000; /** - * Shift for the bits in {@link #mPrivateFlags2} related to the - * "accessibilityFocusable" attribute. + * Flag indicating that start/end padding has been resolved into left/right padding + * for use in measurement, layout, drawing, etc. This is set by {@link #resolvePadding()} + * and checked by {@link #measure(int, int)} to determine if padding needs to be resolved + * during measurement. In some special cases this is required such as when an adapter-based + * view measures prospective children without attaching them to a window. */ - static final int ACCESSIBILITY_FOCUSABLE_SHIFT = 29; + static final int PFLAG2_PADDING_RESOLVED = 0x20000000; /** - * The system determines whether the view can take accessibility focus - default (recommended). - * <p> - * Such a view is consideted by the focus search if it is: - * <ul> - * <li> - * Important for accessibility and actionable (clickable, long clickable, focusable) - * </li> - * <li> - * Important for accessibility, not actionable (clickable, long clickable, focusable), - * and does not have an actionable predecessor. - * </li> - * </ul> - * An accessibility srvice can request putting accessibility focus on such a view. - * </p> - * - * @hide + * Flag indicating that the start/end drawables has been resolved into left/right ones. */ - public static final int ACCESSIBILITY_FOCUSABLE_AUTO = 0x00000000; + static final int PFLAG2_DRAWABLE_RESOLVED = 0x40000000; /** - * The view can take accessibility focus. - * <p> - * A view that can take accessibility focus is always considered during focus - * search and an accessibility service can request putting accessibility focus - * on it. - * </p> - * - * @hide - */ - public static final int ACCESSIBILITY_FOCUSABLE_YES = 0x00000001; - - /** - * The view can not take accessibility focus. - * <p> - * A view that can not take accessibility focus is never considered during focus - * search and an accessibility service can not request putting accessibility focus - * on it. - * </p> - * - * @hide + * Group of bits indicating that RTL properties resolution is done. */ - public static final int ACCESSIBILITY_FOCUSABLE_NO = 0x00000002; - - /** - * The default whether the view is accessiblity focusable. - */ - static final int ACCESSIBILITY_FOCUSABLE_DEFAULT = ACCESSIBILITY_FOCUSABLE_AUTO; - - /** - * Mask for obtainig the bits which specifies how to determine - * whether a view is accessibility focusable. - */ - static final int ACCESSIBILITY_FOCUSABLE_MASK = (ACCESSIBILITY_FOCUSABLE_AUTO - | ACCESSIBILITY_FOCUSABLE_YES | ACCESSIBILITY_FOCUSABLE_NO) - << ACCESSIBILITY_FOCUSABLE_SHIFT; + static final int ALL_RTL_PROPERTIES_RESOLVED = PFLAG2_LAYOUT_DIRECTION_RESOLVED | + PFLAG2_TEXT_DIRECTION_RESOLVED | + PFLAG2_TEXT_ALIGNMENT_RESOLVED | + PFLAG2_PADDING_RESOLVED | + PFLAG2_DRAWABLE_RESOLVED; + // There are a couple of flags left in mPrivateFlags2 /* End of masks for mPrivateFlags2 */ @@ -2225,19 +2166,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * an animation is cleared between successive frames, in order to tell the associated * DisplayList to clear its animation matrix. */ - static final int VIEW_IS_ANIMATING_TRANSFORM = 0x1; + static final int PFLAG3_VIEW_IS_ANIMATING_TRANSFORM = 0x1; /** * Flag indicating that view has an alpha animation set on it. This is used to track whether an * animation is cleared between successive frames, in order to tell the associated * DisplayList to restore its alpha value. */ - static final int VIEW_IS_ANIMATING_ALPHA = 0x2; + static final int PFLAG3_VIEW_IS_ANIMATING_ALPHA = 0x2; /* End of masks for mPrivateFlags3 */ - static final int DRAG_MASK = DRAG_CAN_ACCEPT | DRAG_HOVERED; + static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED; /** * Always allow a user to over-scroll this view, provided it is a @@ -2604,16 +2545,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@hide} */ @ViewDebug.ExportedProperty(flagMapping = { - @ViewDebug.FlagToString(mask = FORCE_LAYOUT, equals = FORCE_LAYOUT, + @ViewDebug.FlagToString(mask = PFLAG_FORCE_LAYOUT, equals = PFLAG_FORCE_LAYOUT, name = "FORCE_LAYOUT"), - @ViewDebug.FlagToString(mask = LAYOUT_REQUIRED, equals = LAYOUT_REQUIRED, + @ViewDebug.FlagToString(mask = PFLAG_LAYOUT_REQUIRED, equals = PFLAG_LAYOUT_REQUIRED, name = "LAYOUT_REQUIRED"), - @ViewDebug.FlagToString(mask = DRAWING_CACHE_VALID, equals = DRAWING_CACHE_VALID, + @ViewDebug.FlagToString(mask = PFLAG_DRAWING_CACHE_VALID, equals = PFLAG_DRAWING_CACHE_VALID, name = "DRAWING_CACHE_INVALID", outputIf = false), - @ViewDebug.FlagToString(mask = DRAWN, equals = DRAWN, name = "DRAWN", outputIf = true), - @ViewDebug.FlagToString(mask = DRAWN, equals = DRAWN, name = "NOT_DRAWN", outputIf = false), - @ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY_OPAQUE, name = "DIRTY_OPAQUE"), - @ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY, name = "DIRTY") + @ViewDebug.FlagToString(mask = PFLAG_DRAWN, equals = PFLAG_DRAWN, name = "DRAWN", outputIf = true), + @ViewDebug.FlagToString(mask = PFLAG_DRAWN, equals = PFLAG_DRAWN, name = "NOT_DRAWN", outputIf = false), + @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY_OPAQUE, name = "DIRTY_OPAQUE"), + @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY, name = "DIRTY") }) int mPrivateFlags; int mPrivateFlags2; @@ -2846,14 +2787,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@hide} */ @ViewDebug.ExportedProperty(category = "padding") - protected int mPaddingLeft; + protected int mPaddingLeft = 0; /** * The right padding in pixels, that is the distance in pixels between the * right edge of this view and the right edge of its content. * {@hide} */ @ViewDebug.ExportedProperty(category = "padding") - protected int mPaddingRight; + protected int mPaddingRight = 0; /** * The top padding in pixels, that is the distance in pixels between the * top edge of this view and the top edge of its content. @@ -2881,6 +2822,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private CharSequence mContentDescription; /** + * Specifies the id of a view for which this view serves as a label for + * accessibility purposes. + */ + private int mLabelForId = View.NO_ID; + + /** + * Predicate for matching labeled view id with its label for + * accessibility purposes. + */ + private MatchLabelForPredicate mMatchLabelForPredicate; + + /** + * Predicate for matching a view by its id. + */ + private MatchIdPredicate mMatchIdPredicate; + + /** * Cache the paddingRight set by the user to append to the scrollbar's size. * * @hide @@ -2905,13 +2863,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal protected int mUserPaddingLeft; /** - * Cache if the user padding is relative. - * - */ - @ViewDebug.ExportedProperty(category = "padding") - boolean mUserPaddingRelative; - - /** * Cache the paddingStart set by the user to append to the scrollbar's size. * */ @@ -2926,6 +2877,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int mUserPaddingEnd; /** + * Cache initial left padding. + * + * @hide + */ + int mUserPaddingLeftInitial = 0; + + /** + * Cache initial right padding. + * + * @hide + */ + int mUserPaddingRightInitial = 0; + + /** + * Default undefined padding + */ + private static final int UNDEFINED_PADDING = Integer.MIN_VALUE; + + /** * @hide */ int mOldWidthMeasureSpec = Integer.MIN_VALUE; @@ -3218,6 +3188,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private boolean mSendingHoverAccessibilityEvents; /** + * Delegate for injecting accessibility functionality. + */ + AccessibilityDelegate mAccessibilityDelegate; + + /** + * Consistency verifier for debugging purposes. + * @hide + */ + protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() ? + new InputEventConsistencyVerifier(this, 0) : null; + + private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); + + /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -3228,32 +3213,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mResources = context != null ? context.getResources() : null; mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; // Set layout and text direction defaults - mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) | - (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT) | - (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT) | - (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << IMPORTANT_FOR_ACCESSIBILITY_SHIFT) | - (ACCESSIBILITY_FOCUSABLE_DEFAULT << ACCESSIBILITY_FOCUSABLE_SHIFT); + mPrivateFlags2 = + (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) | + (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) | + (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) | + (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) | + (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) | + (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); - mUserPaddingStart = -1; - mUserPaddingEnd = -1; - mUserPaddingRelative = false; + mUserPaddingStart = UNDEFINED_PADDING; + mUserPaddingEnd = UNDEFINED_PADDING; } /** - * Delegate for injecting accessiblity functionality. - */ - AccessibilityDelegate mAccessibilityDelegate; - - /** - * Consistency verifier for debugging purposes. - * @hide - */ - protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier = - InputEventConsistencyVerifier.isInstrumentationEnabled() ? - new InputEventConsistencyVerifier(this, 0) : null; - - /** * Constructor that is called when inflating a view from XML. This is called * when a view is being constructed from an XML file, supplying attributes * that were specified in the XML file. This version uses a default style of @@ -3303,8 +3276,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int topPadding = -1; int rightPadding = -1; int bottomPadding = -1; - int startPadding = -1; - int endPadding = -1; + int startPadding = UNDEFINED_PADDING; + int endPadding = UNDEFINED_PADDING; int padding = -1; @@ -3326,8 +3299,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal boolean transformSet = false; int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; - int overScrollMode = mOverScrollMode; + boolean initializeScrollbars = false; + + boolean leftPaddingDefined = false; + boolean rightPaddingDefined = false; + boolean startPaddingDefined = false; + boolean endPaddingDefined = false; + + final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; + final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); @@ -3337,24 +3318,34 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal break; case com.android.internal.R.styleable.View_padding: padding = a.getDimensionPixelSize(attr, -1); + mUserPaddingLeftInitial = padding; + mUserPaddingRightInitial = padding; + leftPaddingDefined = true; + rightPaddingDefined = true; break; case com.android.internal.R.styleable.View_paddingLeft: leftPadding = a.getDimensionPixelSize(attr, -1); + mUserPaddingLeftInitial = leftPadding; + leftPaddingDefined = true; break; case com.android.internal.R.styleable.View_paddingTop: topPadding = a.getDimensionPixelSize(attr, -1); break; case com.android.internal.R.styleable.View_paddingRight: rightPadding = a.getDimensionPixelSize(attr, -1); + mUserPaddingRightInitial = rightPadding; + rightPaddingDefined = true; break; case com.android.internal.R.styleable.View_paddingBottom: bottomPadding = a.getDimensionPixelSize(attr, -1); break; case com.android.internal.R.styleable.View_paddingStart: - startPadding = a.getDimensionPixelSize(attr, -1); + startPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING); + startPaddingDefined = true; break; case com.android.internal.R.styleable.View_paddingEnd: - endPadding = a.getDimensionPixelSize(attr, -1); + endPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING); + endPaddingDefined = true; break; case com.android.internal.R.styleable.View_scrollX: x = a.getDimensionPixelOffset(attr, 0); @@ -3456,12 +3447,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal break; case com.android.internal.R.styleable.View_layoutDirection: // Clear any layout direction flags (included resolved bits) already set - mPrivateFlags2 &= ~(LAYOUT_DIRECTION_MASK | LAYOUT_DIRECTION_RESOLVED_MASK); + mPrivateFlags2 &= + ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK); // Set the layout direction flags depending on the value of the attribute final int layoutDirection = a.getInt(attr, -1); final int value = (layoutDirection != -1) ? LAYOUT_DIRECTION_FLAGS[layoutDirection] : LAYOUT_DIRECTION_DEFAULT; - mPrivateFlags2 |= (value << LAYOUT_DIRECTION_MASK_SHIFT); + mPrivateFlags2 |= (value << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT); break; case com.android.internal.R.styleable.View_drawingCacheQuality: final int cacheQuality = a.getInt(attr, 0); @@ -3473,6 +3465,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal case com.android.internal.R.styleable.View_contentDescription: setContentDescription(a.getString(attr)); break; + case com.android.internal.R.styleable.View_labelFor: + setLabelFor(a.getResourceId(attr, NO_ID)); + break; case com.android.internal.R.styleable.View_soundEffectsEnabled: if (!a.getBoolean(attr, true)) { viewFlagValues &= ~SOUND_EFFECTS_ENABLED; @@ -3490,12 +3485,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (scrollbars != SCROLLBARS_NONE) { viewFlagValues |= scrollbars; viewFlagMasks |= SCROLLBARS_MASK; - initializeScrollbars(a); + initializeScrollbars = true; } break; //noinspection deprecation case R.styleable.View_fadingEdge: - if (context.getApplicationInfo().targetSdkVersion >= ICE_CREAM_SANDWICH) { + if (targetSdkVersion >= ICE_CREAM_SANDWICH) { // Ignore the attribute starting with ICS break; } @@ -3606,19 +3601,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal break; case R.styleable.View_textDirection: // Clear any text direction flag already set - mPrivateFlags2 &= ~TEXT_DIRECTION_MASK; + mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK; // Set the text direction flags depending on the value of the attribute final int textDirection = a.getInt(attr, -1); if (textDirection != -1) { - mPrivateFlags2 |= TEXT_DIRECTION_FLAGS[textDirection]; + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_FLAGS[textDirection]; } break; case R.styleable.View_textAlignment: // Clear any text alignment flag already set - mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK; + mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK; // Set the text alignment flag depending on the value of the attribute final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT); - mPrivateFlags2 |= TEXT_ALIGNMENT_FLAGS[textAlignment]; + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_FLAGS[textAlignment]; break; case R.styleable.View_importantForAccessibility: setImportantForAccessibility(a.getInt(attr, @@ -3627,41 +3622,78 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - a.recycle(); - setOverScrollMode(overScrollMode); - if (background != null) { - setBackground(background); - } - - // 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. + // Cache start/end 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 (background != null) { + setBackground(background); + } if (padding >= 0) { leftPadding = padding; topPadding = padding; rightPadding = padding; bottomPadding = padding; + mUserPaddingLeftInitial = padding; + mUserPaddingRightInitial = padding; + } + + if (isRtlCompatibilityMode()) { + // RTL compatibility mode: pre Jelly Bean MR1 case OR no RTL support case. + // left / right padding are used if defined (meaning here nothing to do). If they are not + // defined and start / end padding are defined (e.g. in Frameworks resources), then we use + // start / end and resolve them as left / right (layout direction is not taken into account). + // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial + // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if + // defined. + if (!leftPaddingDefined && startPaddingDefined) { + leftPadding = startPadding; + } + mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial; + if (!rightPaddingDefined && endPaddingDefined) { + rightPadding = endPadding; + } + mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial; + } else { + // Jelly Bean MR1 and after case: if start/end defined, they will override any left/right + // values defined. Otherwise, left /right values are used. + // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial + // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if + // defined. + if (startPaddingDefined) { + mUserPaddingLeftInitial = startPadding; + } else if (leftPaddingDefined) { + mUserPaddingLeftInitial = leftPadding; + } + if (endPaddingDefined) { + mUserPaddingRightInitial = endPadding; + } + else if (rightPaddingDefined) { + mUserPaddingRightInitial = rightPadding; + } } - // If the user specified the padding (either with android:padding or - // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise - // use the default padding or the padding from the background drawable - // (stored at this point in mPadding*) - setPadding(leftPadding >= 0 ? leftPadding : mPaddingLeft, + internalSetPadding( + mUserPaddingLeftInitial, topPadding >= 0 ? topPadding : mPaddingTop, - rightPadding >= 0 ? rightPadding : mPaddingRight, + mUserPaddingRightInitial, bottomPadding >= 0 ? bottomPadding : mPaddingBottom); if (viewFlagMasks != 0) { setFlags(viewFlagValues, viewFlagMasks); } + if (initializeScrollbars) { + initializeScrollbars(a); + } + + a.recycle(); + // Needs to be called after mViewFlags is set if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) { recomputePadding(); @@ -3688,10 +3720,6 @@ 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 */ @@ -3699,6 +3727,81 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mResources = null; } + public String toString() { + StringBuilder out = new StringBuilder(128); + out.append(getClass().getName()); + out.append('{'); + out.append(Integer.toHexString(System.identityHashCode(this))); + out.append(' '); + switch (mViewFlags&VISIBILITY_MASK) { + case VISIBLE: out.append('V'); break; + case INVISIBLE: out.append('I'); break; + case GONE: out.append('G'); break; + default: out.append('.'); break; + } + out.append((mViewFlags&FOCUSABLE_MASK) == FOCUSABLE ? 'F' : '.'); + out.append((mViewFlags&ENABLED_MASK) == ENABLED ? 'E' : '.'); + out.append((mViewFlags&DRAW_MASK) == WILL_NOT_DRAW ? '.' : 'D'); + out.append((mViewFlags&SCROLLBARS_HORIZONTAL) != 0 ? 'H' : '.'); + out.append((mViewFlags&SCROLLBARS_VERTICAL) != 0 ? 'V' : '.'); + out.append((mViewFlags&CLICKABLE) != 0 ? 'C' : '.'); + out.append((mViewFlags&LONG_CLICKABLE) != 0 ? 'L' : '.'); + out.append(' '); + out.append((mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0 ? 'R' : '.'); + out.append((mPrivateFlags&PFLAG_FOCUSED) != 0 ? 'F' : '.'); + out.append((mPrivateFlags&PFLAG_SELECTED) != 0 ? 'S' : '.'); + if ((mPrivateFlags&PFLAG_PREPRESSED) != 0) { + out.append('p'); + } else { + out.append((mPrivateFlags&PFLAG_PRESSED) != 0 ? 'P' : '.'); + } + out.append((mPrivateFlags&PFLAG_HOVERED) != 0 ? 'H' : '.'); + out.append((mPrivateFlags&PFLAG_ACTIVATED) != 0 ? 'A' : '.'); + out.append((mPrivateFlags&PFLAG_INVALIDATED) != 0 ? 'I' : '.'); + out.append((mPrivateFlags&PFLAG_DIRTY_MASK) != 0 ? 'D' : '.'); + out.append(' '); + out.append(mLeft); + out.append(','); + out.append(mTop); + out.append('-'); + out.append(mRight); + out.append(','); + out.append(mBottom); + final int id = getId(); + if (id != NO_ID) { + out.append(" #"); + out.append(Integer.toHexString(id)); + final Resources r = mResources; + if (id != 0 && r != null) { + try { + String pkgname; + switch (id&0xff000000) { + case 0x7f000000: + pkgname="app"; + break; + case 0x01000000: + pkgname="android"; + break; + default: + pkgname = r.getResourcePackageName(id); + break; + } + String typename = r.getResourceTypeName(id); + String entryname = r.getResourceEntryName(id); + out.append(" "); + out.append(pkgname); + out.append(":"); + out.append(typename); + out.append("/"); + out.append(entryname); + } catch (Resources.NotFoundException e) { + } + } + } + out.append("}"); + return out.toString(); + } + /** * <p> * Initializes the fading edges from a given set of styled attributes. This @@ -3881,6 +3984,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal scrollabilityCache.scrollBar.setAlwaysDrawVerticalTrack(true); } + // Apply layout direction to the new Drawables if needed + final int layoutDirection = getLayoutDirection(); + if (track != null) { + track.setLayoutDirection(layoutDirection); + } + if (thumb != null) { + thumb.setLayoutDirection(layoutDirection); + } + // Re-apply user/background padding so that scrollbar(s) get added resolvePadding(); } @@ -4247,8 +4359,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal System.out.println(this + " requestFocus()"); } - if ((mPrivateFlags & FOCUSED) == 0) { - mPrivateFlags |= FOCUSED; + if ((mPrivateFlags & PFLAG_FOCUSED) == 0) { + mPrivateFlags |= PFLAG_FOCUSED; if (mParent != null) { mParent.requestChildFocus(this, this); @@ -4294,25 +4406,42 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Whether any parent scrolled. */ public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) { + if (mParent == null) { + return false; + } + View child = this; + + RectF position = (mAttachInfo != null) ? mAttachInfo.mTmpTransformRect : new RectF(); + position.set(rectangle); + ViewParent parent = mParent; boolean scrolled = false; while (parent != null) { + rectangle.set((int) position.left, (int) position.top, + (int) position.right, (int) position.bottom); + scrolled |= parent.requestChildRectangleOnScreen(child, rectangle, immediate); - // offset rect so next call has the rectangle in the - // coordinate system of its direct child. - rectangle.offset(child.getLeft(), child.getTop()); - rectangle.offset(-child.getScrollX(), -child.getScrollY()); + if (!child.hasIdentityMatrix()) { + child.getMatrix().mapRect(position); + } + + position.offset(child.mLeft, child.mTop); if (!(parent instanceof View)) { break; } - child = (View) parent; + View parentView = (View) parent; + + position.offset(-parentView.getScrollX(), -parentView.getScrollY()); + + child = parentView; parent = child.getParent(); } + return scrolled; } @@ -4332,8 +4461,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal System.out.println(this + " clearFocus()"); } - if ((mPrivateFlags & FOCUSED) != 0) { - mPrivateFlags &= ~FOCUSED; + if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { + mPrivateFlags &= ~PFLAG_FOCUSED; if (mParent != null) { mParent.clearChildFocus(this); @@ -4367,8 +4496,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal System.out.println(this + " unFocus()"); } - if ((mPrivateFlags & FOCUSED) != 0) { - mPrivateFlags &= ~FOCUSED; + if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { + mPrivateFlags &= ~PFLAG_FOCUSED; onFocusChanged(false, 0, null); refreshDrawableState(); @@ -4387,7 +4516,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @ViewDebug.ExportedProperty(category = "focus") public boolean hasFocus() { - return (mPrivateFlags & FOCUSED) != 0; + return (mPrivateFlags & PFLAG_FOCUSED) != 0; } /** @@ -4458,7 +4587,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Sends an accessibility event of the given type. If accessiiblity is + * Sends an accessibility event of the given type. If accessibility is * not enabled this method has no effect. The default implementation calls * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)} first * to populate information about the event source (this View), then calls @@ -4502,11 +4631,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param text The announcement text. */ public void announceForAccessibility(CharSequence text) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_ANNOUNCEMENT); + onInitializeAccessibilityEvent(event); event.getText().add(text); - sendAccessibilityEventUnchecked(event); + event.setContentDescription(null); + mParent.requestSendAccessibilityEvent(this, event); } } @@ -4826,6 +4957,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { Rect bounds = mAttachInfo.mTmpInvalRect; + getDrawingRect(bounds); info.setBoundsInParent(bounds); @@ -4837,6 +4969,28 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal info.setParent((View) parent); } + if (mID != View.NO_ID) { + View rootView = getRootView(); + if (rootView == null) { + rootView = this; + } + View label = rootView.findLabelForView(this, mID); + if (label != null) { + info.setLabeledBy(label); + } + } + + if (mLabelForId != View.NO_ID) { + View rootView = getRootView(); + if (rootView == null) { + rootView = this; + } + View labeled = rootView.findViewInsideOutShouldExist(this, mLabelForId); + if (labeled != null) { + info.setLabelFor(labeled); + } + } + info.setVisibleToUser(isVisibleToUser()); info.setPackageName(mContext.getPackageName()); @@ -4852,7 +5006,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal info.setLongClickable(isLongClickable()); // TODO: These make sense only if we are in an AdapterView but all - // views can be selected. Maybe from accessiiblity perspective + // views can be selected. Maybe from accessibility perspective // we should report as selectable view in an AdapterView. info.addAction(AccessibilityNodeInfo.ACTION_SELECT); info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION); @@ -4866,10 +5020,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } if (!isAccessibilityFocused()) { - final int mode = getAccessibilityFocusable(); - if (mode == ACCESSIBILITY_FOCUSABLE_YES || mode == ACCESSIBILITY_FOCUSABLE_AUTO) { - info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); - } + info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); } else { info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } @@ -4891,28 +5042,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - /** - * Returns the delta between the actual and last reported window left. - * - * @hide - */ - public int getActualAndReportedWindowLeftDelta() { - if (mAttachInfo != null) { - return mAttachInfo.mActualWindowLeft - mAttachInfo.mWindowLeft; - } - return 0; - } - - /** - * Returns the delta between the actual and last reported window top. - * - * @hide - */ - public int getActualAndReportedWindowTopDelta() { - if (mAttachInfo != null) { - return mAttachInfo.mActualWindowTop - mAttachInfo.mWindowTop; + private View findLabelForView(View view, int labeledId) { + if (mMatchLabelForPredicate == null) { + mMatchLabelForPredicate = new MatchLabelForPredicate(); } - return 0; + mMatchLabelForPredicate.mLabeledId = labeledId; + return findViewByPredicateInsideOut(view, mMatchLabelForPredicate); } /** @@ -4929,9 +5064,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Computes whether the given portion of this view is visible to the user. Such a view is - * attached, visible, all its predecessors are visible, has an alpha greater than zero, and - * the specified portion is not clipped entirely by its predecessors. + * Computes whether the given portion of this view is visible to the user. + * Such a view is attached, visible, all its predecessors are visible, + * has an alpha greater than zero, and the specified portion is not + * clipped entirely by its predecessors. * * @param boundInView the portion of the view to test; coordinates should be relative; may be * <code>null</code>, and the entire view will be tested in this case. @@ -4945,26 +5081,42 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ protected boolean isVisibleToUser(Rect boundInView) { - Rect visibleRect = mAttachInfo.mTmpInvalRect; - Point offset = mAttachInfo.mPoint; - // The first two checks are made also made by isShown() which - // however traverses the tree up to the parent to catch that. - // Therefore, we do some fail fast check to minimize the up - // tree traversal. - boolean isVisible = mAttachInfo != null - && mAttachInfo.mWindowVisibility == View.VISIBLE - && getAlpha() > 0 - && isShown() - && getGlobalVisibleRect(visibleRect, offset); + if (mAttachInfo != null) { + Rect visibleRect = mAttachInfo.mTmpInvalRect; + Point offset = mAttachInfo.mPoint; + // The first two checks are made also made by isShown() which + // however traverses the tree up to the parent to catch that. + // Therefore, we do some fail fast check to minimize the up + // tree traversal. + boolean isVisible = mAttachInfo.mWindowVisibility == View.VISIBLE + && getAlpha() > 0 + && isShown() + && getGlobalVisibleRect(visibleRect, offset); if (isVisible && boundInView != null) { visibleRect.offset(-offset.x, -offset.y); - isVisible &= boundInView.intersect(visibleRect); + // isVisible is always true here, use a simple assignment + isVisible = boundInView.intersect(visibleRect); } return isVisible; + } + + return false; } /** - * Sets a delegate for implementing accessibility support via compositon as + * Returns the delegate for implementing accessibility support via + * composition. For more details see {@link AccessibilityDelegate}. + * + * @return The delegate, or null if none set. + * + * @hide + */ + public AccessibilityDelegate getAccessibilityDelegate() { + return mAccessibilityDelegate; + } + + /** + * Sets a delegate for implementing accessibility support via composition as * opposed to inheritance. The delegate's primary use is for implementing * backwards compatible widgets. For more details see {@link AccessibilityDelegate}. * @@ -5069,6 +5221,32 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Gets the id of a view for which this view serves as a label for + * accessibility purposes. + * + * @return The labeled view id. + */ + @ViewDebug.ExportedProperty(category = "accessibility") + public int getLabelFor() { + return mLabelForId; + } + + /** + * Sets the id of a view for which this view serves as a label for + * accessibility purposes. + * + * @param id The labeled view id. + */ + @RemotableViewMethod + public void setLabelFor(int id) { + mLabelForId = id; + if (mLabelForId != View.NO_ID + && mID == View.NO_ID) { + mID = generateViewId(); + } + } + + /** * Invoked whenever this view loses focus, either by losing window focus or by losing * focus within its window. This method can be used to clear any state tied to the * focus. For instance, if a button is held pressed with the trackball and the window @@ -5106,7 +5284,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @ViewDebug.ExportedProperty(category = "focus") public boolean isFocused() { - return (mPrivateFlags & FOCUSED) != 0; + return (mPrivateFlags & PFLAG_FOCUSED) != 0; } /** @@ -5117,7 +5295,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * be found. */ public View findFocus() { - return (mPrivateFlags & FOCUSED) != 0 ? this : null; + return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null; } /** @@ -5130,7 +5308,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @attr ref android.R.styleable#View_isScrollContainer */ public boolean isScrollContainer() { - return (mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0; + return (mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0; } /** @@ -5144,16 +5322,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setScrollContainer(boolean isScrollContainer) { if (isScrollContainer) { - if (mAttachInfo != null && (mPrivateFlags&SCROLL_CONTAINER_ADDED) == 0) { + if (mAttachInfo != null && (mPrivateFlags&PFLAG_SCROLL_CONTAINER_ADDED) == 0) { mAttachInfo.mScrollContainers.add(this); - mPrivateFlags |= SCROLL_CONTAINER_ADDED; + mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED; } - mPrivateFlags |= SCROLL_CONTAINER; + mPrivateFlags |= PFLAG_SCROLL_CONTAINER; } else { - if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) { + if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER_ADDED) != 0) { mAttachInfo.mScrollContainers.remove(this); } - mPrivateFlags &= ~(SCROLL_CONTAINER|SCROLL_CONTAINER_ADDED); + mPrivateFlags &= ~(PFLAG_SCROLL_CONTAINER|PFLAG_SCROLL_CONTAINER_ADDED); } } @@ -5403,14 +5581,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Return true if this view applied the insets and it should not * continue propagating further down the hierarchy, false otherwise. * @see #getFitsSystemWindows() - * @see #setFitsSystemWindows() + * @see #setFitsSystemWindows(boolean) * @see #setSystemUiVisibility(int) */ protected boolean fitSystemWindows(Rect insets) { if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { - mUserPaddingStart = -1; - mUserPaddingEnd = -1; - mUserPaddingRelative = false; + mUserPaddingStart = UNDEFINED_PADDING; + mUserPaddingEnd = UNDEFINED_PADDING; if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0 || mAttachInfo == null || (mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0) { @@ -5652,8 +5829,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #LAYOUT_DIRECTION_RTL}, * {@link #LAYOUT_DIRECTION_INHERIT} or * {@link #LAYOUT_DIRECTION_LOCALE}. - * * @attr ref android.R.styleable#View_layoutDirection + * * @hide */ @ViewDebug.ExportedProperty(category = "layout", mapping = { @@ -5662,32 +5839,38 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"), @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE, to = "LOCALE") }) - public int getLayoutDirection() { - return (mPrivateFlags2 & LAYOUT_DIRECTION_MASK) >> LAYOUT_DIRECTION_MASK_SHIFT; + public int getRawLayoutDirection() { + return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT; } /** * Set the layout direction for this view. This will propagate a reset of layout direction * resolution to the view's children and resolve layout direction for this view. * - * @param layoutDirection One of {@link #LAYOUT_DIRECTION_LTR}, - * {@link #LAYOUT_DIRECTION_RTL}, - * {@link #LAYOUT_DIRECTION_INHERIT} or - * {@link #LAYOUT_DIRECTION_LOCALE}. + * @param layoutDirection the layout direction to set. Should be one of: + * + * {@link #LAYOUT_DIRECTION_LTR}, + * {@link #LAYOUT_DIRECTION_RTL}, + * {@link #LAYOUT_DIRECTION_INHERIT}, + * {@link #LAYOUT_DIRECTION_LOCALE}. + * + * Resolution will be done if the value is set to LAYOUT_DIRECTION_INHERIT. The resolution + * proceeds up the parent chain of the view to get the value. If there is no parent, then it + * will return the default {@link #LAYOUT_DIRECTION_LTR}. * * @attr ref android.R.styleable#View_layoutDirection - * @hide */ @RemotableViewMethod public void setLayoutDirection(int layoutDirection) { - if (getLayoutDirection() != layoutDirection) { + if (getRawLayoutDirection() != layoutDirection) { // Reset the current layout direction and the resolved one - mPrivateFlags2 &= ~LAYOUT_DIRECTION_MASK; - resetResolvedLayoutDirection(); - // Set the new layout direction (filtered) and ask for a layout pass + mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_MASK; + resetRtlProperties(); + // Set the new layout direction (filtered) mPrivateFlags2 |= - ((layoutDirection << LAYOUT_DIRECTION_MASK_SHIFT) & LAYOUT_DIRECTION_MASK); - requestLayout(); + ((layoutDirection << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) & PFLAG2_LAYOUT_DIRECTION_MASK); + // We need to resolve all RTL properties as they all depend on layout direction + resolveRtlPropertiesIfNeeded(); } } @@ -5696,19 +5879,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns * {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL. - * @hide + * + * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version + * is lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}. */ @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"), @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL") }) - public int getResolvedLayoutDirection() { - // The layout diretion will be resolved only if needed - if ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED) != LAYOUT_DIRECTION_RESOLVED) { - resolveLayoutDirection(); + public int getLayoutDirection() { + final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; + if (targetSdkVersion < JELLY_BEAN_MR1) { + mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED; + return LAYOUT_DIRECTION_LTR; } - return ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED_RTL) == LAYOUT_DIRECTION_RESOLVED_RTL) ? - LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR; + return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) == + PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR; } /** @@ -5716,11 +5902,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * layout attribute and/or the inherited value from the parent * * @return true if the layout is right-to-left. + * * @hide */ @ViewDebug.ExportedProperty(category = "layout") public boolean isLayoutRtl() { - return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL); + return (getLayoutDirection() == LAYOUT_DIRECTION_RTL); } /** @@ -5737,7 +5924,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @ViewDebug.ExportedProperty(category = "layout") public boolean hasTransientState() { - return (mPrivateFlags2 & HAS_TRANSIENT_STATE) == HAS_TRANSIENT_STATE; + return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE; } /** @@ -5764,8 +5951,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((hasTransientState && mTransientStateCount == 1) || (!hasTransientState && mTransientStateCount == 0)) { // update flag if we've just incremented up from 0 or decremented down to 0 - mPrivateFlags2 = (mPrivateFlags2 & ~HAS_TRANSIENT_STATE) | - (hasTransientState ? HAS_TRANSIENT_STATE : 0); + mPrivateFlags2 = (mPrivateFlags2 & ~PFLAG2_HAS_TRANSIENT_STATE) | + (hasTransientState ? PFLAG2_HAS_TRANSIENT_STATE : 0); if (mParent != null) { try { mParent.childHasTransientStateChanged(this, hasTransientState); @@ -5888,12 +6075,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * the View's internal state from a previously set "pressed" state. */ public void setPressed(boolean pressed) { - final boolean needsRefresh = pressed != ((mPrivateFlags & PRESSED) == PRESSED); + final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED); if (pressed) { - mPrivateFlags |= PRESSED; + mPrivateFlags |= PFLAG_PRESSED; } else { - mPrivateFlags &= ~PRESSED; + mPrivateFlags &= ~PFLAG_PRESSED; } if (needsRefresh) { @@ -5924,7 +6111,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return true if the view is currently pressed, false otherwise */ public boolean isPressed() { - return (mPrivateFlags & PRESSED) == PRESSED; + return (mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED; } /** @@ -6117,17 +6304,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return null; } - private View findViewInsideOutShouldExist(View root, final int childViewId) { - View result = root.findViewByPredicateInsideOut(this, new Predicate<View>() { - @Override - public boolean apply(View t) { - return t.mID == childViewId; - } - }); - + private View findViewInsideOutShouldExist(View root, int id) { + if (mMatchIdPredicate == null) { + mMatchIdPredicate = new MatchIdPredicate(); + } + mMatchIdPredicate.mId = id; + View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate); if (result == null) { - Log.w(VIEW_LOG_TAG, "couldn't find next focus view specified " - + "by user for id " + childViewId); + Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id); } return result; } @@ -6177,12 +6361,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (views == null) { return; } - if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) { - if (isAccessibilityFocusable()) { - views.add(this); - return; - } - } if (!isFocusable()) { return; } @@ -6257,7 +6435,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return True if this View is accessibility focused. */ boolean isAccessibilityFocused() { - return (mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0; + return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0; } /** @@ -6282,8 +6460,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) { return false; } - if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) == 0) { - mPrivateFlags2 |= ACCESSIBILITY_FOCUSED; + if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) == 0) { + mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_FOCUSED; ViewRootImpl viewRootImpl = getViewRootImpl(); if (viewRootImpl != null) { viewRootImpl.setAccessibilityFocus(this, null); @@ -6305,8 +6483,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ public void clearAccessibilityFocus() { - if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) { - mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED; + if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) { + mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED; invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); notifyAccessibilityStateChanged(); @@ -6347,29 +6525,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - private void requestAccessibilityFocusFromHover() { - if (includeForAccessibility() && isActionableForAccessibility()) { - requestAccessibilityFocus(); - } else { - if (mParent != null) { - View nextFocus = mParent.findViewToTakeAccessibilityFocusFromHover(this, this); - if (nextFocus != null) { - nextFocus.requestAccessibilityFocus(); - } - } - } - } - - private boolean canTakeAccessibilityFocusFromHover() { - if (includeForAccessibility() && isActionableForAccessibility()) { - return true; - } - if (mParent != null) { - return (mParent.findViewToTakeAccessibilityFocusFromHover(this, this) == this); - } - return false; - } - /** * Clears accessibility focus without calling any callback methods * normally invoked in {@link #clearAccessibilityFocus()}. This method @@ -6377,8 +6532,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * another view. */ void clearAccessibilityFocusNoCallbacks() { - if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) { - mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED; + if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) { + mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED; invalidate(); } } @@ -6535,8 +6690,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO, to = "no") }) public int getImportantForAccessibility() { - return (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK) - >> IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + return (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK) + >> PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; } /** @@ -6554,9 +6709,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setImportantForAccessibility(int mode) { if (mode != getImportantForAccessibility()) { - mPrivateFlags2 &= ~IMPORTANT_FOR_ACCESSIBILITY_MASK; - mPrivateFlags2 |= (mode << IMPORTANT_FOR_ACCESSIBILITY_SHIFT) - & IMPORTANT_FOR_ACCESSIBILITY_MASK; + mPrivateFlags2 &= ~PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; + mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT) + & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; notifyAccessibilityStateChanged(); } } @@ -6569,15 +6724,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ public boolean isImportantForAccessibility() { - final int mode = (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK) - >> IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + final int mode = (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK) + >> PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; switch (mode) { case IMPORTANT_FOR_ACCESSIBILITY_YES: return true; case IMPORTANT_FOR_ACCESSIBILITY_NO: return false; case IMPORTANT_FOR_ACCESSIBILITY_AUTO: - return isActionableForAccessibility() || hasListenersForAccessibility(); + return isActionableForAccessibility() || hasListenersForAccessibility() + || getAccessibilityNodeProvider() != null; default: throw new IllegalArgumentException("Unknow important for accessibility mode: " + mode); @@ -6585,73 +6741,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Gets the mode for determining whether this View can take accessibility focus. - * - * @return The mode for determining whether a View can take accessibility focus. - * - * @attr ref android.R.styleable#View_accessibilityFocusable - * - * @see #ACCESSIBILITY_FOCUSABLE_YES - * @see #ACCESSIBILITY_FOCUSABLE_NO - * @see #ACCESSIBILITY_FOCUSABLE_AUTO - * - * @hide - */ - @ViewDebug.ExportedProperty(category = "accessibility", mapping = { - @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_AUTO, to = "auto"), - @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_YES, to = "yes"), - @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_NO, to = "no") - }) - public int getAccessibilityFocusable() { - return (mPrivateFlags2 & ACCESSIBILITY_FOCUSABLE_MASK) >>> ACCESSIBILITY_FOCUSABLE_SHIFT; - } - - /** - * Sets how to determine whether this view can take accessibility focus. - * - * @param mode How to determine whether this view can take accessibility focus. - * - * @attr ref android.R.styleable#View_accessibilityFocusable - * - * @see #ACCESSIBILITY_FOCUSABLE_YES - * @see #ACCESSIBILITY_FOCUSABLE_NO - * @see #ACCESSIBILITY_FOCUSABLE_AUTO - * - * @hide - */ - public void setAccessibilityFocusable(int mode) { - if (mode != getAccessibilityFocusable()) { - mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSABLE_MASK; - mPrivateFlags2 |= (mode << ACCESSIBILITY_FOCUSABLE_SHIFT) - & ACCESSIBILITY_FOCUSABLE_MASK; - notifyAccessibilityStateChanged(); - } - } - - /** - * Gets whether this view can take accessibility focus. - * - * @return Whether the view can take accessibility focus. - * - * @hide - */ - public boolean isAccessibilityFocusable() { - final int mode = (mPrivateFlags2 & ACCESSIBILITY_FOCUSABLE_MASK) - >>> ACCESSIBILITY_FOCUSABLE_SHIFT; - switch (mode) { - case ACCESSIBILITY_FOCUSABLE_YES: - return true; - case ACCESSIBILITY_FOCUSABLE_NO: - return false; - case ACCESSIBILITY_FOCUSABLE_AUTO: - return canTakeAccessibilityFocusFromHover() - || getAccessibilityNodeProvider() != null; - default: - throw new IllegalArgumentException("Unknow accessibility focusable mode: " + mode); - } - } - - /** * Gets the parent for accessibility purposes. Note that the parent for * accessibility is not necessary the immediate parent. It is the first * predecessor that is important for accessibility. @@ -6696,10 +6785,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public boolean includeForAccessibility() { if (mAttachInfo != null) { - if (!mAttachInfo.mIncludeNotImportantViews) { - return isImportantForAccessibility(); - } - return true; + return mAttachInfo.mIncludeNotImportantViews || isImportantForAccessibility(); } return false; } @@ -6707,7 +6793,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Returns whether the View is considered actionable from * accessibility perspective. Such view are important for - * accessiiblity. + * accessibility. * * @return True if the view is actionable for accessibility. * @@ -6719,7 +6805,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Returns whether the View has registered callbacks wich makes it - * important for accessiiblity. + * important for accessibility. * * @return True if the view is actionable for accessibility. */ @@ -6748,8 +6834,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!AccessibilityManager.getInstance(mContext).isEnabled()) { return; } - if ((mPrivateFlags2 & ACCESSIBILITY_STATE_CHANGED) == 0) { - mPrivateFlags2 |= ACCESSIBILITY_STATE_CHANGED; + if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_STATE_CHANGED) == 0) { + mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_STATE_CHANGED; if (mParent != null) { mParent.childAccessibilityStateChanged(this); } @@ -6758,12 +6844,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Reset the state indicating the this view has requested clients - * interested in its accessiblity state to be notified. + * interested in its accessibility state to be notified. * * @hide */ public void resetAccessibilityStateChanged() { - mPrivateFlags2 &= ~ACCESSIBILITY_STATE_CHANGED; + mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_STATE_CHANGED; } /** @@ -6832,10 +6918,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } break; case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: { - final int mode = getAccessibilityFocusable(); - if (!isAccessibilityFocused() - && (mode == ACCESSIBILITY_FOCUSABLE_YES - || mode == ACCESSIBILITY_FOCUSABLE_AUTO)) { + if (!isAccessibilityFocused()) { return requestAccessibilityFocus(); } } break; @@ -6929,7 +7012,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ public CharSequence getIterableTextForAccessibility() { - return mContentDescription; + return getContentDescription(); } /** @@ -7018,7 +7101,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void onStartTemporaryDetach() { removeUnsetPressCallback(); - mPrivateFlags |= CANCEL_NEXT_UP_EVENT; + mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT; } /** @@ -7337,13 +7420,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (isPressed()) { setPressed(false); } - if (imm != null && (mPrivateFlags & FOCUSED) != 0) { + if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) { imm.focusOut(this); } removeLongPressCallback(); removeTapCallback(); onFocusLost(); - } else if (imm != null && (mPrivateFlags & FOCUSED) != 0) { + } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) { imm.focusIn(this); } refreshDrawableState(); @@ -7383,7 +7466,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mAttachInfo != null) { initialAwakenScrollBars(); } else { - mPrivateFlags |= AWAKEN_SCROLL_BARS_ON_ATTACH; + mPrivateFlags |= PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH; } } } @@ -7484,7 +7567,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal outRect.bottom -= insets.bottom; return; } - Display d = WindowManagerImpl.getDefault().getDefaultDisplay(); + // The view is not attached to a display so we don't have a context. + // Make a best guess about the display size. + Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); d.getRectSize(outRect); } @@ -7537,7 +7622,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal void needGlobalAttributesUpdate(boolean force) { final AttachInfo ai = mAttachInfo; - if (ai != null) { + if (ai != null && !ai.mRecomputeGlobalAttributes) { if (force || ai.mKeepScreenOn || (ai.mSystemUiVisibility != 0) || ai.mHasSystemUiListeners) { ai.mRecomputeGlobalAttributes = true; @@ -7983,7 +8068,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @ViewDebug.ExportedProperty public boolean isHovered() { - return (mPrivateFlags & HOVERED) != 0; + return (mPrivateFlags & PFLAG_HOVERED) != 0; } /** @@ -8003,14 +8088,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setHovered(boolean hovered) { if (hovered) { - if ((mPrivateFlags & HOVERED) == 0) { - mPrivateFlags |= HOVERED; + if ((mPrivateFlags & PFLAG_HOVERED) == 0) { + mPrivateFlags |= PFLAG_HOVERED; refreshDrawableState(); onHoverChanged(true); } } else { - if ((mPrivateFlags & HOVERED) != 0) { - mPrivateFlags &= ~HOVERED; + if ((mPrivateFlags & PFLAG_HOVERED) != 0) { + mPrivateFlags &= ~PFLAG_HOVERED; refreshDrawableState(); onHoverChanged(false); } @@ -8042,7 +8127,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { - if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) { + if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch @@ -8061,8 +8146,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: - boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; - if ((mPrivateFlags & PRESSED) != 0 || prepressed) { + boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; + if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; @@ -8124,7 +8209,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { - mPrivateFlags |= PREPRESSED; + mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } @@ -8149,7 +8234,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); - if ((mPrivateFlags & PRESSED) != 0) { + if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); @@ -8200,7 +8285,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Remove the prepress detection timer. */ private void removeUnsetPressCallback() { - if ((mPrivateFlags & PRESSED) != 0 && mUnsetPressedState != null) { + if ((mPrivateFlags & PFLAG_PRESSED) != 0 && mUnsetPressedState != null) { setPressed(false); removeCallbacks(mUnsetPressedState); } @@ -8211,7 +8296,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ private void removeTapCallback() { if (mPendingCheckForTap != null) { - mPrivateFlags &= ~PREPRESSED; + mPrivateFlags &= ~PFLAG_PREPRESSED; removeCallbacks(mPendingCheckForTap); } } @@ -8276,13 +8361,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /* Check if the FOCUSABLE bit has changed */ if (((changed & FOCUSABLE_MASK) != 0) && - ((privateFlags & HAS_BOUNDS) !=0)) { + ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) { if (((old & FOCUSABLE_MASK) == FOCUSABLE) - && ((privateFlags & FOCUSED) != 0)) { + && ((privateFlags & PFLAG_FOCUSED) != 0)) { /* Give up focus if we are no longer focusable */ clearFocus(); } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE) - && ((privateFlags & FOCUSED) == 0)) { + && ((privateFlags & PFLAG_FOCUSED) == 0)) { /* * Tell the view system that we are now available to take focus * if no one else already has it. @@ -8301,7 +8386,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * it was not visible. Marking it drawn ensures that the invalidation will * go through. */ - mPrivateFlags |= DRAWN; + mPrivateFlags |= PFLAG_DRAWN; invalidate(true); needGlobalAttributesUpdate(true); @@ -8331,7 +8416,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } // Mark the view drawn to ensure that it gets invalidated properly the next // time it is visible and gets invalidated - mPrivateFlags |= DRAWN; + mPrivateFlags |= PFLAG_DRAWN; } if (mAttachInfo != null) { mAttachInfo.mViewVisibilityChanged = true; @@ -8345,7 +8430,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * If this view is becoming invisible, set the DRAWN flag so that * the next invalidate() will not be skipped. */ - mPrivateFlags |= DRAWN; + mPrivateFlags |= PFLAG_DRAWN; if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE) && hasFocus()) { // root view becoming invisible shouldn't clear focus and accessibility focus @@ -8376,25 +8461,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((changed & DRAWING_CACHE_ENABLED) != 0) { destroyDrawingCache(); - mPrivateFlags &= ~DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; invalidateParentCaches(); } if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) { destroyDrawingCache(); - mPrivateFlags &= ~DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } if ((changed & DRAW_MASK) != 0) { if ((mViewFlags & WILL_NOT_DRAW) != 0) { if (mBackground != null) { - mPrivateFlags &= ~SKIP_DRAW; - mPrivateFlags |= ONLY_DRAWS_BACKGROUND; + mPrivateFlags &= ~PFLAG_SKIP_DRAW; + mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND; } else { - mPrivateFlags |= SKIP_DRAW; + mPrivateFlags |= PFLAG_SKIP_DRAW; } } else { - mPrivateFlags &= ~SKIP_DRAW; + mPrivateFlags &= ~PFLAG_SKIP_DRAW; } requestLayout(); invalidate(true); @@ -8702,7 +8787,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // asked for the matrix; recalculate it with the current values // Figure out if we need to update the pivot point - if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { + if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) { if ((mRight - mLeft) != info.mPrevWidth || (mBottom - mTop) != info.mPrevHeight) { info.mPrevWidth = mRight - mLeft; info.mPrevHeight = mBottom - mTop; @@ -8736,18 +8821,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - /** - * When searching for a view to focus this rectangle is used when considering if this view is - * a good candidate for receiving focus. - * - * By default, the rectangle is the {@link #getDrawingRect}) of the view. - * - * @param r The rectangle to fill in, in this view's coordinates. - */ - public void getFocusRect(Rect r) { - getDrawingRect(r); - } - /** * Utility method to retrieve the inverse of the current mMatrix property. * We cache the matrix to avoid recalculating it when transform properties @@ -8846,7 +8919,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setCameraDistance(-Math.abs(distance) / dpi); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -8892,7 +8965,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setRotation(rotation); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -8943,7 +9016,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setRotationY(rotationY); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -8994,7 +9067,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setRotationX(rotationX); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9037,7 +9110,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setScaleX(scaleX); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9080,7 +9153,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setScaleY(scaleY); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9121,7 +9194,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setPivotX(float pivotX) { ensureTransformationInfo(); - mPrivateFlags |= PIVOT_EXPLICITLY_SET; + mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET; final TransformationInfo info = mTransformationInfo; if (info.mPivotX != pivotX) { invalidateViewProperty(true, false); @@ -9131,7 +9204,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setPivotX(pivotX); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9171,7 +9244,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setPivotY(float pivotY) { ensureTransformationInfo(); - mPrivateFlags |= PIVOT_EXPLICITLY_SET; + mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET; final TransformationInfo info = mTransformationInfo; if (info.mPivotY != pivotY) { invalidateViewProperty(true, false); @@ -9181,7 +9254,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setPivotY(pivotY); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9241,12 +9314,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mTransformationInfo.mAlpha != alpha) { mTransformationInfo.mAlpha = alpha; if (onSetAlpha((int) (alpha * 255))) { - mPrivateFlags |= ALPHA_SET; + mPrivateFlags |= PFLAG_ALPHA_SET; // subclass is handling alpha - don't optimize rendering cache invalidation invalidateParentCaches(); invalidate(true); } else { - mPrivateFlags &= ~ALPHA_SET; + mPrivateFlags &= ~PFLAG_ALPHA_SET; invalidateViewProperty(true, false); if (mDisplayList != null) { mDisplayList.setAlpha(alpha); @@ -9271,10 +9344,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mTransformationInfo.mAlpha = alpha; boolean subclassHandlesAlpha = onSetAlpha((int) (alpha * 255)); if (subclassHandlesAlpha) { - mPrivateFlags |= ALPHA_SET; + mPrivateFlags |= PFLAG_ALPHA_SET; return true; } else { - mPrivateFlags &= ~ALPHA_SET; + mPrivateFlags &= ~PFLAG_ALPHA_SET; if (mDisplayList != null) { mDisplayList.setAlpha(alpha); } @@ -9334,16 +9407,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onSizeChanged(width, mBottom - mTop, width, oldHeight); if (!matrixIsIdentity) { - if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { + if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) { // A change in dimension means an auto-centered pivot point changes, too mTransformationInfo.mMatrixDirty = true; } - mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation invalidate(true); } mBackgroundSizeChanged = true; invalidateParentIfNeeded(); - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9366,7 +9439,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The dirty state of this view. */ public boolean isDirty() { - return (mPrivateFlags & DIRTY_MASK) != 0; + return (mPrivateFlags & PFLAG_DIRTY_MASK) != 0; } /** @@ -9407,16 +9480,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onSizeChanged(width, mBottom - mTop, width, oldHeight); if (!matrixIsIdentity) { - if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { + if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) { // A change in dimension means an auto-centered pivot point changes, too mTransformationInfo.mMatrixDirty = true; } - mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation invalidate(true); } mBackgroundSizeChanged = true; invalidateParentIfNeeded(); - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9474,16 +9547,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onSizeChanged(mRight - mLeft, height, oldWidth, height); if (!matrixIsIdentity) { - if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { + if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) { // A change in dimension means an auto-centered pivot point changes, too mTransformationInfo.mMatrixDirty = true; } - mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation invalidate(true); } mBackgroundSizeChanged = true; invalidateParentIfNeeded(); - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9538,16 +9611,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onSizeChanged(mRight - mLeft, height, oldWidth, height); if (!matrixIsIdentity) { - if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { + if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) { // A change in dimension means an auto-centered pivot point changes, too mTransformationInfo.mMatrixDirty = true; } - mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation invalidate(true); } mBackgroundSizeChanged = true; invalidateParentIfNeeded(); - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9635,7 +9708,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setTranslationX(translationX); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9676,7 +9749,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mDisplayList != null) { mDisplayList.setTranslationY(translationY); } - if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) { + if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) { // View was rejected last time it was drawn by its parent; this may have changed invalidateParentIfNeeded(); } @@ -9726,7 +9799,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * When a view has focus and the user navigates away from it, the next view is searched for * starting from the rectangle filled in by this method. * - * By default, the rectange is the {@link #getDrawingRect(android.graphics.Rect)}) + * By default, the rectangle is the {@link #getDrawingRect(android.graphics.Rect)}) * of the view. However, if your view maintains some idea of internal selection, * such as a cursor, or a selected row or column, you should override this method and * fill in a more specific rectangle. @@ -9910,6 +9983,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal throw new NullPointerException("Layout parameters cannot be null"); } mLayoutParams = params; + resolveLayoutParams(); if (mParent instanceof ViewGroup) { ((ViewGroup) mParent).onSetLayoutParams(this, params); } @@ -9917,6 +9991,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Resolve the layout parameters depending on the resolved layout direction + */ + private void resolveLayoutParams() { + if (mLayoutParams != null) { + mLayoutParams.onResolveLayoutDirection(getLayoutDirection()); + } + } + + /** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. @@ -10137,12 +10220,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (skipInvalidate()) { return; } - if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) || - (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID || - (mPrivateFlags & INVALIDATED) != INVALIDATED) { - mPrivateFlags &= ~DRAWING_CACHE_VALID; - mPrivateFlags |= INVALIDATED; - mPrivateFlags |= DIRTY; + if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || + (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID || + (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) { + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; + mPrivateFlags |= PFLAG_INVALIDATED; + mPrivateFlags |= PFLAG_DIRTY; final ViewParent p = mParent; final AttachInfo ai = mAttachInfo; //noinspection PointlessBooleanExpression,ConstantConditions @@ -10180,12 +10263,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (skipInvalidate()) { return; } - if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) || - (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID || - (mPrivateFlags & INVALIDATED) != INVALIDATED) { - mPrivateFlags &= ~DRAWING_CACHE_VALID; - mPrivateFlags |= INVALIDATED; - mPrivateFlags |= DIRTY; + if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || + (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID || + (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) { + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; + mPrivateFlags |= PFLAG_INVALIDATED; + mPrivateFlags |= PFLAG_DIRTY; final ViewParent p = mParent; final AttachInfo ai = mAttachInfo; //noinspection PointlessBooleanExpression,ConstantConditions @@ -10232,15 +10315,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (skipInvalidate()) { return; } - if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) || - (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) || - (mPrivateFlags & INVALIDATED) != INVALIDATED || isOpaque() != mLastIsOpaque) { + if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || + (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || + (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) { mLastIsOpaque = isOpaque(); - mPrivateFlags &= ~DRAWN; - mPrivateFlags |= DIRTY; + mPrivateFlags &= ~PFLAG_DRAWN; + mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { - mPrivateFlags |= INVALIDATED; - mPrivateFlags &= ~DRAWING_CACHE_VALID; + mPrivateFlags |= PFLAG_INVALIDATED; + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; @@ -10281,12 +10364,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * list properties are not being used in this view */ void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) { - if (mDisplayList == null || (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) { + if (mDisplayList == null || (mPrivateFlags & PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION) { if (invalidateParent) { invalidateParentCaches(); } if (forceRedraw) { - mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation } invalidate(false); } else { @@ -10330,7 +10413,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ protected void invalidateParentCaches() { if (mParent instanceof View) { - ((View) mParent).mPrivateFlags |= INVALIDATED; + ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED; } } @@ -10362,9 +10445,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @ViewDebug.ExportedProperty(category = "drawing") public boolean isOpaque() { - return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK && - ((mTransformationInfo != null ? mTransformationInfo.mAlpha : 1) - >= 1.0f - ViewConfiguration.ALPHA_THRESHOLD); + return (mPrivateFlags & PFLAG_OPAQUE_MASK) == PFLAG_OPAQUE_MASK && + ((mTransformationInfo != null ? mTransformationInfo.mAlpha : 1.0f) >= 1.0f); } /** @@ -10377,17 +10459,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // - Doesn't have scrollbars or scrollbars are inside overlay if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) { - mPrivateFlags |= OPAQUE_BACKGROUND; + mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND; } else { - mPrivateFlags &= ~OPAQUE_BACKGROUND; + mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND; } final int flags = mViewFlags; if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) || (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY) { - mPrivateFlags |= OPAQUE_SCROLLBARS; + mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS; } else { - mPrivateFlags &= ~OPAQUE_SCROLLBARS; + mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS; } } @@ -10395,7 +10477,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ protected boolean hasOpaqueScrollbars() { - return (mPrivateFlags & OPAQUE_SCROLLBARS) == OPAQUE_SCROLLBARS; + return (mPrivateFlags & PFLAG_OPAQUE_SCROLLBARS) == PFLAG_OPAQUE_SCROLLBARS; } /** @@ -10923,7 +11005,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ protected void recomputePadding() { - setPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom); + internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom); } /** @@ -11331,9 +11413,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal scrollBar.setParameters(computeVerticalScrollRange(), computeVerticalScrollOffset(), computeVerticalScrollExtent(), true); - switch (mVerticalScrollbarPosition) { + int verticalScrollbarPosition = mVerticalScrollbarPosition; + if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) { + verticalScrollbarPosition = isLayoutRtl() ? + SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT; + } + switch (verticalScrollbarPosition) { default: - case SCROLLBAR_POSITION_DEFAULT: case SCROLLBAR_POSITION_RIGHT: left = scrollX + width - size - (mUserPaddingRight & inside); break; @@ -11436,24 +11522,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #onDetachedFromWindow() */ protected void onAttachedToWindow() { - if ((mPrivateFlags & REQUEST_TRANSPARENT_REGIONS) != 0) { + if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) { mParent.requestTransparentRegion(this); } - if ((mPrivateFlags & AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) { + if ((mPrivateFlags & PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) { initialAwakenScrollBars(); - mPrivateFlags &= ~AWAKEN_SCROLL_BARS_ON_ATTACH; + mPrivateFlags &= ~PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH; } jumpDrawablesToCurrentState(); - // Order is important here: LayoutDirection MUST be resolved before Padding - // and TextDirection - resolveLayoutDirection(); - resolvePadding(); - resolveTextDirection(); - resolveTextAlignment(); - clearAccessibilityFocus(); if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); @@ -11466,6 +11545,44 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Resolve all RTL related properties. + */ + void resolveRtlPropertiesIfNeeded() { + if (!needRtlPropertiesResolution()) return; + + // Order is important here: LayoutDirection MUST be resolved first + if (!isLayoutDirectionResolved()) { + resolveLayoutDirection(); + resolveLayoutParams(); + } + // ... then we can resolve the others properties depending on the resolved LayoutDirection. + if (!isTextDirectionResolved()) { + resolveTextDirection(); + } + if (!isTextAlignmentResolved()) { + resolveTextAlignment(); + } + if (!isPaddingResolved()) { + resolvePadding(); + } + if (!isDrawablesResolved()) { + resolveDrawables(); + } + requestLayout(); + invalidate(true); + onRtlPropertiesChanged(getLayoutDirection()); + } + + // Reset resolution of all RTL related properties. + void resetRtlProperties() { + resetResolvedLayoutDirection(); + resetResolvedTextDirection(); + resetResolvedTextAlignment(); + resetResolvedPadding(); + resetResolvedDrawables(); + } + + /** * @see #onScreenStateChanged(int) */ void dispatchScreenStateChanged(int screenState) { @@ -11492,36 +11609,74 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or + * RTL not supported) + */ + private boolean isRtlCompatibilityMode() { + final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; + return targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport(); + } + + /** + * @return true if RTL properties need resolution. + */ + private boolean needRtlPropertiesResolution() { + return (mPrivateFlags2 & ALL_RTL_PROPERTIES_RESOLVED) != ALL_RTL_PROPERTIES_RESOLVED; + } + + /** + * Called when any RTL property (layout direction or text direction or text alignment) has + * been changed. + * + * Subclasses need to override this method to take care of cached information that depends on the + * resolved layout direction, or to inform child views that inherit their layout direction. + * + * The default implementation does nothing. + * + * @param layoutDirection the direction of the layout + * + * @see #LAYOUT_DIRECTION_LTR + * @see #LAYOUT_DIRECTION_RTL + */ + public void onRtlPropertiesChanged(int layoutDirection) { + } + + /** * 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. - * Will call {@link View#onResolvedLayoutDirectionChanged} when resolution is done. + * + * @return true if resolution has been done, false otherwise. + * * @hide */ - public void resolveLayoutDirection() { + public boolean resolveLayoutDirection() { // Clear any previous layout direction resolution - mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_MASK; + mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK; if (hasRtlSupport()) { // Set resolved depending on layout direction - switch (getLayoutDirection()) { + switch ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> + PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) { case LAYOUT_DIRECTION_INHERIT: - // If this is root view, no need to look at parent's layout dir. - if (canResolveLayoutDirection()) { - ViewGroup viewGroup = ((ViewGroup) mParent); + // We cannot resolve yet. LTR is by default and let the resolution happen again + // later to get the correct resolved value + if (!canResolveLayoutDirection()) return false; - if (viewGroup.getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) { - mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL; - } - } else { - // Nothing to do, LTR by default + View parent = ((View) mParent); + // Parent has not yet resolved, LTR is still the default + if (!parent.isLayoutDirectionResolved()) return false; + + if (parent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) { + mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL; } break; case LAYOUT_DIRECTION_RTL: - mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL; + mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL; break; case LAYOUT_DIRECTION_LOCALE: - if(isLayoutDirectionRtl(Locale.getDefault())) { - mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL; + if((LAYOUT_DIRECTION_RTL == + TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()))) { + mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL; } break; default: @@ -11530,137 +11685,115 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } // Set to resolved - mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED; - onResolvedLayoutDirectionChanged(); - // Resolve padding - resolvePadding(); + mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED; + return true; } /** - * Called when layout direction has been resolved. + * Check if layout direction resolution can be done. + * + * @return true if layout direction resolution can be done otherwise return false. * - * The default implementation does nothing. * @hide */ - public void onResolvedLayoutDirectionChanged() { + public boolean canResolveLayoutDirection() { + switch (getRawLayoutDirection()) { + case LAYOUT_DIRECTION_INHERIT: + return (mParent != null) && (mParent instanceof ViewGroup) && + ((ViewGroup) mParent).canResolveLayoutDirection(); + default: + return true; + } } /** - * Resolve padding depending on layout direction. + * Reset the resolved layout direction. Layout direction will be resolved during a call to + * {@link #onMeasure(int, int)}. + * * @hide */ - public void resolvePadding() { - // If the user specified the absolute padding (either with android:padding or - // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise - // use the default padding or the padding from the background drawable - // (stored at this point in mPadding*) - int resolvedLayoutDirection = getResolvedLayoutDirection(); - switch (resolvedLayoutDirection) { - case LAYOUT_DIRECTION_RTL: - // Start user padding override Right user padding. Otherwise, if Right user - // padding is not defined, use the default Right padding. If Right user padding - // is defined, just use it. - if (mUserPaddingStart >= 0) { - mUserPaddingRight = mUserPaddingStart; - } else if (mUserPaddingRight < 0) { - mUserPaddingRight = mPaddingRight; - } - if (mUserPaddingEnd >= 0) { - mUserPaddingLeft = mUserPaddingEnd; - } else if (mUserPaddingLeft < 0) { - mUserPaddingLeft = mPaddingLeft; - } - break; - case LAYOUT_DIRECTION_LTR: - default: - // Start user padding override Left user padding. Otherwise, if Left user - // padding is not defined, use the default left padding. If Left user padding - // is defined, just use it. - if (mUserPaddingStart >= 0) { - mUserPaddingLeft = mUserPaddingStart; - } else if (mUserPaddingLeft < 0) { - mUserPaddingLeft = mPaddingLeft; - } - if (mUserPaddingEnd >= 0) { - mUserPaddingRight = mUserPaddingEnd; - } else if (mUserPaddingRight < 0) { - mUserPaddingRight = mPaddingRight; - } - } - - mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom; - - if(isPaddingRelative()) { - setPaddingRelative(mUserPaddingStart, mPaddingTop, mUserPaddingEnd, mUserPaddingBottom); - } else { - recomputePadding(); - } - onPaddingChanged(resolvedLayoutDirection); + public void resetResolvedLayoutDirection() { + // Reset the current resolved bits + mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK; } /** - * Resolve padding depending on the layout direction. Subclasses that care about - * padding resolution should override this method. The default implementation does - * nothing. - * - * @param layoutDirection the direction of the layout + * @return true if the layout direction is inherited. * - * @see {@link #LAYOUT_DIRECTION_LTR} - * @see {@link #LAYOUT_DIRECTION_RTL} * @hide */ - public void onPaddingChanged(int layoutDirection) { + public boolean isLayoutDirectionInherited() { + return (getRawLayoutDirection() == LAYOUT_DIRECTION_INHERIT); } /** - * Check if layout direction resolution can be done. - * - * @return true if layout direction resolution can be done otherwise return false. - * @hide + * @return true if layout direction has been resolved. */ - public boolean canResolveLayoutDirection() { - switch (getLayoutDirection()) { - case LAYOUT_DIRECTION_INHERIT: - return (mParent != null) && (mParent instanceof ViewGroup); - default: - return true; - } + private boolean isLayoutDirectionResolved() { + return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED; } /** - * Reset the resolved layout direction. Will call {@link View#onResolvedLayoutDirectionReset} - * when reset is done. + * Return if padding has been resolved + * * @hide */ - public void resetResolvedLayoutDirection() { - // Reset the current resolved bits - mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_MASK; - onResolvedLayoutDirectionReset(); - // Reset also the text direction - resetResolvedTextDirection(); + boolean isPaddingResolved() { + return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) == PFLAG2_PADDING_RESOLVED; } /** - * Called during reset of resolved layout direction. - * - * Subclasses need to override this method to clear cached information that depends on the - * resolved layout direction, or to inform child views that inherit their layout direction. + * Resolve padding depending on layout direction. * - * The default implementation does nothing. * @hide */ - public void onResolvedLayoutDirectionReset() { + public void resolvePadding() { + if (!isRtlCompatibilityMode()) { + // Post Jelly Bean MR1 case: we need to take the resolved layout direction into account. + // If start / end padding are defined, they will be resolved (hence overriding) to + // left / right or right / left depending on the resolved layout direction. + // If start / end padding are not defined, use the left / right ones. + int resolvedLayoutDirection = getLayoutDirection(); + // Set user padding to initial values ... + mUserPaddingLeft = mUserPaddingLeftInitial; + mUserPaddingRight = mUserPaddingRightInitial; + // ... then resolve it. + switch (resolvedLayoutDirection) { + case LAYOUT_DIRECTION_RTL: + if (mUserPaddingStart != UNDEFINED_PADDING) { + mUserPaddingRight = mUserPaddingStart; + } + if (mUserPaddingEnd != UNDEFINED_PADDING) { + mUserPaddingLeft = mUserPaddingEnd; + } + break; + case LAYOUT_DIRECTION_LTR: + default: + if (mUserPaddingStart != UNDEFINED_PADDING) { + mUserPaddingLeft = mUserPaddingStart; + } + if (mUserPaddingEnd != UNDEFINED_PADDING) { + mUserPaddingRight = mUserPaddingEnd; + } + } + + mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom; + + internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, + mUserPaddingBottom); + onRtlPropertiesChanged(resolvedLayoutDirection); + } + + mPrivateFlags2 |= PFLAG2_PADDING_RESOLVED; } /** - * Check if a Locale uses an RTL script. + * Reset the resolved layout direction. * - * @param locale Locale to check - * @return true if the Locale uses an RTL script. * @hide */ - protected static boolean isLayoutDirectionRtl(Locale locale) { - return (LAYOUT_DIRECTION_RTL == LocaleUtil.getLayoutDirectionFromLocale(locale)); + public void resetResolvedPadding() { + mPrivateFlags2 &= ~PFLAG2_PADDING_RESOLVED; } /** @@ -11670,7 +11803,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #onAttachedToWindow() */ protected void onDetachedFromWindow() { - mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; removeUnsetPressCallback(); removeLongPressCallback(); @@ -11693,8 +11826,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mCurrentAnimation = null; - resetResolvedLayoutDirection(); - resetResolvedTextAlignment(); + resetRtlProperties(); + onRtlPropertiesChanged(LAYOUT_DIRECTION_DEFAULT); resetAccessibilityStateChanged(); } @@ -11737,6 +11870,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Gets the logical display to which the view's window has been attached. + * + * @return The logical display, or null if the view is not currently attached to a window. + */ + public Display getDisplay() { + return mAttachInfo != null ? mAttachInfo.mDisplay : null; + } + + /** * Retrieve private session object this view hierarchy is using to * communicate with the window manager. * @return the session object to communicate with the window manager @@ -11754,14 +11896,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mAttachInfo = info; mWindowAttachCount++; // We will need to evaluate the drawable state at least once. - mPrivateFlags |= DRAWABLE_STATE_DIRTY; + mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY; if (mFloatingTreeObserver != null) { info.mTreeObserver.merge(mFloatingTreeObserver); mFloatingTreeObserver = null; } - if ((mPrivateFlags&SCROLL_CONTAINER) != 0) { + if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) { mAttachInfo.mScrollContainers.add(this); - mPrivateFlags |= SCROLL_CONTAINER_ADDED; + mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED; } performCollectViewAttributes(mAttachInfo, visibility); onAttachedToWindow(); @@ -11783,10 +11925,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (vis != GONE) { onWindowVisibilityChanged(vis); } - if ((mPrivateFlags&DRAWABLE_STATE_DIRTY) != 0) { + if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) { // If nobody has evaluated the drawable state yet, then do it now. refreshDrawableState(); } + needGlobalAttributesUpdate(false); } void dispatchDetachedFromWindow() { @@ -11813,9 +11956,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - if ((mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0) { + if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) { mAttachInfo.mScrollContainers.remove(this); - mPrivateFlags &= ~SCROLL_CONTAINER_ADDED; + mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED; } mAttachInfo = null; @@ -11847,9 +11990,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { - mPrivateFlags &= ~SAVE_STATE_CALLED; + mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; Parcelable state = onSaveInstanceState(); - if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) { + if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) { throw new IllegalStateException( "Derived class did not call super.onSaveInstanceState()"); } @@ -11883,7 +12026,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #setSaveEnabled(boolean) */ protected Parcelable onSaveInstanceState() { - mPrivateFlags |= SAVE_STATE_CALLED; + mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; return BaseSavedState.EMPTY_STATE; } @@ -11918,9 +12061,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (state != null) { // Log.i("View", "Restoreing #" + Integer.toHexString(mID) // + ": " + state); - mPrivateFlags &= ~SAVE_STATE_CALLED; + mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; onRestoreInstanceState(state); - if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) { + if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) { throw new IllegalStateException( "Derived class did not call super.onRestoreInstanceState()"); } @@ -11941,7 +12084,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #dispatchRestoreInstanceState(android.util.SparseArray) */ protected void onRestoreInstanceState(Parcelable state) { - mPrivateFlags |= SAVE_STATE_CALLED; + mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; if (state != BaseSavedState.EMPTY_STATE && state != null) { throw new IllegalArgumentException("Wrong state class, expecting View State but " + "received " + state.getClass().toString() + " instead. This usually happens " @@ -12016,13 +12159,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by * this view's alpha value. Calling {@link #setAlpha(float)} is therefore * equivalent to setting a hardware layer on this view and providing a paint with - * the desired alpha value.<p> + * the desired alpha value.</p> * * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE disabled}, * {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware} * for more information on when and how to use layers.</p> * - * @param layerType The ype of layer to use with this view, must be one of + * @param layerType The type of layer to use with this view, must be one of * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or * {@link #LAYER_TYPE_HARDWARE} * @param paint The paint used to compose the layer. This argument is optional @@ -12074,6 +12217,50 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Updates the {@link Paint} object used with the current layer (used only if the current + * layer type is not set to {@link #LAYER_TYPE_NONE}). Changed properties of the Paint + * provided to {@link #setLayerType(int, android.graphics.Paint)} will be used the next time + * the View is redrawn, but {@link #setLayerPaint(android.graphics.Paint)} must be called to + * ensure that the view gets redrawn immediately. + * + * <p>A layer is associated with an optional {@link android.graphics.Paint} + * instance that controls how the layer is composed on screen. The following + * properties of the paint are taken into account when composing the layer:</p> + * <ul> + * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li> + * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li> + * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li> + * </ul> + * + * <p>If this view has an alpha value set to < 1.0 by calling + * {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by + * this view's alpha value. Calling {@link #setAlpha(float)} is therefore + * equivalent to setting a hardware layer on this view and providing a paint with + * the desired alpha value.</p> + * + * @param paint The paint used to compose the layer. This argument is optional + * and can be null. It is ignored when the layer type is + * {@link #LAYER_TYPE_NONE} + * + * @see #setLayerType(int, android.graphics.Paint) + */ + public void setLayerPaint(Paint paint) { + int layerType = getLayerType(); + if (layerType != LAYER_TYPE_NONE) { + mLayerPaint = paint == null ? new Paint() : paint; + if (layerType == LAYER_TYPE_HARDWARE) { + HardwareLayer layer = getHardwareLayer(); + if (layer != null) { + layer.setLayerPaint(paint); + } + invalidateViewProperty(false, false); + } else { + invalidate(); + } + } + } + + /** * Indicates whether this view has a static layer. A view with layer type * {@link #LAYER_TYPE_NONE} is a static layer. Other types of layers are * dynamic. @@ -12135,13 +12322,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - // Make sure the HardwareRenderer.validate() was invoked before calling this method - void flushLayer() { - if (mLayerType == LAYER_TYPE_HARDWARE && mHardwareLayer != null) { - mHardwareLayer.flush(); - } - } - /** * <p>Returns a hardware layer that can be used to draw this view again * without executing its draw method.</p> @@ -12163,14 +12343,30 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return null; } - if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) { + if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) { if (mHardwareLayer == null) { mHardwareLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer( width, height, isOpaque()); mLocalDirtyRect.set(0, 0, width, height); - } else if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) { - mHardwareLayer.resize(width, height); - mLocalDirtyRect.set(0, 0, width, height); + } else { + if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) { + if (mHardwareLayer.resize(width, height)) { + mLocalDirtyRect.set(0, 0, width, height); + } + } + + // This should not be necessary but applications that change + // the parameters of their background drawable without calling + // this.setBackground(Drawable) can leave the view in a bad state + // (for instance isOpaque() returns true, but the background is + // not opaque.) + computeOpaqueFlags(); + + final boolean opaque = isOpaque(); + if (mHardwareLayer.isValid() && mHardwareLayer.isOpaque() != opaque) { + mHardwareLayer.setOpaque(opaque); + mLocalDirtyRect.set(0, 0, width, height); + } } // The layer is not valid if the underlying GPU resources cannot be allocated @@ -12178,7 +12374,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return null; } - mHardwareLayer.redraw(getHardwareLayerDisplayList(mHardwareLayer), mLocalDirtyRect); + mHardwareLayer.setLayerPaint(mLayerPaint); + mHardwareLayer.redrawLater(getHardwareLayerDisplayList(mHardwareLayer), mLocalDirtyRect); + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) viewRoot.pushHardwareLayerUpdate(mHardwareLayer); + mLocalDirtyRect.setEmpty(); } @@ -12202,6 +12402,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mHardwareLayer.destroy(); mHardwareLayer = null; + if (mDisplayList != null) { + mDisplayList.reset(); + } invalidate(true); invalidateParentCaches(); } @@ -12278,10 +12481,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @SuppressWarnings({"UnusedDeclaration"}) public void outputDirtyFlags(String indent, boolean clear, int clearMask) { - Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.DIRTY_MASK) + - ") DRAWN(" + (mPrivateFlags & DRAWN) + ")" + " CACHE_VALID(" + - (mPrivateFlags & View.DRAWING_CACHE_VALID) + - ") INVALIDATED(" + (mPrivateFlags & INVALIDATED) + ")"); + Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) + + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" + + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) + + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")"); if (clear) { mPrivateFlags &= clearMask; } @@ -12345,15 +12548,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return null; } - if (((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || + if (((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || displayList == null || !displayList.isValid() || (!isLayer && mRecreateDisplayList))) { // Don't need to recreate the display list, just need to tell our // children to restore/recreate theirs if (displayList != null && displayList.isValid() && !isLayer && !mRecreateDisplayList) { - mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID; - mPrivateFlags &= ~DIRTY_MASK; + mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchGetDisplayList(); return displayList; @@ -12382,9 +12585,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal canvas.setViewport(width, height); // The dirty rect should always be null for a display list canvas.onPreDraw(null); - int layerType = ( - !(mParent instanceof ViewGroup) || ((ViewGroup)mParent).mDrawLayers) ? - getLayerType() : LAYER_TYPE_NONE; + int layerType = getLayerType(); if (!isLayer && layerType != LAYER_TYPE_NONE) { if (layerType == LAYER_TYPE_HARDWARE) { final HardwareLayer layer = getHardwareLayer(); @@ -12410,12 +12611,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal canvas.translate(-mScrollX, -mScrollY); if (!isLayer) { - mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID; - mPrivateFlags &= ~DIRTY_MASK; + mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DIRTY_MASK; } // Fast path for layouts with no backgrounds - if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); } else { draw(canvas); @@ -12433,8 +12634,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } } else if (!isLayer) { - mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID; - mPrivateFlags &= ~DIRTY_MASK; + mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DIRTY_MASK; } return displayList; @@ -12556,7 +12757,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public void setDrawingCacheBackgroundColor(int color) { if (color != mDrawingCacheBackgroundColor) { mDrawingCacheBackgroundColor = color; - mPrivateFlags &= ~DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } } @@ -12602,7 +12803,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #destroyDrawingCache() */ public void buildDrawingCache(boolean autoScale) { - if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ? + if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) { mCachingFailed = false; @@ -12621,10 +12822,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque(); final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache; - if (width <= 0 || height <= 0 || - // Projected bitmap size in bytes - (width * height * (opaque && !use32BitCache ? 2 : 4) > - ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) { + final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4); + final long drawingCacheSize = + ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize(); + if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) { + if (width > 0 && height > 0) { + Log.w(VIEW_LOG_TAG, "View too large to fit into drawing cache, needs " + + projectedBitmapSize + " bytes, only " + + drawingCacheSize + " available"); + } destroyDrawingCache(); mCachingFailed = true; return; @@ -12662,7 +12868,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (bitmap != null) bitmap.recycle(); try { - bitmap = Bitmap.createBitmap(width, height, quality); + bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), + width, height, quality); bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); if (autoScale) { mDrawingCache = bitmap; @@ -12717,15 +12924,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal canvas.translate(-mScrollX, -mScrollY); - mPrivateFlags |= DRAWN; + mPrivateFlags |= PFLAG_DRAWN; if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated || mLayerType != LAYER_TYPE_NONE) { - mPrivateFlags |= DRAWING_CACHE_VALID; + mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID; } // Fast path for layouts with no backgrounds - if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { - mPrivateFlags &= ~DIRTY_MASK; + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { + mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); @@ -12754,7 +12961,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal width = (int) ((width * scale) + 0.5f); height = (int) ((height * scale) + 0.5f); - Bitmap bitmap = Bitmap.createBitmap(width > 0 ? width : 1, height > 0 ? height : 1, quality); + Bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), + width > 0 ? width : 1, height > 0 ? height : 1, quality); if (bitmap == null) { throw new OutOfMemoryError(); } @@ -12792,10 +13000,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Temporarily remove the dirty mask int flags = mPrivateFlags; - mPrivateFlags &= ~DIRTY_MASK; + mPrivateFlags &= ~PFLAG_DIRTY_MASK; // Fast path for layouts with no backgrounds - if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); } else { draw(canvas); @@ -12979,13 +13187,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (more) { if (!a.willChangeBounds()) { - if ((flags & (parent.FLAG_OPTIMIZE_INVALIDATE | parent.FLAG_ANIMATION_DONE)) == - parent.FLAG_OPTIMIZE_INVALIDATE) { - parent.mGroupFlags |= parent.FLAG_INVALIDATE_REQUIRED; - } else if ((flags & parent.FLAG_INVALIDATE_REQUIRED) == 0) { + if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) == + ViewGroup.FLAG_OPTIMIZE_INVALIDATE) { + parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED; + } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) { // The child need to draw an animation, potentially offscreen, so // make sure we do not cancel invalidate requests - parent.mPrivateFlags |= DRAW_ANIMATION; + parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; parent.invalidate(mLeft, mTop, mRight, mBottom); } } else { @@ -12998,7 +13206,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // The child need to draw an animation, potentially offscreen, so // make sure we do not cancel invalidate requests - parent.mPrivateFlags |= DRAW_ANIMATION; + parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; final int left = mLeft + (int) region.left; final int top = mTop + (int) region.top; @@ -13060,7 +13268,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mTransformationInfo.matrix3D = new Matrix(); } displayList.setCameraDistance(mTransformationInfo.mCamera.getLocationZ()); - if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == PIVOT_EXPLICITLY_SET) { + if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == PFLAG_PIVOT_EXPLICITLY_SET) { displayList.setPivotX(getPivotX()); displayList.setPivotY(getPivotY()); } @@ -13091,7 +13299,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal boolean scalingRequired = false; boolean caching; - int layerType = parent.mDrawLayers ? getLayerType() : LAYER_TYPE_NONE; + int layerType = getLayerType(); final boolean hardwareAccelerated = canvas.isHardwareAccelerated(); if ((flags & ViewGroup.FLAG_CHILDREN_DRAWN_WITH_CACHE) != 0 || @@ -13108,15 +13316,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal more = drawAnimation(parent, drawingTime, a, scalingRequired); concatMatrix = a.willChangeTransformationMatrix(); if (concatMatrix) { - mPrivateFlags3 |= VIEW_IS_ANIMATING_TRANSFORM; + mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } transformToApply = parent.mChildTransformation; } else { - if ((mPrivateFlags3 & VIEW_IS_ANIMATING_TRANSFORM) == VIEW_IS_ANIMATING_TRANSFORM && + if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) == PFLAG3_VIEW_IS_ANIMATING_TRANSFORM && mDisplayList != null) { // No longer animating: clear out old animation matrix mDisplayList.setAnimationMatrix(null); - mPrivateFlags3 &= ~VIEW_IS_ANIMATING_TRANSFORM; + mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } if (!useDisplayListProperties && (flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { @@ -13135,27 +13343,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Sets the flag as early as possible to allow draw() implementations // to call invalidate() successfully when doing animations - mPrivateFlags |= DRAWN; - - if (!concatMatrix && canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) && - (mPrivateFlags & DRAW_ANIMATION) == 0) { - mPrivateFlags2 |= VIEW_QUICK_REJECTED; + mPrivateFlags |= PFLAG_DRAWN; + + if (!concatMatrix && + (flags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS | + ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN && + canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) && + (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) { + mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED; return more; } - mPrivateFlags2 &= ~VIEW_QUICK_REJECTED; + mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED; if (hardwareAccelerated) { // Clear INVALIDATED flag to allow invalidation to occur during rendering, but // retain the flag's value temporarily in the mRecreateDisplayList flag - mRecreateDisplayList = (mPrivateFlags & INVALIDATED) == INVALIDATED; - mPrivateFlags &= ~INVALIDATED; + mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) == PFLAG_INVALIDATED; + mPrivateFlags &= ~PFLAG_INVALIDATED; } - computeScroll(); - - final int sx = mScrollX; - final int sy = mScrollY; - DisplayList displayList = null; Bitmap cache = null; boolean hasDisplayList = false; @@ -13202,6 +13408,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } + int sx = 0; + int sy = 0; + if (!hasDisplayList) { + computeScroll(); + sx = mScrollX; + sy = mScrollY; + } + final boolean hasNoCache = cache == null || hasDisplayList; final boolean offsetForScroll = cache == null && !hasDisplayList && layerType != LAYER_TYPE_HARDWARE; @@ -13229,7 +13443,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal float alpha = useDisplayListProperties ? 1 : getAlpha(); if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() || - (mPrivateFlags3 & VIEW_IS_ANIMATING_ALPHA) == VIEW_IS_ANIMATING_ALPHA) { + (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) { if (transformToApply != null || !childHasIdentityMatrix) { int transX = 0; int transY = 0; @@ -13269,11 +13483,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Deal with alpha if it is or used to be <1 if (alpha < 1 || - (mPrivateFlags3 & VIEW_IS_ANIMATING_ALPHA) == VIEW_IS_ANIMATING_ALPHA) { + (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) { if (alpha < 1) { - mPrivateFlags3 |= VIEW_IS_ANIMATING_ALPHA; + mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA; } else { - mPrivateFlags3 &= ~VIEW_IS_ANIMATING_ALPHA; + mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA; } parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; if (hasNoCache) { @@ -13294,13 +13508,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } else { // Alpha is handled by the child directly, clobber the layer's alpha - mPrivateFlags |= ALPHA_SET; + mPrivateFlags |= PFLAG_ALPHA_SET; } } } - } else if ((mPrivateFlags & ALPHA_SET) == ALPHA_SET) { + } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) { onSetAlpha(255); - mPrivateFlags &= ~ALPHA_SET; + mPrivateFlags &= ~PFLAG_ALPHA_SET; } if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN && @@ -13347,19 +13561,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!layerRendered) { if (!hasDisplayList) { // Fast path for layouts with no backgrounds - if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { - mPrivateFlags &= ~DIRTY_MASK; + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { + mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); } } else { - mPrivateFlags &= ~DIRTY_MASK; + mPrivateFlags &= ~PFLAG_DIRTY_MASK; ((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags); } } } else if (cache != null) { - mPrivateFlags &= ~DIRTY_MASK; + mPrivateFlags &= ~PFLAG_DIRTY_MASK; Paint cachePaint; if (layerType == LAYER_TYPE_NONE) { @@ -13399,7 +13613,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // display lists to render, force an invalidate to allow the animation to // continue drawing another frame parent.invalidate(true); - if (a.hasAlpha() && (mPrivateFlags & ALPHA_SET) == ALPHA_SET) { + if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) { // alpha animations should cause the child to recreate its display list invalidate(true); } @@ -13421,9 +13635,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; - final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && + final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); - mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN; + mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed @@ -13678,12 +13892,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal String output = ""; int numFlags = 0; - if ((privateFlags & WANTS_FOCUS) == WANTS_FOCUS) { + if ((privateFlags & PFLAG_WANTS_FOCUS) == PFLAG_WANTS_FOCUS) { output += "WANTS_FOCUS"; numFlags++; } - if ((privateFlags & FOCUSED) == FOCUSED) { + if ((privateFlags & PFLAG_FOCUSED) == PFLAG_FOCUSED) { if (numFlags > 0) { output += " "; } @@ -13691,7 +13905,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal numFlags++; } - if ((privateFlags & SELECTED) == SELECTED) { + if ((privateFlags & PFLAG_SELECTED) == PFLAG_SELECTED) { if (numFlags > 0) { output += " "; } @@ -13699,7 +13913,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal numFlags++; } - if ((privateFlags & IS_ROOT_NAMESPACE) == IS_ROOT_NAMESPACE) { + if ((privateFlags & PFLAG_IS_ROOT_NAMESPACE) == PFLAG_IS_ROOT_NAMESPACE) { if (numFlags > 0) { output += " "; } @@ -13707,7 +13921,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal numFlags++; } - if ((privateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + if ((privateFlags & PFLAG_HAS_BOUNDS) == PFLAG_HAS_BOUNDS) { if (numFlags > 0) { output += " "; } @@ -13715,7 +13929,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal numFlags++; } - if ((privateFlags & DRAWN) == DRAWN) { + if ((privateFlags & PFLAG_DRAWN) == PFLAG_DRAWN) { if (numFlags > 0) { output += " "; } @@ -13732,7 +13946,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return true if the layout will be forced during next layout pass */ public boolean isLayoutRequested() { - return (mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT; + return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; } /** @@ -13762,9 +13976,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int oldB = mBottom; int oldR = mRight; boolean changed = setFrame(l, t, r, b); - if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { + if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); - mPrivateFlags &= ~LAYOUT_REQUIRED; + mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { @@ -13776,7 +13990,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } } - mPrivateFlags &= ~FORCE_LAYOUT; + mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; } /** @@ -13820,7 +14034,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal changed = true; // Remember our drawn bit - int drawn = mPrivateFlags & DRAWN; + int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; @@ -13839,11 +14053,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); } - mPrivateFlags |= HAS_BOUNDS; + mPrivateFlags |= PFLAG_HAS_BOUNDS; if (sizeChanged) { - if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { + if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) { // A change in dimension means an auto-centered pivot point changes, too if (mTransformationInfo != null) { mTransformationInfo.mMatrixDirty = true; @@ -13858,7 +14072,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // This is because someone may have invalidated this view // before this call to setFrame came in, thereby clearing // the DRAWN bit. - mPrivateFlags |= DRAWN; + mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); // parent display list may need to be recreated based on a change in the bounds // of any child @@ -13963,13 +14177,42 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Return the layout direction of a given Drawable. - * - * @param who the Drawable to query - * @hide - */ - public int getResolvedLayoutDirection(Drawable who) { - return (who == mBackground) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT; + * Resolve the Drawables depending on the layout direction. This is implicitly supposing + * that the View directionality can and will be resolved before its Drawables. + * + * Will call {@link View#onResolveDrawables} when resolution is done. + * + * @hide + */ + public void resolveDrawables() { + if (mBackground != null) { + mBackground.setLayoutDirection(getLayoutDirection()); + } + mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED; + onResolveDrawables(getLayoutDirection()); + } + + /** + * Called when layout direction has been resolved. + * + * The default implementation does nothing. + * + * @param layoutDirection The resolved layout direction. + * + * @see #LAYOUT_DIRECTION_LTR + * @see #LAYOUT_DIRECTION_RTL + * + * @hide + */ + public void onResolveDrawables(int layoutDirection) { + } + + private void resetResolvedDrawables() { + mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED; + } + + private boolean isDrawablesResolved() { + return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED; } /** @@ -14020,7 +14263,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getDrawableState */ public void refreshDrawableState() { - mPrivateFlags |= DRAWABLE_STATE_DIRTY; + mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY; drawableStateChanged(); ViewParent parent = mParent; @@ -14040,11 +14283,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #onCreateDrawableState(int) */ public final int[] getDrawableState() { - if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) { + if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) { return mDrawableState; } else { mDrawableState = onCreateDrawableState(0); - mPrivateFlags &= ~DRAWABLE_STATE_DIRTY; + mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY; return mDrawableState; } } @@ -14075,12 +14318,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int privateFlags = mPrivateFlags; int viewStateIndex = 0; - if ((privateFlags & PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED; + if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED; if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED; if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED; - if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED; + if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED; if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED; - if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED; + if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED; if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested && HardwareRenderer.isAvailable()) { // This is set if HW acceleration is requested, even if the current @@ -14088,11 +14331,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // windows to better match their app. viewStateIndex |= VIEW_STATE_ACCELERATED; } - if ((privateFlags & HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED; + if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED; final int privateFlags2 = mPrivateFlags2; - if ((privateFlags2 & DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT; - if ((privateFlags2 & DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED; + if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT; + if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED; drawableState = VIEW_STATE_SETS[viewStateIndex]; @@ -14100,10 +14343,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (false) { Log.i("View", "drawableStateIndex=" + viewStateIndex); Log.i("View", toString() - + " pressed=" + ((privateFlags & PRESSED) != 0) + + " pressed=" + ((privateFlags & PFLAG_PRESSED) != 0) + " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED) + " fo=" + hasFocus() - + " sl=" + ((privateFlags & SELECTED) != 0) + + " sl=" + ((privateFlags & PFLAG_SELECTED) != 0) + " wf=" + hasWindowFocus() + ": " + Arrays.toString(drawableState)); } @@ -14167,7 +14410,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @RemotableViewMethod public void setBackgroundColor(int color) { if (mBackground instanceof ColorDrawable) { - ((ColorDrawable) mBackground).setColor(color); + ((ColorDrawable) mBackground.mutate()).setColor(color); + computeOpaqueFlags(); } else { setBackground(new ColorDrawable(color)); } @@ -14215,6 +14459,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @Deprecated public void setBackgroundDrawable(Drawable background) { + computeOpaqueFlags(); + if (background == mBackground) { return; } @@ -14238,14 +14484,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal padding = new Rect(); sThreadLocal.set(padding); } + resetResolvedDrawables(); + background.setLayoutDirection(getLayoutDirection()); if (background.getPadding(padding)) { - switch (background.getResolvedLayoutDirectionSelf()) { + resetResolvedPadding(); + switch (background.getLayoutDirection()) { case LAYOUT_DIRECTION_RTL: - setPadding(padding.right, padding.top, padding.left, padding.bottom); + mUserPaddingLeftInitial = padding.right; + mUserPaddingRightInitial = padding.left; + internalSetPadding(padding.right, padding.top, padding.left, padding.bottom); break; case LAYOUT_DIRECTION_LTR: default: - setPadding(padding.left, padding.top, padding.right, padding.bottom); + mUserPaddingLeftInitial = padding.left; + mUserPaddingRightInitial = padding.right; + internalSetPadding(padding.left, padding.top, padding.right, padding.bottom); } } @@ -14263,23 +14516,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal background.setVisible(getVisibility() == VISIBLE, false); mBackground = background; - if ((mPrivateFlags & SKIP_DRAW) != 0) { - mPrivateFlags &= ~SKIP_DRAW; - mPrivateFlags |= ONLY_DRAWS_BACKGROUND; + if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) { + mPrivateFlags &= ~PFLAG_SKIP_DRAW; + mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND; requestLayout = true; } } else { /* Remove the background */ mBackground = null; - if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) { + if ((mPrivateFlags & PFLAG_ONLY_DRAWS_BACKGROUND) != 0) { /* * This view ONLY drew the background before and we're removing * the background, so now it won't draw anything * (hence we SKIP_DRAW) */ - mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND; - mPrivateFlags |= SKIP_DRAW; + mPrivateFlags &= ~PFLAG_ONLY_DRAWS_BACKGROUND; + mPrivateFlags |= PFLAG_SKIP_DRAW; } /* @@ -14335,14 +14588,21 @@ 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) { - mUserPaddingStart = -1; - mUserPaddingEnd = -1; - mUserPaddingRelative = false; + resetResolvedPadding(); + + mUserPaddingStart = UNDEFINED_PADDING; + mUserPaddingEnd = UNDEFINED_PADDING; + + mUserPaddingLeftInitial = left; + mUserPaddingRightInitial = right; internalSetPadding(left, top, right, bottom); } - private void internalSetPadding(int left, int top, int right, int bottom) { + /** + * @hide + */ + protected void internalSetPadding(int left, int top, int right, int bottom) { mUserPaddingLeft = left; mUserPaddingRight = right; mUserPaddingBottom = bottom; @@ -14357,7 +14617,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal ? 0 : getVerticalScrollbarWidth(); switch (mVerticalScrollbarPosition) { case SCROLLBAR_POSITION_DEFAULT: - if (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) { + if (isLayoutRtl()) { left += offset; } else { right += offset; @@ -14402,25 +14662,36 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Sets the relative padding. The view may add on the space required to display * the scrollbars, depending on the style and visibility of the scrollbars. + * So the values returned from {@link #getPaddingStart}, {@link #getPaddingTop}, + * {@link #getPaddingEnd} and {@link #getPaddingBottom} may be different * from the values set in this call. * + * @attr ref android.R.styleable#View_padding + * @attr ref android.R.styleable#View_paddingBottom + * @attr ref android.R.styleable#View_paddingStart + * @attr ref android.R.styleable#View_paddingEnd + * @attr ref android.R.styleable#View_paddingTop * @param start the start padding in pixels * @param top the top padding in pixels * @param end the end padding in pixels * @param bottom the bottom padding in pixels - * @hide */ public void setPaddingRelative(int start, int top, int end, int bottom) { + resetResolvedPadding(); + mUserPaddingStart = start; mUserPaddingEnd = end; - mUserPaddingRelative = true; - switch(getResolvedLayoutDirection()) { + switch(getLayoutDirection()) { case LAYOUT_DIRECTION_RTL: + mUserPaddingLeftInitial = end; + mUserPaddingRightInitial = start; internalSetPadding(end, top, start, bottom); break; case LAYOUT_DIRECTION_LTR: default: + mUserPaddingLeftInitial = start; + mUserPaddingRightInitial = end; internalSetPadding(start, top, end, bottom); } } @@ -14453,6 +14724,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return the left padding in pixels */ public int getPaddingLeft() { + if (!isPaddingResolved()) { + resolvePadding(); + } return mPaddingLeft; } @@ -14462,10 +14736,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * required to display the scrollbars as well. * * @return the start padding in pixels - * @hide */ public int getPaddingStart() { - return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + if (!isPaddingResolved()) { + resolvePadding(); + } + return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? mPaddingRight : mPaddingLeft; } @@ -14477,6 +14753,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return the right padding in pixels */ public int getPaddingRight() { + if (!isPaddingResolved()) { + resolvePadding(); + } return mPaddingRight; } @@ -14486,22 +14765,43 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * required to display the scrollbars as well. * * @return the end padding in pixels - * @hide */ public int getPaddingEnd() { - return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + if (!isPaddingResolved()) { + resolvePadding(); + } + return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? mPaddingLeft : mPaddingRight; } /** * Return if the padding as been set thru relative values - * {@link #setPaddingRelative(int, int, int, int)} + * {@link #setPaddingRelative(int, int, int, int)} or thru + * @attr ref android.R.styleable#View_paddingStart or + * @attr ref android.R.styleable#View_paddingEnd * * @return true if the padding is relative or false if it is not. - * @hide */ public boolean isPaddingRelative() { - return mUserPaddingRelative; + return (mUserPaddingStart != UNDEFINED_PADDING || mUserPaddingEnd != UNDEFINED_PADDING); + } + + /** + * @hide + */ + public void resetPaddingToInitialValues() { + if (isRtlCompatibilityMode()) { + mPaddingLeft = mUserPaddingLeftInitial; + mPaddingRight = mUserPaddingRightInitial; + } else { + if (isLayoutRtl()) { + mPaddingLeft = mUserPaddingRightInitial; + mPaddingRight = mUserPaddingLeftInitial; + } else { + mPaddingLeft = mUserPaddingLeftInitial; + mPaddingRight = mUserPaddingRightInitial; + } + } } /** @@ -14530,8 +14830,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param selected true if the view must be selected, false otherwise */ public void setSelected(boolean selected) { - if (((mPrivateFlags & SELECTED) != 0) != selected) { - mPrivateFlags = (mPrivateFlags & ~SELECTED) | (selected ? SELECTED : 0); + if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) { + mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0); if (!selected) resetPressedState(); invalidate(true); refreshDrawableState(); @@ -14559,7 +14859,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @ViewDebug.ExportedProperty public boolean isSelected() { - return (mPrivateFlags & SELECTED) != 0; + return (mPrivateFlags & PFLAG_SELECTED) != 0; } /** @@ -14576,8 +14876,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param activated true if the view must be activated, false otherwise */ public void setActivated(boolean activated) { - if (((mPrivateFlags & ACTIVATED) != 0) != activated) { - mPrivateFlags = (mPrivateFlags & ~ACTIVATED) | (activated ? ACTIVATED : 0); + if (((mPrivateFlags & PFLAG_ACTIVATED) != 0) != activated) { + mPrivateFlags = (mPrivateFlags & ~PFLAG_ACTIVATED) | (activated ? PFLAG_ACTIVATED : 0); invalidate(true); refreshDrawableState(); dispatchSetActivated(activated); @@ -14601,7 +14901,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @ViewDebug.ExportedProperty public boolean isActivated() { - return (mPrivateFlags & ACTIVATED) != 0; + return (mPrivateFlags & PFLAG_ACTIVATED) != 0; } /** @@ -14879,6 +15179,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setId(int id) { mID = id; + if (mID == View.NO_ID && mLabelForId != View.NO_ID) { + mID = generateViewId(); + } } /** @@ -14889,9 +15192,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setIsRootNamespace(boolean isRoot) { if (isRoot) { - mPrivateFlags |= IS_ROOT_NAMESPACE; + mPrivateFlags |= PFLAG_IS_ROOT_NAMESPACE; } else { - mPrivateFlags &= ~IS_ROOT_NAMESPACE; + mPrivateFlags &= ~PFLAG_IS_ROOT_NAMESPACE; } } @@ -14901,7 +15204,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return true if the view belongs to the root namespace, false otherwise */ public boolean isRootNamespace() { - return (mPrivateFlags&IS_ROOT_NAMESPACE) != 0; + return (mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0; } /** @@ -15050,7 +15353,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } Log.d(VIEW_LOG_TAG, output); - if ((mPrivateFlags & FOCUSED) != 0) { + if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { output = debugIndent(depth) + " FOCUSED"; Log.d(VIEW_LOG_TAG, output); } @@ -15130,12 +15433,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * tree. */ public void requestLayout() { - mPrivateFlags |= FORCE_LAYOUT; - mPrivateFlags |= INVALIDATED; - - if (mLayoutParams != null) { - mLayoutParams.onResolveLayoutDirection(getResolvedLayoutDirection()); - } + mPrivateFlags |= PFLAG_FORCE_LAYOUT; + mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); @@ -15148,8 +15447,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * on the parent. */ public void forceLayout() { - mPrivateFlags |= FORCE_LAYOUT; - mPrivateFlags |= INVALIDATED; + mPrivateFlags |= PFLAG_FORCE_LAYOUT; + mPrivateFlags |= PFLAG_INVALIDATED; } /** @@ -15173,25 +15472,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #onMeasure(int, int) */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { - if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || + if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag - mPrivateFlags &= ~MEASURED_DIMENSION_SET; + mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; + + resolveRtlPropertiesIfNeeded(); // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer - if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { + if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } - mPrivateFlags |= LAYOUT_REQUIRED; + mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; @@ -15265,7 +15566,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; - mPrivateFlags |= MEASURED_DIMENSION_SET; + mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; } /** @@ -15509,7 +15810,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getAnimation() */ protected void onAnimationStart() { - mPrivateFlags |= ANIMATION_STARTED; + mPrivateFlags |= PFLAG_ANIMATION_STARTED; } /** @@ -15521,7 +15822,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getAnimation() */ protected void onAnimationEnd() { - mPrivateFlags &= ~ANIMATION_STARTED; + mPrivateFlags &= ~PFLAG_ANIMATION_STARTED; } /** @@ -15558,14 +15859,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal final AttachInfo attachInfo = mAttachInfo; if (region != null && attachInfo != null) { final int pflags = mPrivateFlags; - if ((pflags & SKIP_DRAW) == 0) { + if ((pflags & PFLAG_SKIP_DRAW) == 0) { // The SKIP_DRAW flag IS NOT set, so this view draws. We need to // remove it from the transparent region. final int[] location = attachInfo.mTransparentLocation; getLocationInWindow(location); region.op(location[0], location[1], location[0] + mRight - mLeft, location[1] + mBottom - mTop, Region.Op.DIFFERENCE); - } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) { + } else if ((pflags & PFLAG_ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) { // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable // exists, so we remove the background drawable's non-transparent // parts from this transparent region. @@ -16042,7 +16343,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } boolean canAcceptDrag() { - return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0; + return (mPrivateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0; } /** @@ -16297,6 +16598,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * {@link #TEXT_DIRECTION_LOCALE} + * * @hide */ @ViewDebug.ExportedProperty(category = "text", mapping = { @@ -16307,8 +16609,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"), @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE") }) - public int getTextDirection() { - return (mPrivateFlags2 & TEXT_DIRECTION_MASK) >> TEXT_DIRECTION_MASK_SHIFT; + public int getRawTextDirection() { + return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_MASK) >> PFLAG2_TEXT_DIRECTION_MASK_SHIFT; } /** @@ -16322,15 +16624,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * {@link #TEXT_DIRECTION_LOCALE} - * @hide + * + * Resolution will be done if the value is set to TEXT_DIRECTION_INHERIT. The resolution + * proceeds up the parent chain of the view to get the value. If there is no parent, then it will + * return the default {@link #TEXT_DIRECTION_FIRST_STRONG}. */ public void setTextDirection(int textDirection) { - if (getTextDirection() != textDirection) { + if (getRawTextDirection() != textDirection) { // Reset the current text direction and the resolved one - mPrivateFlags2 &= ~TEXT_DIRECTION_MASK; + mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK; resetResolvedTextDirection(); // Set the new text direction - mPrivateFlags2 |= ((textDirection << TEXT_DIRECTION_MASK_SHIFT) & TEXT_DIRECTION_MASK); + mPrivateFlags2 |= ((textDirection << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) & PFLAG2_TEXT_DIRECTION_MASK); + // Do resolution + resolveTextDirection(); + // Notify change + onRtlPropertiesChanged(getLayoutDirection()); // Refresh requestLayout(); invalidate(true); @@ -16340,11 +16649,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Return the resolved text direction. * - * This needs resolution if the value is TEXT_DIRECTION_INHERIT. The resolution matches - * {@link #getTextDirection()}if it is not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds - * up the parent chain of the view. if there is no parent, then it will return the default - * {@link #TEXT_DIRECTION_FIRST_STRONG}. - * * @return the resolved text direction. Returns one of: * * {@link #TEXT_DIRECTION_FIRST_STRONG} @@ -16352,51 +16656,56 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * {@link #TEXT_DIRECTION_LOCALE} - * @hide */ - public int getResolvedTextDirection() { - // The text direction will be resolved only if needed - if ((mPrivateFlags2 & TEXT_DIRECTION_RESOLVED) != TEXT_DIRECTION_RESOLVED) { - resolveTextDirection(); - } - return (mPrivateFlags2 & TEXT_DIRECTION_RESOLVED_MASK) >> TEXT_DIRECTION_RESOLVED_MASK_SHIFT; + public int getTextDirection() { + return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT; } /** - * Resolve the text direction. Will call {@link View#onResolvedTextDirectionChanged} when - * resolution is done. + * Resolve the text direction. + * + * @return true if resolution has been done, false otherwise. + * * @hide */ - public void resolveTextDirection() { + public boolean resolveTextDirection() { // Reset any previous text direction resolution - mPrivateFlags2 &= ~(TEXT_DIRECTION_RESOLVED | TEXT_DIRECTION_RESOLVED_MASK); + mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK); if (hasRtlSupport()) { // Set resolved text direction flag depending on text direction flag - final int textDirection = getTextDirection(); + final int textDirection = getRawTextDirection(); switch(textDirection) { case TEXT_DIRECTION_INHERIT: - if (canResolveTextDirection()) { - ViewGroup viewGroup = ((ViewGroup) mParent); - - // Set current resolved direction to the same value as the parent's one - final int parentResolvedDirection = viewGroup.getResolvedTextDirection(); - switch (parentResolvedDirection) { - case TEXT_DIRECTION_FIRST_STRONG: - case TEXT_DIRECTION_ANY_RTL: - case TEXT_DIRECTION_LTR: - case TEXT_DIRECTION_RTL: - case TEXT_DIRECTION_LOCALE: - mPrivateFlags2 |= - (parentResolvedDirection << TEXT_DIRECTION_RESOLVED_MASK_SHIFT); - break; - default: - // Default resolved direction is "first strong" heuristic - mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT; - } - } else { + if (!canResolveTextDirection()) { // We cannot do the resolution if there is no parent, so use the default one - mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT; + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; + // Resolution will need to happen again later + return false; + } + + View parent = ((View) mParent); + // Parent has not yet resolved, so we still return the default + if (!parent.isTextDirectionResolved()) { + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; + // Resolution will need to happen again later + return false; + } + + // Set current resolved direction to the same value as the parent's one + final int parentResolvedDirection = parent.getTextDirection(); + switch (parentResolvedDirection) { + case TEXT_DIRECTION_FIRST_STRONG: + case TEXT_DIRECTION_ANY_RTL: + case TEXT_DIRECTION_LTR: + case TEXT_DIRECTION_RTL: + case TEXT_DIRECTION_LOCALE: + mPrivateFlags2 |= + (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT); + break; + default: + // Default resolved direction is "first strong" heuristic + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; } break; case TEXT_DIRECTION_FIRST_STRONG: @@ -16405,65 +16714,64 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal case TEXT_DIRECTION_RTL: case TEXT_DIRECTION_LOCALE: // Resolved direction is the same as text direction - mPrivateFlags2 |= (textDirection << TEXT_DIRECTION_RESOLVED_MASK_SHIFT); + mPrivateFlags2 |= (textDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT); break; default: // Default resolved direction is "first strong" heuristic - mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT; + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; } } else { // Default resolved direction is "first strong" heuristic - mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT; + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; } // Set to resolved - mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED; - onResolvedTextDirectionChanged(); - } - - /** - * Called when text direction has been resolved. Subclasses that care about text direction - * resolution should override this method. - * - * The default implementation does nothing. - * @hide - */ - public void onResolvedTextDirectionChanged() { + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED; + return true; } /** * Check if text direction resolution can be done. * * @return true if text direction resolution can be done otherwise return false. - * @hide */ - public boolean canResolveTextDirection() { - switch (getTextDirection()) { + private boolean canResolveTextDirection() { + switch (getRawTextDirection()) { case TEXT_DIRECTION_INHERIT: - return (mParent != null) && (mParent instanceof ViewGroup); + return (mParent != null) && (mParent instanceof View) && + ((View) mParent).canResolveTextDirection(); default: return true; } } /** - * Reset resolved text direction. Text direction can be resolved with a call to - * getResolvedTextDirection(). Will call {@link View#onResolvedTextDirectionReset} when - * reset is done. + * Reset resolved text direction. Text direction will be resolved during a call to + * {@link #onMeasure(int, int)}. + * * @hide */ public void resetResolvedTextDirection() { - mPrivateFlags2 &= ~(TEXT_DIRECTION_RESOLVED | TEXT_DIRECTION_RESOLVED_MASK); - onResolvedTextDirectionReset(); + // Reset any previous text direction resolution + mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK); + // Set to default value + mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT; } /** - * Called when text direction is reset. Subclasses that care about text direction reset should - * override this method and do a reset of the text direction of their children. The default - * implementation does nothing. + * @return true if text direction is inherited. + * * @hide */ - public void onResolvedTextDirectionReset() { + public boolean isTextDirectionInherited() { + return (getRawTextDirection() == TEXT_DIRECTION_INHERIT); + } + + /** + * @return true if text direction is resolved. + */ + private boolean isTextDirectionResolved() { + return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED; } /** @@ -16479,6 +16787,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_ALIGNMENT_TEXT_END}, * {@link #TEXT_ALIGNMENT_VIEW_START}, * {@link #TEXT_ALIGNMENT_VIEW_END} + * * @hide */ @ViewDebug.ExportedProperty(category = "text", mapping = { @@ -16490,8 +16799,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") }) - public int getTextAlignment() { - return (mPrivateFlags2 & TEXT_ALIGNMENT_MASK) >> TEXT_ALIGNMENT_MASK_SHIFT; + public int getRawTextAlignment() { + return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_MASK) >> PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT; } /** @@ -16507,16 +16816,24 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_ALIGNMENT_VIEW_START}, * {@link #TEXT_ALIGNMENT_VIEW_END} * + * Resolution will be done if the value is set to TEXT_ALIGNMENT_INHERIT. The resolution + * proceeds up the parent chain of the view to get the value. If there is no parent, then it + * will return the default {@link #TEXT_ALIGNMENT_GRAVITY}. + * * @attr ref android.R.styleable#View_textAlignment - * @hide */ public void setTextAlignment(int textAlignment) { - if (textAlignment != getTextAlignment()) { + if (textAlignment != getRawTextAlignment()) { // Reset the current and resolved text alignment - mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK; + mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK; resetResolvedTextAlignment(); // Set the new text alignment - mPrivateFlags2 |= ((textAlignment << TEXT_ALIGNMENT_MASK_SHIFT) & TEXT_ALIGNMENT_MASK); + mPrivateFlags2 |= + ((textAlignment << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) & PFLAG2_TEXT_ALIGNMENT_MASK); + // Do resolution + resolveTextAlignment(); + // Notify change + onRtlPropertiesChanged(getLayoutDirection()); // Refresh requestLayout(); invalidate(true); @@ -16526,10 +16843,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Return the resolved text alignment. * - * The resolved text alignment. This needs resolution if the value is - * TEXT_ALIGNMENT_INHERIT. The resolution matches {@link #setTextAlignment(int)} if it is - * not TEXT_ALIGNMENT_INHERIT, otherwise resolution proceeds up the parent chain of the view. - * * @return the resolved text alignment. Returns one of: * * {@link #TEXT_ALIGNMENT_GRAVITY}, @@ -16538,7 +16851,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_ALIGNMENT_TEXT_END}, * {@link #TEXT_ALIGNMENT_VIEW_START}, * {@link #TEXT_ALIGNMENT_VIEW_END} - * @hide */ @ViewDebug.ExportedProperty(category = "text", mapping = { @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"), @@ -16549,53 +16861,59 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") }) - public int getResolvedTextAlignment() { - // If text alignment is not resolved, then resolve it - if ((mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED) != TEXT_ALIGNMENT_RESOLVED) { - resolveTextAlignment(); - } - return (mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED_MASK) >> TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + public int getTextAlignment() { + return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >> + PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; } /** - * Resolve the text alignment. Will call {@link View#onResolvedTextAlignmentChanged} when - * resolution is done. + * Resolve the text alignment. + * + * @return true if resolution has been done, false otherwise. + * * @hide */ - public void resolveTextAlignment() { + public boolean resolveTextAlignment() { // Reset any previous text alignment resolution - mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK); + mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK); if (hasRtlSupport()) { // Set resolved text alignment flag depending on text alignment flag - final int textAlignment = getTextAlignment(); + final int textAlignment = getRawTextAlignment(); switch (textAlignment) { case TEXT_ALIGNMENT_INHERIT: // Check if we can resolve the text alignment - if (canResolveLayoutDirection() && mParent instanceof View) { - View view = (View) mParent; - - final int parentResolvedTextAlignment = view.getResolvedTextAlignment(); - switch (parentResolvedTextAlignment) { - case TEXT_ALIGNMENT_GRAVITY: - case TEXT_ALIGNMENT_TEXT_START: - case TEXT_ALIGNMENT_TEXT_END: - case TEXT_ALIGNMENT_CENTER: - case TEXT_ALIGNMENT_VIEW_START: - case TEXT_ALIGNMENT_VIEW_END: - // Resolved text alignment is the same as the parent resolved - // text alignment - mPrivateFlags2 |= - (parentResolvedTextAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); - break; - default: - // Use default resolved text alignment - mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; - } - } - else { + if (!canResolveTextAlignment()) { // We cannot do the resolution if there is no parent so use the default - mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; + // Resolution will need to happen again later + return false; + } + View parent = (View) mParent; + + // Parent has not yet resolved, so we still return the default + if (!parent.isTextAlignmentResolved()) { + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; + // Resolution will need to happen again later + return false; + } + + final int parentResolvedTextAlignment = parent.getTextAlignment(); + switch (parentResolvedTextAlignment) { + case TEXT_ALIGNMENT_GRAVITY: + case TEXT_ALIGNMENT_TEXT_START: + case TEXT_ALIGNMENT_TEXT_END: + case TEXT_ALIGNMENT_CENTER: + case TEXT_ALIGNMENT_VIEW_START: + case TEXT_ALIGNMENT_VIEW_END: + // Resolved text alignment is the same as the parent resolved + // text alignment + mPrivateFlags2 |= + (parentResolvedTextAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); + break; + default: + // Use default resolved text alignment + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; } break; case TEXT_ALIGNMENT_GRAVITY: @@ -16605,66 +16923,82 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal case TEXT_ALIGNMENT_VIEW_START: case TEXT_ALIGNMENT_VIEW_END: // Resolved text alignment is the same as text alignment - mPrivateFlags2 |= (textAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); + mPrivateFlags2 |= (textAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); break; default: // Use default resolved text alignment - mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; } } else { // Use default resolved text alignment - mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; } // Set the resolved - mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED; - onResolvedTextAlignmentChanged(); + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED; + return true; } /** * Check if text alignment resolution can be done. * * @return true if text alignment resolution can be done otherwise return false. - * @hide */ - public boolean canResolveTextAlignment() { - switch (getTextAlignment()) { + private boolean canResolveTextAlignment() { + switch (getRawTextAlignment()) { case TEXT_DIRECTION_INHERIT: - return (mParent != null); + return (mParent != null) && (mParent instanceof View) && + ((View) mParent).canResolveTextAlignment(); default: return true; } } /** - * Called when text alignment has been resolved. Subclasses that care about text alignment - * resolution should override this method. + * Reset resolved text alignment. Text alignment will be resolved during a call to + * {@link #onMeasure(int, int)}. * - * The default implementation does nothing. * @hide */ - public void onResolvedTextAlignmentChanged() { + public void resetResolvedTextAlignment() { + // Reset any previous text alignment resolution + mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK); + // Set to default + mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT; } /** - * Reset resolved text alignment. Text alignment can be resolved with a call to - * getResolvedTextAlignment(). Will call {@link View#onResolvedTextAlignmentReset} when - * reset is done. + * @return true if text alignment is inherited. + * * @hide */ - public void resetResolvedTextAlignment() { - // Reset any previous text alignment resolution - mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK); - onResolvedTextAlignmentReset(); + public boolean isTextAlignmentInherited() { + return (getRawTextAlignment() == TEXT_ALIGNMENT_INHERIT); } /** - * Called when text alignment is reset. Subclasses that care about text alignment reset should - * override this method and do a reset of the text alignment of their children. The default - * implementation does nothing. - * @hide + * @return true if text alignment is resolved. */ - public void onResolvedTextAlignmentReset() { + private boolean isTextAlignmentResolved() { + return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED; + } + + /** + * Generate a value suitable for use in {@link #setId(int)}. + * This value will not collide with ID values generated at build time by aapt for R.id. + * + * @return a generated ID value + */ + public static int generateViewId() { + for (;;) { + final int result = sNextGeneratedId.get(); + // aapt-generated IDs have the high byte nonzero; clamp to the range under that. + int newValue = result + 1; + if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. + if (sNextGeneratedId.compareAndSet(result, newValue)) { + return result; + } + } } // @@ -16967,7 +17301,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private final class CheckForTap implements Runnable { public void run() { - mPrivateFlags &= ~PREPRESSED; + mPrivateFlags &= ~PFLAG_PREPRESSED; setPressed(true); checkForLongClick(ViewConfiguration.getTapTimeout()); } @@ -17315,6 +17649,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal final IBinder mWindowToken; + final Display mDisplay; + final Callbacks mRootCallbacks; HardwareCanvas mHardwareCanvas; @@ -17359,20 +17695,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int mWindowTop; /** - * Left actual position of this view's window. - * - * TODO: This is a workaround for 6623031. Remove when fixed. - */ - int mActualWindowLeft; - - /** - * Actual top position of this view's window. - * - * TODO: This is a workaround for 6623031. Remove when fixed. - */ - int mActualWindowTop; - - /** * Indicates whether views need to use 32-bit drawing caches */ boolean mUse32BitDrawingCache; @@ -17542,6 +17864,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal final RectF mTmpTransformRect = new RectF(); /** + * Temporary for use in transforming invalidation rect + */ + final Matrix mTmpMatrix = new Matrix(); + + /** + * Temporary for use in transforming invalidation rect + */ + final Transformation mTmpTransformation = new Transformation(); + + /** * Temporary list for use in collecting focusable descendents of a view. */ final ArrayList<View> mTempArrayList = new ArrayList<View>(24); @@ -17578,11 +17910,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param handler the events handler the view must use */ - AttachInfo(IWindowSession session, IWindow window, + AttachInfo(IWindowSession session, IWindow window, Display display, ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) { mSession = session; mWindow = window; mWindowToken = window.asBinder(); + mDisplay = display; mViewRootImpl = viewRootImpl; mHandler = handler; mRootCallbacks = effectPlayer; @@ -17656,23 +17989,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // use use a height of 1, and then wack the matrix each time we // actually use it. shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP); - paint.setShader(shader); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + this.host = host; } public void setFadeColor(int color) { - if (color != 0 && color != mLastColor) { + if (color != mLastColor) { mLastColor = color; - color |= 0xFF000000; - - shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000, - color & 0x00FFFFFF, Shader.TileMode.CLAMP); - paint.setShader(shader); - // Restore the default transfer mode (src_over) - paint.setXfermode(null); + if (color != 0) { + shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000, + color & 0x00FFFFFF, Shader.TileMode.CLAMP); + paint.setShader(shader); + // Restore the default transfer mode (src_over) + paint.setXfermode(null); + } else { + shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP); + paint.setShader(shader); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + } } } @@ -17960,4 +18297,64 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return null; } } + + private class MatchIdPredicate implements Predicate<View> { + public int mId; + + @Override + public boolean apply(View view) { + return (view.mID == mId); + } + } + + private class MatchLabelForPredicate implements Predicate<View> { + private int mLabeledId; + + @Override + public boolean apply(View view) { + return (view.mLabelForId == mLabeledId); + } + } + + /** + * Dump all private flags in readable format, useful for documentation and + * sanity checking. + */ + private static void dumpFlags() { + final HashMap<String, String> found = Maps.newHashMap(); + try { + for (Field field : View.class.getDeclaredFields()) { + final int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) { + if (field.getType().equals(int.class)) { + final int value = field.getInt(null); + dumpFlag(found, field.getName(), value); + } else if (field.getType().equals(int[].class)) { + final int[] values = (int[]) field.get(null); + for (int i = 0; i < values.length; i++) { + dumpFlag(found, field.getName() + "[" + i + "]", values[i]); + } + } + } + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + + final ArrayList<String> keys = Lists.newArrayList(); + keys.addAll(found.keySet()); + Collections.sort(keys); + for (String key : keys) { + Log.d(VIEW_LOG_TAG, found.get(key)); + } + } + + private static void dumpFlag(HashMap<String, String> found, String name, int value) { + // Sort flags by prefix, then by bits, always keeping unique keys + final String bits = String.format("%32s", Integer.toBinaryString(value)).replace('0', ' '); + final int prefix = name.indexOf('_'); + final String key = (prefix > 0 ? name.substring(0, prefix) : name) + bits + name; + final String output = bits + " " + name; + found.put(key, output); + } } diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 823befb..499075e 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -20,6 +20,7 @@ import android.app.AppGlobals; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Point; import android.os.RemoteException; import android.provider.Settings; import android.util.DisplayMetrics; @@ -30,24 +31,6 @@ import android.util.SparseArray; */ public class ViewConfiguration { /** - * Expected bit depth of the display panel. - * - * @hide - */ - public static final float PANEL_BIT_DEPTH = 24; - - /** - * Minimum alpha required for a view to draw. - * - * @hide - */ - public static final float ALPHA_THRESHOLD = 0.5f / PANEL_BIT_DEPTH; - /** - * @hide - */ - public static final float ALPHA_THRESHOLD_INT = 0x7f / PANEL_BIT_DEPTH; - - /** * Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in * dips */ @@ -295,15 +278,18 @@ public class ViewConfiguration { mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_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 * display.getRawWidth() * display.getRawHeight(); + final WindowManager win = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + final Display display = win.getDefaultDisplay(); + final Point size = new Point(); + display.getRealSize(size); + mMaximumDrawingCacheSize = 4 * size.x * size.y; mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f); mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f); if (!sHasPermanentMenuKeySet) { - IWindowManager wm = Display.getWindowManager(); + IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); try { sHasPermanentMenuKey = !wm.hasSystemNavBar() && !wm.hasNavigationBar(); sHasPermanentMenuKeySet = true; diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index dd671dc..c013d85 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -255,6 +255,35 @@ public class ViewDebug { boolean retrieveReturn() default false; } + /** + * Allows a View to inject custom children into HierarchyViewer. For example, + * WebView uses this to add its internal layer tree as a child to itself + * @hide + */ + public interface HierarchyHandler { + /** + * Dumps custom children to hierarchy viewer. + * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int) + * for the format + * + * An empty implementation should simply do nothing + * + * @param out The output writer + * @param level The indentation level + */ + public void dumpViewHierarchyWithProperties(BufferedWriter out, int level); + + /** + * Returns a View to enable grabbing screenshots from custom children + * returned in dumpViewHierarchyWithProperties. + * + * @param className The className of the view to find + * @param hashCode The hashCode of the view to find + * @return the View to capture from, or null if not found + */ + public View findHierarchyView(String className, int hashCode); + } + private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null; private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null; @@ -468,8 +497,8 @@ public class ViewDebug { throws IOException { long durationMeasure = - (root || (view.mPrivateFlags & View.MEASURED_DIMENSION_SET) != 0) ? profileViewOperation( - view, new ViewOperation<Void>() { + (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0) + ? profileViewOperation(view, new ViewOperation<Void>() { public Void[] pre() { forceLayout(view); return null; @@ -495,8 +524,8 @@ public class ViewDebug { }) : 0; long durationLayout = - (root || (view.mPrivateFlags & View.LAYOUT_REQUIRED) != 0) ? profileViewOperation( - view, new ViewOperation<Void>() { + (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0) + ? profileViewOperation(view, new ViewOperation<Void>() { public Void[] pre() { return null; } @@ -509,15 +538,14 @@ public class ViewDebug { } }) : 0; long durationDraw = - (root || !view.willNotDraw() || (view.mPrivateFlags & View.DRAWN) != 0) ? profileViewOperation( - view, - new ViewOperation<Object>() { + (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0) + ? profileViewOperation(view, new ViewOperation<Object>() { public Object[] pre() { final DisplayMetrics metrics = (view != null && view.getResources() != null) ? view.getResources().getDisplayMetrics() : null; final Bitmap bitmap = metrics != null ? - Bitmap.createBitmap(metrics.widthPixels, + Bitmap.createBitmap(metrics, metrics.widthPixels, metrics.heightPixels, Bitmap.Config.RGB_565) : null; final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null; return new Object[] { @@ -622,7 +650,7 @@ public class ViewDebug { final boolean localVisible = view.getVisibility() == View.VISIBLE && visible; - if ((view.mPrivateFlags & View.SKIP_DRAW) != View.SKIP_DRAW) { + if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) { final int id = view.getId(); String name = view.getClass().getSimpleName(); if (id != View.NO_ID) { @@ -677,7 +705,8 @@ public class ViewDebug { Log.w("View", "Failed to create capture bitmap!"); // Send an empty one so that it doesn't get stuck waiting for // something. - b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(), + 1, 1, Bitmap.Config.ARGB_8888); } BufferedOutputStream out = null; @@ -759,6 +788,13 @@ public class ViewDebug { } else if (isRequestedView(view, className, hashCode)) { return view; } + if (view instanceof HierarchyHandler) { + final View found = ((HierarchyHandler)view) + .findHierarchyView(className, hashCode); + if (found != null) { + return found; + } + } } return null; @@ -783,6 +819,9 @@ public class ViewDebug { dumpViewWithProperties(context, view, out, level + 1); } } + if (group instanceof HierarchyHandler) { + ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1); + } } private static boolean dumpViewWithProperties(Context context, View view, @@ -1139,10 +1178,14 @@ public class ViewDebug { private static void writeValue(BufferedWriter out, Object value) throws IOException { if (value != null) { - String output = value.toString().replace("\n", "\\n"); - out.write(String.valueOf(output.length())); - out.write(","); - out.write(output); + String output = "[EXCEPTION]"; + try { + output = value.toString().replace("\n", "\\n"); + } finally { + out.write(String.valueOf(output.length())); + out.write(","); + out.write(output); + } } else { out.write("4,null"); } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 0c83a73..db1c00a 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -170,6 +170,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * This field should be made private, so it is hidden from the SDK. * {@hide} */ + @ViewDebug.ExportedProperty(flagMapping = { + @ViewDebug.FlagToString(mask = FLAG_CLIP_CHILDREN, equals = FLAG_CLIP_CHILDREN, + name = "CLIP_CHILDREN"), + @ViewDebug.FlagToString(mask = FLAG_CLIP_TO_PADDING, equals = FLAG_CLIP_TO_PADDING, + name = "CLIP_TO_PADDING"), + @ViewDebug.FlagToString(mask = FLAG_PADDING_NOT_NULL, equals = FLAG_PADDING_NOT_NULL, + name = "PADDING_NOT_NULL") + }) protected int mGroupFlags; /* @@ -404,10 +412,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // views during a transition when they otherwise would have become gone/invisible private ArrayList<View> mVisibilityChangingChildren; - // Indicates whether this container will use its children layers to draw - @ViewDebug.ExportedProperty(category = "drawing") - boolean mDrawLayers = true; - // Indicates how many of this container's child subtrees contain transient state @ViewDebug.ExportedProperty(category = "layout") private int mChildCountWithTransientState = 0; @@ -628,11 +632,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * FOCUS_RIGHT, or 0 for not applicable. */ public View focusSearch(View focused, int direction) { - // If we are moving accessibility focus we want to consider all - // views no matter if they are on the screen. It is responsibility - // of the accessibility service to check whether the result is in - // the screen. - if (isRootNamespace() && (direction & FOCUS_ACCESSIBILITY) == 0) { + if (isRootNamespace()) { // root namespace means we should consider ourselves the top of the // tree for focus searching; otherwise we could be focus searching // into other tabs. see LocalActivityManager and TabHost for more info @@ -804,7 +804,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public boolean hasFocus() { - return (mPrivateFlags & FOCUSED) != 0 || mFocused != null; + return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null; } /* @@ -867,8 +867,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int descendantFocusability = getDescendantFocusability(); - if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS - || (focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) { + if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { final int count = mChildrenCount; final View[] children = mChildren; @@ -886,9 +885,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // among the focusable children would be more interesting. if (descendantFocusability != FOCUS_AFTER_DESCENDANTS // No focusable descendants - || (focusableCount == views.size()) - // We are collecting accessibility focusables. - || (focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) { + || (focusableCount == views.size())) { super.addFocusables(views, direction, focusableMode); } } @@ -901,7 +898,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = 0; i < childrenCount; i++) { View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE - && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { + && (child.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { child.findViewsWithText(outViews, text, flags); } } @@ -1180,7 +1177,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View view = mCurrentDragView; event.mAction = DragEvent.ACTION_DRAG_EXITED; view.dispatchDragEvent(event); - view.mPrivateFlags2 &= ~View.DRAG_HOVERED; + view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED; view.refreshDrawableState(); } mCurrentDragView = target; @@ -1189,7 +1186,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (target != null) { event.mAction = DragEvent.ACTION_DRAG_ENTERED; target.dispatchDragEvent(event); - target.mPrivateFlags2 |= View.DRAG_HOVERED; + target.mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED; target.refreshDrawableState(); } event.mAction = action; // restore the event's original state @@ -1223,7 +1220,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (mCurrentDragView != null) { final View view = mCurrentDragView; view.dispatchDragEvent(event); - view.mPrivateFlags2 &= ~View.DRAG_HOVERED; + view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED; view.refreshDrawableState(); mCurrentDragView = null; @@ -1284,7 +1281,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mDragNotifiedChildren.add(child); canAccept = child.dispatchDragEvent(mCurrentDrag); if (canAccept && !child.canAcceptDrag()) { - child.mPrivateFlags2 |= View.DRAG_CAN_ACCEPT; + child.mPrivateFlags2 |= View.PFLAG2_DRAG_CAN_ACCEPT; child.refreshDrawableState(); } } @@ -1333,9 +1330,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public boolean dispatchKeyEventPreIme(KeyEvent event) { - if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) + == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { return super.dispatchKeyEventPreIme(event); - } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) + == PFLAG_HAS_BOUNDS) { return mFocused.dispatchKeyEventPreIme(event); } return false; @@ -1350,11 +1349,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mInputEventConsistencyVerifier.onKeyEvent(event, 1); } - if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) + == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { if (super.dispatchKeyEvent(event)) { return true; } - } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) + == PFLAG_HAS_BOUNDS) { if (mFocused.dispatchKeyEvent(event)) { return true; } @@ -1371,9 +1372,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { - if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) + == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { return super.dispatchKeyShortcutEvent(event); - } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) + == PFLAG_HAS_BOUNDS) { return mFocused.dispatchKeyShortcutEvent(event); } return false; @@ -1388,11 +1391,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mInputEventConsistencyVerifier.onTrackballEvent(event, 1); } - if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) + == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { if (super.dispatchTrackballEvent(event)) { return true; } - } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) + == PFLAG_HAS_BOUNDS) { if (mFocused.dispatchTrackballEvent(event)) { return true; } @@ -1658,20 +1663,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * @hide - */ - @Override - public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) { - if (includeForAccessibility() && isActionableForAccessibility()) { - return this; - } - if (mParent != null) { - return mParent.findViewToTakeAccessibilityFocusFromHover(this, descendant); - } - return null; - } - - /** * Implement this method to intercept hover events before they are handled * by child views. * <p> @@ -1732,8 +1723,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final float x = event.getX(); final float y = event.getY(); + final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = childrenCount - 1; i >= 0; i--) { - final View child = children[i]; + final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; + final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; @@ -1755,9 +1748,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override protected boolean dispatchGenericFocusedEvent(MotionEvent event) { // Send the event to the focused child or to this view group if it has focus. - if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) + == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { return super.dispatchGenericFocusedEvent(event); - } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) + == PFLAG_HAS_BOUNDS) { return mFocused.dispatchGenericMotionEvent(event); } return false; @@ -1858,8 +1853,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); + final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = childrenCount - 1; i >= 0; i--) { - final View child = children[i]; + final int childIndex = customOrder ? + getChildDrawingOrder(childrenCount, i) : i; + final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; @@ -1877,7 +1875,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); - mLastTouchDownIndex = i; + mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); @@ -1968,8 +1966,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * Returns true if the flag was previously set. */ private static boolean resetCancelNextUpFlag(View view) { - if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { - view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) { + view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; return true; } return false; @@ -2535,11 +2533,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * {@inheritDoc} + * @hide */ @Override - public void setPadding(int left, int top, int right, int bottom) { - super.setPadding(left, top, right, bottom); + protected void internalSetPadding(int left, int top, int right, int bottom) { + super.internalSetPadding(left, top, right, bottom); if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingBottom) != 0) { mGroupFlags |= FLAG_PADDING_NOT_NULL; @@ -2786,7 +2784,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } // We will draw our child's animation, let's reset the flag - mPrivateFlags &= ~DRAW_ANIMATION; + mPrivateFlags &= ~PFLAG_DRAW_ANIMATION; mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED; boolean more = false; @@ -2906,8 +2904,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View child = children[i]; if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) && child.hasStaticLayer()) { - child.mRecreateDisplayList = (child.mPrivateFlags & INVALIDATED) == INVALIDATED; - child.mPrivateFlags &= ~INVALIDATED; + child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) + == PFLAG_INVALIDATED; + child.mPrivateFlags &= ~PFLAG_INVALIDATED; child.getDisplayList(); child.mRecreateDisplayList = false; } @@ -2930,45 +2929,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * - * @param enabled True if children should be drawn with layers, false otherwise. - * - * @hide - */ - public void setChildrenLayersEnabled(boolean enabled) { - if (enabled != mDrawLayers) { - mDrawLayers = enabled; - invalidate(true); - - boolean flushLayers = !enabled; - AttachInfo info = mAttachInfo; - if (info != null && info.mHardwareRenderer != null && - info.mHardwareRenderer.isEnabled()) { - if (!info.mHardwareRenderer.validate()) { - flushLayers = false; - } - } else { - flushLayers = false; - } - - // We need to invalidate any child with a layer. For instance, - // if a child is backed by a hardware layer and we disable layers - // the child is marked as not dirty (flags cleared the last time - // the child was drawn inside its layer.) However, that child might - // never have created its own display list or have an obsolete - // display list. By invalidating the child we ensure the display - // list is in sync with the content of the hardware layer. - for (int i = 0; i < mChildrenCount; i++) { - View child = mChildren[i]; - if (child.mLayerType != LAYER_TYPE_NONE) { - if (flushLayers) child.flushLayer(); - child.invalidate(true); - } - } - } - } - - /** * By default, children are clipped to their bounds before drawing. This * allows view groups to override this behavior for animations, etc. * @@ -3052,7 +3012,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * * @param enabled True to enable static transformations on children, false otherwise. * - * @see #FLAG_SUPPORT_STATIC_TRANSFORMATIONS + * @see #getChildStaticTransformation(View, android.view.animation.Transformation) */ protected void setStaticTransformationsEnabled(boolean enabled) { setBooleanFlag(FLAG_SUPPORT_STATIC_TRANSFORMATIONS, enabled); @@ -3062,7 +3022,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * Sets <code>t</code> to be the static transformation of the child, if set, returning a * boolean to indicate whether a static transform was set. The default implementation * simply returns <code>false</code>; subclasses may override this method for different - * behavior. + * behavior. {@link #setStaticTransformationsEnabled(boolean)} must be set to true + * for this method to be called. * * @param child The child view whose static transform is being requested * @param t The Transformation which will hold the result @@ -3088,7 +3049,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = 0; i < len; i++) { View v = where[i]; - if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { + if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { @@ -3115,7 +3076,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = 0; i < len; i++) { View v = where[i]; - if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { + if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewWithTag(tag); if (v != null) { @@ -3142,7 +3103,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = 0; i < len; i++) { View v = where[i]; - if (v != childToSkip && (v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { + if (v != childToSkip && (v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewByPredicate(predicate); if (v != null) { @@ -3352,7 +3313,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean preventRequestLayout) { child.mParent = null; addViewInner(child, index, params, preventRequestLayout); - child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN; + child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; return true; } @@ -3362,7 +3323,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @param child the child on which to perform the cleanup */ protected void cleanupLayoutState(View child) { - child.mPrivateFlags &= ~View.FORCE_LAYOUT; + child.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT; } private void addViewInner(View child, int index, LayoutParams params, @@ -3421,6 +3382,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager ai.mKeepScreenOn = lastKeepOn; } + if (child.isLayoutDirectionInherited()) { + child.resetResolvedLayoutDirection(); + child.resolveRtlPropertiesIfNeeded(); + } + onViewAdded(child); if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) { @@ -3655,6 +3621,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager childHasTransientStateChanged(view, false); } + view.resetRtlProperties(); + onViewRemoved(view); needGlobalAttributesUpdate(false); @@ -3838,6 +3806,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Finishes the removal of a detached view. This method will dispatch the detached from * window event and notify the hierarchy change listener. + * <p> + * This method is intended to be lightweight and makes no assumptions about whether the + * parent or child should be redrawn. Proper use of this method will include also making + * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls. + * For example, callers can {@link #post(Runnable) post} a {@link Runnable} + * which performs a {@link #requestLayout()} on the next frame, after all detach/remove + * calls are finished, causing layout to be run prior to redrawing the view hierarchy. * * @param child the child to be definitely removed from the view hierarchy * @param animate if true and the view has an animation, the view is placed in the @@ -3878,10 +3853,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Attaches a view to this view group. Attaching a view assigns this group as the parent, - * sets the layout parameters and puts the view in the list of children so it can be retrieved - * by calling {@link #getChildAt(int)}. - * - * This method should be called only for view which were detached from their parent. + * sets the layout parameters and puts the view in the list of children so that + * it can be retrieved by calling {@link #getChildAt(int)}. + * <p> + * This method is intended to be lightweight and makes no assumptions about whether the + * parent or child should be redrawn. Proper use of this method will include also making + * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls. + * For example, callers can {@link #post(Runnable) post} a {@link Runnable} + * which performs a {@link #requestLayout()} on the next frame, after all detach/attach + * calls are finished, causing layout to be run prior to redrawing the view hierarchy. + * <p> + * This method should be called only for views which were detached from their parent. * * @param child the child to attach * @param index the index at which the child should be attached @@ -3902,9 +3884,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager addInArray(child, index); child.mParent = this; - child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK & ~DRAWING_CACHE_VALID) | - DRAWN | INVALIDATED; - this.mPrivateFlags |= INVALIDATED; + child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK + & ~PFLAG_DRAWING_CACHE_VALID) + | PFLAG_DRAWN | PFLAG_INVALIDATED; + this.mPrivateFlags |= PFLAG_INVALIDATED; if (child.hasFocus()) { requestChildFocus(child, child.findFocus()); @@ -3912,10 +3895,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Detaches a view from its parent. Detaching a view should be temporary and followed - * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} - * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, - * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. + * Detaches a view from its parent. Detaching a view should be followed + * either by a call to + * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} + * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be + * temporary; reattachment or removal should happen within the same drawing cycle as + * detachment. When a view is detached, its parent is null and cannot be retrieved by a + * call to {@link #getChildAt(int)}. * * @param child the child to detach * @@ -3930,10 +3916,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Detaches a view from its parent. Detaching a view should be temporary and followed - * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} - * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, - * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. + * Detaches a view from its parent. Detaching a view should be followed + * either by a call to + * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} + * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be + * temporary; reattachment or removal should happen within the same drawing cycle as + * detachment. When a view is detached, its parent is null and cannot be retrieved by a + * call to {@link #getChildAt(int)}. * * @param index the index of the child to detach * @@ -3948,10 +3937,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Detaches a range of view from their parent. Detaching a view should be temporary and followed - * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} - * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, its - * parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. + * Detaches a range of views from their parents. Detaching a view should be followed + * either by a call to + * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} + * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be + * temporary; reattachment or removal should happen within the same drawing cycle as + * detachment. When a view is detached, its parent is null and cannot be retrieved by a + * call to {@link #getChildAt(int)}. * * @param start the first index of the childrend range to detach * @param count the number of children to detach @@ -3967,10 +3959,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Detaches all views from the parent. Detaching a view should be temporary and followed - * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} - * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, - * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. + * Detaches all views from the parent. Detaching a view should be followed + * either by a call to + * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} + * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be + * temporary; reattachment or removal should happen within the same drawing cycle as + * detachment. When a view is detached, its parent is null and cannot be retrieved by a + * call to {@link #getChildAt(int)}. * * @see #detachViewFromParent(View) * @see #detachViewFromParent(int) @@ -4005,7 +4000,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // If the child is drawing an animation, we want to copy this flag onto // ourselves and the parent to make sure the invalidate request goes // through - final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION; + final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) + == PFLAG_DRAW_ANIMATION; // Check whether the child that requests the invalidate is fully opaque // Views being animated or transformed are not considered opaque because we may @@ -4015,22 +4011,38 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager child.getAnimation() == null && childMatrix.isIdentity(); // Mark the child as dirty, using the appropriate flag // Make sure we do not set both flags at the same time - int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY; + int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY; if (child.mLayerType != LAYER_TYPE_NONE) { - mPrivateFlags |= INVALIDATED; - mPrivateFlags &= ~DRAWING_CACHE_VALID; + mPrivateFlags |= PFLAG_INVALIDATED; + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; child.mLocalDirtyRect.union(dirty); } final int[] location = attachInfo.mInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; - if (!childMatrix.isIdentity()) { + if (!childMatrix.isIdentity() || + (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); - //boundingRect.inset(-0.5f, -0.5f); - childMatrix.mapRect(boundingRect); + Matrix transformMatrix; + if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { + Transformation t = attachInfo.mTmpTransformation; + boolean transformed = getChildStaticTransformation(child, t); + if (transformed) { + transformMatrix = attachInfo.mTmpMatrix; + transformMatrix.set(t.getMatrix()); + if (!childMatrix.isIdentity()) { + transformMatrix.preConcat(childMatrix); + } + } else { + transformMatrix = childMatrix; + } + } else { + transformMatrix = childMatrix; + } + transformMatrix.mapRect(boundingRect); dirty.set((int) (boundingRect.left - 0.5f), (int) (boundingRect.top - 0.5f), (int) (boundingRect.right + 0.5f), @@ -4045,7 +4057,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (drawAnimation) { if (view != null) { - view.mPrivateFlags |= DRAW_ANIMATION; + view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } else if (parent instanceof ViewRootImpl) { ((ViewRootImpl) parent).mIsAnimating = true; } @@ -4056,10 +4068,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (view != null) { if ((view.mViewFlags & FADING_EDGE_MASK) != 0 && view.getSolidColor() == 0) { - opaqueFlag = DIRTY; + opaqueFlag = PFLAG_DIRTY; } - if ((view.mPrivateFlags & DIRTY_MASK) != DIRTY) { - view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag; + if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { + view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } @@ -4090,8 +4102,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * does not intersect with this ViewGroup's bounds. */ public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { - if ((mPrivateFlags & DRAWN) == DRAWN || - (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) { + if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN || + (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != FLAG_OPTIMIZE_INVALIDATE) { dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, @@ -4105,20 +4117,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager dirty.setEmpty(); } } - mPrivateFlags &= ~DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; if (mLayerType != LAYER_TYPE_NONE) { - mPrivateFlags |= INVALIDATED; + mPrivateFlags |= PFLAG_INVALIDATED; mLocalDirtyRect.union(dirty); } return mParent; } else { - mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID; + mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = mLeft; location[CHILD_TOP_INDEX] = mTop; @@ -4130,7 +4142,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (mLayerType != LAYER_TYPE_NONE) { - mPrivateFlags |= INVALIDATED; + mPrivateFlags |= PFLAG_INVALIDATED; mLocalDirtyRect.union(dirty); } @@ -4192,8 +4204,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * coordinate system, pruning the invalidation if the parent has already been invalidated. */ private ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) { - if ((mPrivateFlags & DRAWN) == DRAWN || - (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) { + if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN || + (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { dirty.offset(left - mScrollX, top - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0 || @@ -4956,11 +4968,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager view.clearAnimation(); } - if ((view.mPrivateFlags & ANIMATION_STARTED) == ANIMATION_STARTED) { + if ((view.mPrivateFlags & PFLAG_ANIMATION_STARTED) == PFLAG_ANIMATION_STARTED) { view.onAnimationEnd(); // Should be performed by onAnimationEnd() but this avoid an infinite loop, // so we'd rather be safe than sorry - view.mPrivateFlags &= ~ANIMATION_STARTED; + view.mPrivateFlags &= ~PFLAG_ANIMATION_STARTED; // Draw one more frame after the animation is done mGroupFlags |= FLAG_INVALIDATE_REQUIRED; } @@ -5059,7 +5071,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public boolean gatherTransparentRegion(Region region) { // If no transparent regions requested, we are always opaque. - final boolean meOpaque = (mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) == 0; + final boolean meOpaque = (mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0; if (meOpaque && region == null) { // The caller doesn't care about the region, so stop now. return true; @@ -5084,7 +5096,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ public void requestTransparentRegion(View child) { if (child != null) { - child.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS; + child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS; if (mParent != null) { mParent.requestTransparentRegion(this); } @@ -5249,12 +5261,67 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override - public void onResolvedLayoutDirectionReset() { - // Take care of resetting the children resolution too - final int count = getChildCount(); + public boolean resolveLayoutDirection() { + final boolean result = super.resolveLayoutDirection(); + if (result) { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isLayoutDirectionInherited()) { + child.resolveLayoutDirection(); + } + } + } + return result; + } + + /** + * @hide + */ + @Override + public boolean resolveTextDirection() { + final boolean result = super.resolveTextDirection(); + if (result) { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isTextDirectionInherited()) { + child.resolveTextDirection(); + } + } + } + return result; + } + + /** + * @hide + */ + @Override + public boolean resolveTextAlignment() { + final boolean result = super.resolveTextAlignment(); + if (result) { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isTextAlignmentInherited()) { + child.resolveTextAlignment(); + } + } + } + return result; + } + + /** + * @hide + */ + @Override + public void resetResolvedLayoutDirection() { + super.resetResolvedLayoutDirection(); + + int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); - if (child.getLayoutDirection() == LAYOUT_DIRECTION_INHERIT) { + if (child.isLayoutDirectionInherited()) { child.resetResolvedLayoutDirection(); } } @@ -5264,12 +5331,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override - public void onResolvedTextDirectionReset() { - // Take care of resetting the children resolution too - final int count = getChildCount(); + public void resetResolvedTextDirection() { + super.resetResolvedTextDirection(); + + int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); - if (child.getTextDirection() == TEXT_DIRECTION_INHERIT) { + if (child.isTextDirectionInherited()) { child.resetResolvedTextDirection(); } } @@ -5279,12 +5347,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override - public void onResolvedTextAlignmentReset() { - // Take care of resetting the children resolution too - final int count = getChildCount(); + public void resetResolvedTextAlignment() { + super.resetResolvedTextAlignment(); + + int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); - if (child.getTextAlignment() == TEXT_ALIGNMENT_INHERIT) { + if (child.isTextAlignmentInherited()) { child.resetResolvedTextAlignment(); } } @@ -5448,15 +5517,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Extracts the layout parameters from the supplied attributes. + * Extracts the <code>width</code> and <code>height</code> layout parameters + * from the supplied TypedArray, <code>a</code>, and assigns them + * to the appropriate fields. If, <code>a</code>, does not contain an + * entry for either attribute, the value, {@link ViewGroup.LayoutParams#WRAP_CONTENT}, + * is used as a default. * * @param a the style attributes to extract the parameters from * @param widthAttr the identifier of the width attribute * @param heightAttr the identifier of the height attribute */ protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { - width = a.getLayoutDimension(widthAttr, "layout_width"); - height = a.getLayoutDimension(heightAttr, "layout_height"); + width = a.getLayoutDimension(widthAttr, WRAP_CONTENT); + height = a.getLayoutDimension(heightAttr, WRAP_CONTENT); } /** @@ -5468,7 +5541,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * * {@link View#LAYOUT_DIRECTION_LTR} * {@link View#LAYOUT_DIRECTION_RTL} - * @hide */ public void onResolveLayoutDirection(int layoutDirection) { } @@ -5560,24 +5632,31 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * The start margin in pixels of the child. * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value * to this field. - * @hide */ @ViewDebug.ExportedProperty(category = "layout") - public int startMargin = DEFAULT_RELATIVE; + private int startMargin = DEFAULT_RELATIVE; /** * The end margin in pixels of the child. * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value * to this field. - * @hide */ @ViewDebug.ExportedProperty(category = "layout") - public int endMargin = DEFAULT_RELATIVE; + private int endMargin = DEFAULT_RELATIVE; /** * The default start and end margin. + * @hide */ - static private final int DEFAULT_RELATIVE = Integer.MIN_VALUE; + public static final int DEFAULT_RELATIVE = Integer.MIN_VALUE; + + private int initialLeftMargin; + private int initialRightMargin; + + private static int LAYOUT_DIRECTION_UNDEFINED = -1; + + // Layout direction undefined by default + private int layoutDirection = LAYOUT_DIRECTION_UNDEFINED; /** * Creates a new set of layout parameters. The values are extracted from @@ -5617,6 +5696,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager R.styleable.ViewGroup_MarginLayout_layout_marginEnd, DEFAULT_RELATIVE); } + initialLeftMargin = leftMargin; + initialRightMargin = rightMargin; + a.recycle(); } @@ -5642,6 +5724,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager this.bottomMargin = source.bottomMargin; this.startMargin = source.startMargin; this.endMargin = source.endMargin; + + this.initialLeftMargin = source.leftMargin; + this.initialRightMargin = source.rightMargin; + + setLayoutDirection(source.layoutDirection); } /** @@ -5671,6 +5758,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager topMargin = top; rightMargin = right; bottomMargin = bottom; + initialLeftMargin = left; + initialRightMargin = right; } /** @@ -5688,6 +5777,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom + * * @hide */ public void setMarginsRelative(int start, int top, int end, int bottom) { @@ -5695,6 +5785,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager topMargin = top; endMargin = end; bottomMargin = bottom; + initialLeftMargin = 0; + initialRightMargin = 0; + } + + /** + * Sets the relative start margin. + * + * @param start the start margin size + * + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart + */ + public void setMarginStart(int start) { + startMargin = start; } /** @@ -5703,10 +5806,27 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart * * @return the start margin in pixels. - * @hide */ public int getMarginStart() { - return startMargin; + if (startMargin != DEFAULT_RELATIVE) return startMargin; + switch(layoutDirection) { + case View.LAYOUT_DIRECTION_RTL: + return rightMargin; + case View.LAYOUT_DIRECTION_LTR: + default: + return leftMargin; + } + } + + /** + * Sets the relative end margin. + * + * @param end the end margin size + * + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd + */ + public void setMarginEnd(int end) { + endMargin = end; } /** @@ -5715,10 +5835,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd * * @return the end margin in pixels. - * @hide */ public int getMarginEnd() { - return endMargin; + if (endMargin != DEFAULT_RELATIVE) return endMargin; + switch(layoutDirection) { + case View.LAYOUT_DIRECTION_RTL: + return leftMargin; + case View.LAYOUT_DIRECTION_LTR: + default: + return rightMargin; + } } /** @@ -5727,29 +5853,53 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd * - * @return true if either marginStart or marginEnd has been set - * @hide + * @return true if either marginStart or marginEnd has been set. */ public boolean isMarginRelative() { return (startMargin != DEFAULT_RELATIVE) || (endMargin != DEFAULT_RELATIVE); } /** + * Set the layout direction + * @param layoutDirection the layout direction. + * Should be either {@link View#LAYOUT_DIRECTION_LTR} + * or {@link View#LAYOUT_DIRECTION_RTL}. + */ + public void setLayoutDirection(int layoutDirection) { + if (layoutDirection != View.LAYOUT_DIRECTION_LTR && + layoutDirection != View.LAYOUT_DIRECTION_RTL) return; + this.layoutDirection = layoutDirection; + } + + /** + * Retuns the layout direction. Can be either {@link View#LAYOUT_DIRECTION_LTR} or + * {@link View#LAYOUT_DIRECTION_RTL}. + * + * @return the layout direction. + */ + public int getLayoutDirection() { + return layoutDirection; + } + + /** * This will be called by {@link android.view.View#requestLayout()}. Left and Right margins * may be overridden depending on layout direction. - * @hide */ @Override public void onResolveLayoutDirection(int layoutDirection) { + setLayoutDirection(layoutDirection); + + if (!isMarginRelative()) return; + switch(layoutDirection) { case View.LAYOUT_DIRECTION_RTL: - leftMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : leftMargin; - rightMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : rightMargin; + leftMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : initialLeftMargin; + rightMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : initialRightMargin; break; case View.LAYOUT_DIRECTION_LTR: default: - leftMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : leftMargin; - rightMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : rightMargin; + leftMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : initialLeftMargin; + rightMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : initialRightMargin; break; } } @@ -5757,6 +5907,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * @hide */ + public boolean isLayoutRtl() { + return (layoutDirection == View.LAYOUT_DIRECTION_RTL); + } + + /** + * @hide + */ @Override public void onDebugDraw(View view, Canvas canvas) { drawRect(canvas, @@ -6080,7 +6237,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager view.getDrawingRect(viewLocation); root.offsetDescendantRectToMyCoords(view, viewLocation); mView = view; - mLayoutDirection = root.getResolvedLayoutDirection(); + mLayoutDirection = root.getLayoutDirection(); } private void clear() { diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index d93b996..ddff91d 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -295,16 +295,4 @@ public interface ViewParent { * @hide */ public void childAccessibilityStateChanged(View child); - - /** - * A descendant requests this view to find a candidate to take accessibility - * focus from hover. - * - * @param child The child making the call. - * @param descendant The descendant that made the initial request. - * @return A view to take accessibility focus. - * - * @hide - */ - public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant); } diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index ce6f4c5..d8db14c 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -1036,7 +1036,7 @@ public class ViewPropertyAnimator { if ((propertyMask & TRANSFORM_MASK) != 0) { mView.mTransformationInfo.mMatrixDirty = true; if (!useDisplayListProperties) { - mView.mPrivateFlags |= View.DRAWN; // force another invalidation + mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation } } // invalidate(false) in all cases except if alphaHandled gets set to true diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ad850da..438f792 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -18,7 +18,6 @@ package android.view; import android.Manifest; import android.animation.LayoutTransition; -import android.animation.ValueAnimator; import android.app.ActivityManagerNative; import android.content.ClipDescription; import android.content.ComponentCallbacks; @@ -53,6 +52,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; +import android.os.UserHandle; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.Log; @@ -74,10 +74,9 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import com.android.internal.R; +import com.android.internal.os.SomeArgs; import com.android.internal.policy.PolicyManager; import com.android.internal.view.BaseSurfaceHolder; -import com.android.internal.view.IInputMethodCallback; -import com.android.internal.view.IInputMethodSession; import com.android.internal.view.RootViewSurfaceTaker; import java.io.IOException; @@ -89,7 +88,7 @@ import java.util.HashSet; /** * The top of a view hierarchy, implementing the needed protocol between View * and the WindowManager. This is for the most part an internal implementation - * detail of {@link WindowManagerImpl}. + * detail of {@link WindowManagerGlobal}. * * {@hide} */ @@ -111,7 +110,7 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_FPS = false; private static final boolean USE_RENDER_THREAD = false; - + /** * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. @@ -127,11 +126,6 @@ public final class ViewRootImpl implements ViewParent, */ static final int MAX_TRACKBALL_DELAY = 250; - static IWindowSession sWindowSession; - - static final Object mStaticInit = new Object(); - static boolean mInitialized = false; - static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>(); @@ -144,6 +138,9 @@ public final class ViewRootImpl implements ViewParent, private static boolean sRenderThreadQueried = false; private static final Object[] sRenderThreadQueryLock = new Object[0]; + final IWindowSession mWindowSession; + final Display mDisplay; + long mLastTrackballTime = 0; final TrackballAxis mTrackballAxisX = new TrackballAxis(); final TrackballAxis mTrackballAxisY = new TrackballAxis(); @@ -218,6 +215,9 @@ public final class ViewRootImpl implements ViewParent, boolean mTraversalScheduled; int mTraversalBarrier; boolean mWillDrawSoon; + /** Set to true while in performTraversals for detecting when die(true) is called from internal + * callbacks such as onMeasure, onPreDraw, onDraw and deferring doDie() until later. */ + boolean mIsInTraversal; boolean mFitSystemWindowsRequested; boolean mLayoutRequested; boolean mFirst; @@ -251,9 +251,7 @@ public final class ViewRootImpl implements ViewParent, boolean mAdded; boolean mAddedTouchMode; - CompatibilityInfoHolder mCompatibilityInfo; - - /*package*/ int mAddNesting; + final CompatibilityInfoHolder mCompatibilityInfo; // These are accessed by multiple threads. final Rect mWinFrame; // frame given by window manager. @@ -268,12 +266,6 @@ public final class ViewRootImpl implements ViewParent, final Configuration mLastConfiguration = new Configuration(); final Configuration mPendingConfiguration = new Configuration(); - class ResizedInfo { - Rect contentInsets; - Rect visibleInsets; - Configuration newConfig; - } - boolean mScrollMayChange; int mSoftInputMode; View mLastScrolledFocus; @@ -322,6 +314,7 @@ public final class ViewRootImpl implements ViewParent, HashSet<View> mTempHashSet; private final int mDensity; + private final int mNoncompatDensity; /** * Consistency verifier for debugging purposes. @@ -330,24 +323,6 @@ public final class ViewRootImpl implements ViewParent, InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this, 0) : null; - public static IWindowSession getWindowSession(Looper mainLooper) { - synchronized (mStaticInit) { - if (!mInitialized) { - try { - InputMethodManager imm = InputMethodManager.getInstance(mainLooper); - IWindowManager windowManager = Display.getWindowManager(); - sWindowSession = windowManager.openSession( - imm.getClient(), imm.getInputContext()); - float animatorScale = windowManager.getAnimationScale(2); - ValueAnimator.setDurationScale(animatorScale); - mInitialized = true; - } catch (RemoteException e) { - } - } - return sWindowSession; - } - } - static final class SystemUiVisibilityInfo { int seq; int globalVisibility; @@ -355,7 +330,7 @@ public final class ViewRootImpl implements ViewParent, int localChanges; } - public ViewRootImpl(Context context) { + public ViewRootImpl(Context context, Display display) { super(); if (MEASURE_LATENCY) { @@ -367,7 +342,11 @@ public final class ViewRootImpl implements ViewParent, // Initialize the statics when this class is first instantiated. This is // done here instead of in the static block because Zygote does not // allow the spawning of threads. - getWindowSession(context.getMainLooper()); + mWindowSession = WindowManagerGlobal.getWindowSession(context.getMainLooper()); + mDisplay = display; + + CompatibilityInfoHolder cih = display.getCompatibilityInfo(); + mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder(); mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); @@ -391,9 +370,10 @@ public final class ViewRootImpl implements ViewParent, new AccessibilityInteractionConnectionManager(); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityInteractionConnectionManager); - mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, mHandler, this); + mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); mViewConfiguration = ViewConfiguration.get(context); mDensity = context.getResources().getDisplayMetrics().densityDpi; + mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context); mProfileRendering = Boolean.parseBoolean( SystemProperties.get(PROPERTY_PROFILE_RENDERING, "false")); @@ -467,9 +447,10 @@ public final class ViewRootImpl implements ViewParent, * @hide */ static boolean isInTouchMode() { - if (mInitialized) { + IWindowSession windowSession = WindowManagerGlobal.peekWindowSession(); + if (windowSession != null) { try { - return sWindowSession.getInTouchMode(); + return windowSession.getInTouchMode(); } catch (RemoteException e) { } } @@ -548,9 +529,9 @@ public final class ViewRootImpl implements ViewParent, mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); - res = sWindowSession.add(mWindow, mSeq, mWindowAttributes, - getHostVisibility(), mAttachInfo.mContentInsets, - mInputChannel); + res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, + getHostVisibility(), mDisplay.getDisplayId(), + mAttachInfo.mContentInsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; @@ -572,7 +553,7 @@ public final class ViewRootImpl implements ViewParent, mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingVisibleInsets.set(0, 0, 0, 0); if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow); - if (res < WindowManagerImpl.ADD_OKAY) { + if (res < WindowManagerGlobal.ADD_OKAY) { mView = null; mAttachInfo.mRootView = null; mAdded = false; @@ -580,33 +561,33 @@ public final class ViewRootImpl implements ViewParent, unscheduleTraversals(); setAccessibilityFocus(null, null); switch (res) { - case WindowManagerImpl.ADD_BAD_APP_TOKEN: - case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN: - throw new WindowManagerImpl.BadTokenException( + case WindowManagerGlobal.ADD_BAD_APP_TOKEN: + case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: + throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); - case WindowManagerImpl.ADD_NOT_APP_TOKEN: - throw new WindowManagerImpl.BadTokenException( + case WindowManagerGlobal.ADD_NOT_APP_TOKEN: + throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); - case WindowManagerImpl.ADD_APP_EXITING: - throw new WindowManagerImpl.BadTokenException( + case WindowManagerGlobal.ADD_APP_EXITING: + throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); - case WindowManagerImpl.ADD_DUPLICATE_ADD: - throw new WindowManagerImpl.BadTokenException( + case WindowManagerGlobal.ADD_DUPLICATE_ADD: + throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); - case WindowManagerImpl.ADD_STARTING_NOT_NEEDED: + case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; - case WindowManagerImpl.ADD_MULTIPLE_SINGLETON: - throw new WindowManagerImpl.BadTokenException( + case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: + throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- another window of this type already exists"); - case WindowManagerImpl.ADD_PERMISSION_DENIED: - throw new WindowManagerImpl.BadTokenException( + case WindowManagerGlobal.ADD_PERMISSION_DENIED: + throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- permission denied for this window type"); } @@ -629,8 +610,8 @@ public final class ViewRootImpl implements ViewParent, } view.assignParent(this); - mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0; - mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0; + mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0; + mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0; if (mAccessibilityManager.isEnabled()) { mAccessibilityInteractionConnectionManager.ensureConnection(); @@ -673,6 +654,12 @@ public final class ViewRootImpl implements ViewParent, } } + void pushHardwareLayerUpdate(HardwareLayer layer) { + if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { + mAttachInfo.mHardwareRenderer.pushLayerUpdate(layer); + } + } + public boolean attachFunctor(int functor) { //noinspection SimplifiableIfStatement if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { @@ -852,8 +839,8 @@ public final class ViewRootImpl implements ViewParent, void invalidateWorld(View view) { view.invalidate(); if (view instanceof ViewGroup) { - ViewGroup parent = (ViewGroup)view; - for (int i=0; i<parent.getChildCount(); i++) { + ViewGroup parent = (ViewGroup) view; + for (int i = 0; i < parent.getChildCount(); i++) { invalidateWorld(parent.getChildAt(i)); } } @@ -1027,17 +1014,15 @@ public final class ViewRootImpl implements ViewParent, //Log.i(TAG, "Computing view hierarchy attributes!"); attachInfo.mRecomputeGlobalAttributes = false; boolean oldScreenOn = attachInfo.mKeepScreenOn; - int oldVis = attachInfo.mSystemUiVisibility; - boolean oldHasSystemUiListeners = attachInfo.mHasSystemUiListeners; attachInfo.mKeepScreenOn = false; attachInfo.mSystemUiVisibility = 0; attachInfo.mHasSystemUiListeners = false; mView.dispatchCollectViewAttributes(attachInfo, 0); attachInfo.mSystemUiVisibility &= ~attachInfo.mDisabledSystemUiVisibility; + WindowManager.LayoutParams params = mWindowAttributes; if (attachInfo.mKeepScreenOn != oldScreenOn - || attachInfo.mSystemUiVisibility != oldVis - || attachInfo.mHasSystemUiListeners != oldHasSystemUiListeners) { - WindowManager.LayoutParams params = mWindowAttributes; + || attachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility + || attachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) { applyKeepScreenOnFlag(params); params.subtreeSystemUiVisibility = attachInfo.mSystemUiVisibility; params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners; @@ -1127,6 +1112,7 @@ public final class ViewRootImpl implements ViewParent, if (host == null || !mAdded) return; + mIsInTraversal = true; mWillDrawSoon = true; boolean windowSizeMayChange = false; boolean newSurface = false; @@ -1171,9 +1157,8 @@ public final class ViewRootImpl implements ViewParent, if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) { // NOTE -- system code, won't try to do compat mode. - Display disp = WindowManagerImpl.getDefault().getDefaultDisplay(); Point size = new Point(); - disp.getRealSize(size); + mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { @@ -1258,9 +1243,8 @@ public final class ViewRootImpl implements ViewParent, if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) { // NOTE -- system code, won't try to do compat mode. - Display disp = WindowManagerImpl.getDefault().getDefaultDisplay(); Point size = new Point(); - disp.getRealSize(size); + mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { @@ -1310,7 +1294,7 @@ public final class ViewRootImpl implements ViewParent, } } - if (params != null && (host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) { + if (params != null && (host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) { if (!PixelFormat.formatHasAlpha(params.format)) { params.format = PixelFormat.TRANSLUCENT; } @@ -1425,6 +1409,7 @@ public final class ViewRootImpl implements ViewParent, mResizeBuffer.getHeight() != mHeight) { mResizeBuffer.resize(mWidth, mHeight); } + // TODO: should handle create/resize failure layerCanvas = mResizeBuffer.start(hwRendererCanvas); layerCanvas.setViewport(mWidth, mHeight); layerCanvas.onPreDraw(null); @@ -1504,11 +1489,13 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mHardwareRenderer != null) { try { - hwInitialized = mAttachInfo.mHardwareRenderer.initialize(mHolder); + hwInitialized = mAttachInfo.mHardwareRenderer.initialize( + mHolder.getSurface()); } catch (Surface.OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException initializing HW surface", e); try { - if (!sWindowSession.outOfMemory(mWindow)) { + if (!mWindowSession.outOfMemory(mWindow) && + Process.myUid() != Process.SYSTEM_UID) { Slog.w(TAG, "No processes killed for memory; killing self"); Process.killProcess(Process.myPid()); } @@ -1537,11 +1524,11 @@ public final class ViewRootImpl implements ViewParent, mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) { mFullRedrawNeeded = true; try { - mAttachInfo.mHardwareRenderer.updateSurface(mHolder); + mAttachInfo.mHardwareRenderer.updateSurface(mHolder.getSurface()); } catch (Surface.OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException updating HW surface", e); try { - if (!sWindowSession.outOfMemory(mWindow)) { + if (!mWindowSession.outOfMemory(mWindow)) { Slog.w(TAG, "No processes killed for memory; killing self"); Process.killProcess(Process.myPid()); } @@ -1628,14 +1615,15 @@ public final class ViewRootImpl implements ViewParent, mHeight != mAttachInfo.mHardwareRenderer.getHeight()) { mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight); if (!hwInitialized) { - mAttachInfo.mHardwareRenderer.invalidate(mHolder); + mAttachInfo.mHardwareRenderer.invalidate(mHolder.getSurface()); + mFullRedrawNeeded = true; } } } if (!mStopped) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( - (relayoutResult&WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE) != 0); + (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); @@ -1680,6 +1668,30 @@ public final class ViewRootImpl implements ViewParent, layoutRequested = true; } } + } else { + // Not the first pass and no window/insets/visibility change but the window + // may have moved and we need check that and if so to update the left and right + // in the attach info. We translate only the window frame since on window move + // the window manager tells us only for the new frame but the insets are the + // same and we do not want to translate them more than once. + + // TODO: Well, we are checking whether the frame has changed similarly + // to how this is done for the insets. This is however incorrect since + // the insets and the frame are translated. For example, the old frame + // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new + // reported frame is (2, 2 - 2, 2) which implies no change but this is not + // true since we are comparing a not translated value to a translated one. + // This scenario is rare but we may want to fix that. + + final boolean windowMoved = (attachInfo.mWindowLeft != frame.left + || attachInfo.mWindowTop != frame.top); + if (windowMoved) { + if (mTranslator != null) { + mTranslator.translateRectInScreenToAppWinFrame(frame); + } + attachInfo.mWindowLeft = frame.left; + attachInfo.mWindowTop = frame.top; + } } final boolean didLayout = layoutRequested && !mStopped; @@ -1691,7 +1703,7 @@ public final class ViewRootImpl implements ViewParent, // By this point all views have been sized and positionned // We can compute the transparent area - if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) { + if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) { // start out transparent // TODO: AVOID THAT CALL BY CACHING THE RESULT? host.getLocationInWindow(mTmpLocation); @@ -1708,7 +1720,7 @@ public final class ViewRootImpl implements ViewParent, mPreviousTransparentRegion.set(mTransparentRegion); // reconfigure window manager try { - sWindowSession.setTransparentRegion(mWindow, mTransparentRegion); + mWindowSession.setTransparentRegion(mWindow, mTransparentRegion); } catch (RemoteException e) { } } @@ -1757,7 +1769,7 @@ public final class ViewRootImpl implements ViewParent, } try { - sWindowSession.setInsets(mWindow, insets.mTouchableInsets, + mWindowSession.setInsets(mWindow, insets.mTouchableInsets, contentInsets, visibleInsets, touchableRegion); } catch (RemoteException e) { } @@ -1782,7 +1794,7 @@ public final class ViewRootImpl implements ViewParent, + mRealFocusedView); } } - if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_ANIMATING) != 0) { + if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) { // The first time we relayout the window, if the system is // doing window animations, we want to hold of on any future // draws until the animation is done. @@ -1813,7 +1825,7 @@ public final class ViewRootImpl implements ViewParent, } // Remember if we must report the next draw. - if ((relayoutResult & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { + if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { mReportNextDraw = true; } @@ -1842,6 +1854,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } } + + mIsInTraversal = false; } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { @@ -1875,7 +1889,7 @@ public final class ViewRootImpl implements ViewParent, // the test below should not fail unless someone is messing with us checkThread(); if (mView == child) { - mView.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS; + mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS; // Need to make sure we re-evaluate the window attributes next // time around, to ensure the window has the correct format. mWindowAttributesChanged = true; @@ -2043,7 +2057,7 @@ public final class ViewRootImpl implements ViewParent, } } try { - sWindowSession.finishDrawing(mWindow); + mWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { } } @@ -2199,7 +2213,7 @@ public final class ViewRootImpl implements ViewParent, } catch (Surface.OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException locking surface", e); try { - if (!sWindowSession.outOfMemory(mWindow)) { + if (!mWindowSession.outOfMemory(mWindow)) { Slog.w(TAG, "No processes killed for memory; killing self"); Process.killProcess(Process.myPid()); } @@ -2238,7 +2252,7 @@ public final class ViewRootImpl implements ViewParent, dirty.setEmpty(); mIsAnimating = false; attachInfo.mDrawingTime = SystemClock.uptimeMillis(); - mView.mPrivateFlags |= View.DRAWN; + mView.mPrivateFlags |= View.PFLAG_DRAWN; if (DEBUG_DRAW) { Context cxt = mView.getContext(); @@ -2251,8 +2265,7 @@ public final class ViewRootImpl implements ViewParent, if (mTranslator != null) { mTranslator.translateCanvas(canvas); } - canvas.setScreenDensity(scalingRequired - ? DisplayMetrics.DENSITY_DEVICE : 0); + canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; mView.draw(canvas); @@ -2281,14 +2294,6 @@ public final class ViewRootImpl implements ViewParent, return true; } - @Override - public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) { - if (descendant.includeForAccessibility()) { - return descendant; - } - return null; - } - /** * We want to draw a highlight around the current accessibility focused. * Since adding a style for all possible view is not a viable option we @@ -2637,7 +2642,7 @@ public final class ViewRootImpl implements ViewParent, mInputEventReceiver = null; } try { - sWindowSession.remove(mWindow); + mWindowSession.remove(mWindow); } catch (RemoteException e) { } @@ -2660,7 +2665,7 @@ public final class ViewRootImpl implements ViewParent, CompatibilityInfo ci = mCompatibilityInfo.getIfNeeded(); if (ci != null) { config = new Configuration(config); - ci.applyToConfiguration(config); + ci.applyToConfiguration(mNoncompatDensity, config); } synchronized (sConfigCallbacks) { @@ -2671,7 +2676,7 @@ public final class ViewRootImpl implements ViewParent, if (mView != null) { // At this point the resources have been updated to // have the most recent config, whatever that is. Use - // the on in them which may be newer. + // the one in them which may be newer. config = mView.getResources().getConfiguration(); if (force || mLastConfiguration.diff(config) != 0) { mLastConfiguration.setTo(config); @@ -2727,6 +2732,7 @@ public final class ViewRootImpl implements ViewParent, private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 22; private final static int MSG_DISPATCH_DONE_ANIMATING = 23; private final static int MSG_INVALIDATE_WORLD = 24; + private final static int MSG_WINDOW_MOVED = 25; final class ViewRootHandler extends Handler { @Override @@ -2778,6 +2784,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST"; case MSG_DISPATCH_DONE_ANIMATING: return "MSG_DISPATCH_DONE_ANIMATING"; + case MSG_WINDOW_MOVED: + return "MSG_WINDOW_MOVED"; } return super.getMessageName(message); } @@ -2806,28 +2814,31 @@ public final class ViewRootImpl implements ViewParent, case MSG_DISPATCH_GET_NEW_SURFACE: handleGetNewSurface(); break; - case MSG_RESIZED: - ResizedInfo ri = (ResizedInfo)msg.obj; - - if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2 - && mPendingContentInsets.equals(ri.contentInsets) - && mPendingVisibleInsets.equals(ri.visibleInsets) - && ((ResizedInfo)msg.obj).newConfig == null) { + case MSG_RESIZED: { + // Recycled in the fall through... + SomeArgs args = (SomeArgs) msg.obj; + if (mWinFrame.equals((Rect) args.arg1) + && mPendingContentInsets.equals((Rect) args.arg2) + && mPendingVisibleInsets.equals((Rect) args.arg3) + && ((Configuration) args.arg4 == null)) { break; } - // fall through... + } // fall through... case MSG_RESIZED_REPORT: if (mAdded) { - Configuration config = ((ResizedInfo)msg.obj).newConfig; + SomeArgs args = (SomeArgs) msg.obj; + + Configuration config = (Configuration) args.arg4; if (config != null) { updateConfiguration(config, false); } - mWinFrame.left = 0; - mWinFrame.right = msg.arg1; - mWinFrame.top = 0; - mWinFrame.bottom = msg.arg2; - mPendingContentInsets.set(((ResizedInfo)msg.obj).contentInsets); - mPendingVisibleInsets.set(((ResizedInfo)msg.obj).visibleInsets); + + mWinFrame.set((Rect) args.arg1); + mPendingContentInsets.set((Rect) args.arg2); + mPendingVisibleInsets.set((Rect) args.arg3); + + args.recycle(); + if (msg.what == MSG_RESIZED_REPORT) { mReportNextDraw = true; } @@ -2835,6 +2846,24 @@ public final class ViewRootImpl implements ViewParent, if (mView != null) { forceLayout(mView); } + + requestLayout(); + } + break; + case MSG_WINDOW_MOVED: + if (mAdded) { + final int w = mWinFrame.width(); + final int h = mWinFrame.height(); + final int l = msg.arg1; + final int t = msg.arg2; + mWinFrame.left = l; + mWinFrame.right = l + w; + mWinFrame.top = t; + mWinFrame.bottom = t + h; + + if (mView != null) { + forceLayout(mView); + } requestLayout(); } break; @@ -2854,11 +2883,11 @@ public final class ViewRootImpl implements ViewParent, mFullRedrawNeeded = true; try { mAttachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight, - mHolder); + mHolder.getSurface()); } catch (Surface.OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException locking surface", e); try { - if (!sWindowSession.outOfMemory(mWindow)) { + if (!mWindowSession.outOfMemory(mWindow)) { Slog.w(TAG, "No processes killed for memory; killing self"); Process.killProcess(Process.myPid()); } @@ -2979,7 +3008,9 @@ public final class ViewRootImpl implements ViewParent, handleDispatchDoneAnimating(); } break; case MSG_INVALIDATE_WORLD: { - invalidateWorld(mView); + if (mView != null) { + invalidateWorld(mView); + } } break; } } @@ -3003,7 +3034,7 @@ public final class ViewRootImpl implements ViewParent, // tell the window manager try { - sWindowSession.setInTouchMode(inTouchMode); + mWindowSession.setInTouchMode(inTouchMode); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -3190,6 +3221,33 @@ public final class ViewRootImpl implements ViewParent, mInputEventConsistencyVerifier.onTrackballEvent(event, 0); } + if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) { + if (LOCAL_LOGV) + Log.v(TAG, "Dispatching trackball " + event + " to " + mView); + + // Dispatch to the IME before propagating down the view hierarchy. + // The IME will eventually call back into handleImeFinishedEvent. + if (mLastWasImTarget) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + final int seq = event.getSequenceNumber(); + if (DEBUG_IMF) + Log.v(TAG, "Sending trackball event to IME: seq=" + + seq + " event=" + event); + imm.dispatchTrackballEvent(mView.getContext(), seq, event, + mInputMethodCallback); + return; + } + } + } + + // Not dispatching to IME, continue with post IME actions. + deliverTrackballEventPostIme(q); + } + + private void deliverTrackballEventPostIme(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent) q.mEvent; + // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { finishInputEvent(q, false); @@ -3323,8 +3381,33 @@ public final class ViewRootImpl implements ViewParent, mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0); } - final int source = event.getSource(); - final boolean isJoystick = (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0; + if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) { + if (LOCAL_LOGV) + Log.v(TAG, "Dispatching generic motion " + event + " to " + mView); + + // Dispatch to the IME before propagating down the view hierarchy. + // The IME will eventually call back into handleImeFinishedEvent. + if (mLastWasImTarget) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + final int seq = event.getSequenceNumber(); + if (DEBUG_IMF) + Log.v(TAG, "Sending generic motion event to IME: seq=" + + seq + " event=" + event); + imm.dispatchGenericMotionEvent(mView.getContext(), seq, event, + mInputMethodCallback); + return; + } + } + } + + // Not dispatching to IME, continue with post IME actions. + deliverGenericMotionEventPostIme(q); + } + + private void deliverGenericMotionEventPostIme(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent) q.mEvent; + final boolean isJoystick = (event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0; // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { @@ -3345,7 +3428,8 @@ public final class ViewRootImpl implements ViewParent, } if (isJoystick) { - // Translate the joystick event into DPAD keys and try to deliver those. + // Translate the joystick event into DPAD keys and try to deliver + // those. updateJoystickDirection(event, true); finishInputEvent(q, true); } else { @@ -3500,13 +3584,7 @@ public final class ViewRootImpl implements ViewParent, mInputEventConsistencyVerifier.onKeyEvent(event, 0); } - if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) { - // If there is no view, then the event will not be handled. - if (mView == null || !mAdded) { - finishInputEvent(q, false); - return; - } - + if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) { if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView); // Perform predispatching before the IME. @@ -3536,15 +3614,46 @@ public final class ViewRootImpl implements ViewParent, void handleImeFinishedEvent(int seq, boolean handled) { final QueuedInputEvent q = mCurrentInputEvent; if (q != null && q.mEvent.getSequenceNumber() == seq) { - final KeyEvent event = (KeyEvent)q.mEvent; if (DEBUG_IMF) { Log.v(TAG, "IME finished event: seq=" + seq - + " handled=" + handled + " event=" + event); + + " handled=" + handled + " event=" + q); } if (handled) { finishInputEvent(q, true); } else { - deliverKeyEventPostIme(q); + if (q.mEvent instanceof KeyEvent) { + KeyEvent event = (KeyEvent)q.mEvent; + if (event.getAction() != KeyEvent.ACTION_UP) { + // If the window doesn't currently have input focus, then drop + // this event. This could be an event that came back from the + // IME dispatch but the window has lost focus in the meantime. + if (!mAttachInfo.mHasWindowFocus) { + Slog.w(TAG, "Dropping event due to no window focus: " + event); + finishInputEvent(q, true); + return; + } + } + deliverKeyEventPostIme(q); + } else { + MotionEvent event = (MotionEvent)q.mEvent; + if (event.getAction() != MotionEvent.ACTION_CANCEL + && event.getAction() != MotionEvent.ACTION_UP) { + // If the window doesn't currently have input focus, then drop + // this event. This could be an event that came back from the + // IME dispatch but the window has lost focus in the meantime. + if (!mAttachInfo.mHasWindowFocus) { + Slog.w(TAG, "Dropping event due to no window focus: " + event); + finishInputEvent(q, true); + return; + } + } + final int source = q.mEvent.getSource(); + if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { + deliverTrackballEventPostIme(q); + } else { + deliverGenericMotionEventPostIme(q); + } + } } } else { if (DEBUG_IMF) { @@ -3717,10 +3826,10 @@ public final class ViewRootImpl implements ViewParent, if (prevDragView != mCurrentDragView) { try { if (prevDragView != null) { - sWindowSession.dragRecipientExited(mWindow); + mWindowSession.dragRecipientExited(mWindow); } if (mCurrentDragView != null) { - sWindowSession.dragRecipientEntered(mWindow); + mWindowSession.dragRecipientEntered(mWindow); } } catch (RemoteException e) { Slog.e(TAG, "Unable to note drag target change"); @@ -3732,7 +3841,7 @@ public final class ViewRootImpl implements ViewParent, mDragDescription = null; try { Log.i(TAG, "Reporting drop result: " + result); - sWindowSession.reportDropResult(mWindow, result); + mWindowSession.reportDropResult(mWindow, result); } catch (RemoteException e) { Log.e(TAG, "Unable to report drop result"); } @@ -3834,11 +3943,11 @@ public final class ViewRootImpl implements ViewParent, params.type = mOrigWindowType; } } - int relayoutResult = sWindowSession.relayout( + int relayoutResult = mWindowSession.relayout( mWindow, mSeq, params, (int) (mView.getMeasuredWidth() * appScale + 0.5f), (int) (mView.getMeasuredHeight() * appScale + 0.5f), - viewVisibility, insetsPending ? WindowManagerImpl.RELAYOUT_INSETS_PENDING : 0, + viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mWinFrame, mPendingContentInsets, mPendingVisibleInsets, mPendingConfiguration, mSurface); //Log.d(TAG, "<<<<<< BACK FROM relayout"); @@ -3895,7 +4004,7 @@ public final class ViewRootImpl implements ViewParent, */ public boolean performHapticFeedback(int effectId, boolean always) { try { - return sWindowSession.performHapticFeedback(mWindow, effectId, always); + return mWindowSession.performHapticFeedback(mWindow, effectId, always); } catch (RemoteException e) { return false; } @@ -3941,7 +4050,9 @@ public final class ViewRootImpl implements ViewParent, } public void die(boolean immediate) { - if (immediate) { + // Make sure we do execute immediately if we are in the middle of a traversal or the damage + // done by dispatchDetachedFromWindow will cause havoc on return. + if (immediate && !mIsInTraversal) { doDie(); } else { if (!mIsDrawing) { @@ -3974,8 +4085,8 @@ public final class ViewRootImpl implements ViewParent, // animation info. try { if ((relayoutWindow(mWindowAttributes, viewVisibility, false) - & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { - sWindowSession.finishDrawing(mWindow); + & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { + mWindowSession.finishDrawing(mWindow); } } catch (RemoteException e) { } @@ -4034,26 +4145,37 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } - public void dispatchResized(int w, int h, Rect contentInsets, + public void dispatchResized(Rect frame, Rect contentInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig) { - if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w - + " h=" + h + " contentInsets=" + contentInsets.toShortString() + if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": frame=" + frame.toShortString() + + " contentInsets=" + contentInsets.toShortString() + " visibleInsets=" + visibleInsets.toShortString() + " reportDraw=" + reportDraw); - Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT :MSG_RESIZED); + Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED); if (mTranslator != null) { + mTranslator.translateRectInScreenToAppWindow(frame); mTranslator.translateRectInScreenToAppWindow(contentInsets); mTranslator.translateRectInScreenToAppWindow(visibleInsets); - w *= mTranslator.applicationInvertedScale; - h *= mTranslator.applicationInvertedScale; - } - msg.arg1 = w; - msg.arg2 = h; - ResizedInfo ri = new ResizedInfo(); - ri.contentInsets = new Rect(contentInsets); - ri.visibleInsets = new Rect(visibleInsets); - ri.newConfig = newConfig; - msg.obj = ri; + } + SomeArgs args = SomeArgs.obtain(); + final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid()); + args.arg1 = sameProcessCall ? new Rect(frame) : frame; + args.arg2 = sameProcessCall ? new Rect(contentInsets) : contentInsets; + args.arg3 = sameProcessCall ? new Rect(visibleInsets) : visibleInsets; + args.arg4 = sameProcessCall && newConfig != null ? new Configuration(newConfig) : newConfig; + msg.obj = args; + mHandler.sendMessage(msg); + } + + public void dispatchMoved(int newX, int newY) { + if (DEBUG_LAYOUT) Log.v(TAG, "Window moved " + this + ": newX=" + newX + " newY=" + newY); + if (mTranslator != null) { + PointF point = new PointF(newX, newY); + mTranslator.translatePointInScreenToAppWindow(point); + newX = (int) (point.x + 0.5); + newY = (int) (point.y + 0.5); + } + Message msg = mHandler.obtainMessage(MSG_WINDOW_MOVED, newX, newY); mHandler.sendMessage(msg); } @@ -4615,9 +4737,19 @@ public final class ViewRootImpl implements ViewParent, // ViewAncestor never intercepts touch event, so this can be a no-op } - public boolean requestChildRectangleOnScreen(View child, Rect rectangle, - boolean immediate) { - return scrollToRectOrFocus(rectangle, immediate); + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + final boolean scrolled = scrollToRectOrFocus(rectangle, immediate); + if (rectangle != null) { + mTempRect.set(rectangle); + mTempRect.offset(0, -mCurScrollY); + mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop); + try { + mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect, immediate); + } catch (RemoteException re) { + /* ignore */ + } + } + return scrolled; } public void childHasTransientStateChanged(View child, boolean hasTransientState) { @@ -4664,41 +4796,48 @@ public final class ViewRootImpl implements ViewParent, } } - static class InputMethodCallback extends IInputMethodCallback.Stub { + static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback { private WeakReference<ViewRootImpl> mViewAncestor; public InputMethodCallback(ViewRootImpl viewAncestor) { mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor); } + @Override public void finishedEvent(int seq, boolean handled) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchImeFinishedEvent(seq, handled); } } - - public void sessionCreated(IInputMethodSession session) { - // Stub -- not for use in the client. - } } static class W extends IWindow.Stub { private final WeakReference<ViewRootImpl> mViewAncestor; + private final IWindowSession mWindowSession; W(ViewRootImpl viewAncestor) { mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor); + mWindowSession = viewAncestor.mWindowSession; } - public void resized(int w, int h, Rect contentInsets, + public void resized(Rect frame, Rect contentInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { - viewAncestor.dispatchResized(w, h, contentInsets, + viewAncestor.dispatchResized(frame, contentInsets, visibleInsets, reportDraw, newConfig); } } + @Override + public void moved(int newX, int newY) { + final ViewRootImpl viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchMoved(newX, newY); + } + } + public void dispatchAppVisibility(boolean visible) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -4778,7 +4917,7 @@ public final class ViewRootImpl implements ViewParent, boolean sync) { if (sync) { try { - sWindowSession.wallpaperOffsetsComplete(asBinder()); + mWindowSession.wallpaperOffsetsComplete(asBinder()); } catch (RemoteException e) { } } @@ -4788,7 +4927,7 @@ public final class ViewRootImpl implements ViewParent, int z, Bundle extras, boolean sync) { if (sync) { try { - sWindowSession.wallpaperCommandComplete(asBinder(), null); + mWindowSession.wallpaperCommandComplete(asBinder(), null); } catch (RemoteException e) { } } @@ -5186,15 +5325,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, - int windowLeft, int windowTop, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, + int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId, - windowLeft, windowTop, interactionId, callback, flags, interrogatingPid, - interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -5227,15 +5364,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, - int windowLeft, int windowTop, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, + int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId, - windowLeft, windowTop, interactionId, callback, flags, interrogatingPid, - interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -5248,15 +5383,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, - int windowLeft, int windowTop, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, + int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text, - windowLeft, windowTop, interactionId, callback, flags, interrogatingPid, - interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -5268,15 +5401,14 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void findFocus(long accessibilityNodeId, int focusType, int windowLeft, - int windowTop, int interactionId, + public void findFocus(long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() - .findFocusClientThread(accessibilityNodeId, focusType, windowLeft, windowTop, - interactionId, callback, flags, interrogatingPid, interrogatingTid); + .findFocusClientThread(accessibilityNodeId, focusType, interactionId, callback, + flags, interrogatingPid, interrogatingTid); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -5288,15 +5420,14 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void focusSearch(long accessibilityNodeId, int direction, int windowLeft, - int windowTop, int interactionId, + public void focusSearch(long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() - .focusSearchClientThread(accessibilityNodeId, direction, windowLeft, windowTop, - interactionId, callback, flags, interrogatingPid, interrogatingTid); + .focusSearchClientThread(accessibilityNodeId, direction, interactionId, + callback, flags, interrogatingPid, interrogatingTid); } else { // We cannot make the call and notify the caller so it does not wait. try { diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 1c5d436..cfcf3c0 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -32,12 +32,18 @@ import java.util.concurrent.CopyOnWriteArrayList; * for more information. */ public final class ViewTreeObserver { + // Recursive listeners use CopyOnWriteArrayList private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; - private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners; private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; - private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; - private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners; - private ArrayList<OnPreDrawListener> mOnPreDrawListeners; + + // Non-recursive listeners use CopyOnWriteArray + // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive + private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners; + private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; + private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners; + private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners; + + // These listeners cannot be mutated during dispatch private ArrayList<OnDrawListener> mOnDrawListeners; private boolean mAlive = true; @@ -147,7 +153,7 @@ public final class ViewTreeObserver { * windows behind it should be placed. */ public final Rect contentInsets = new Rect(); - + /** * Offsets from the frame of the window at which windows behind it * are visible. @@ -166,13 +172,13 @@ public final class ViewTreeObserver { * can be touched. */ public static final int TOUCHABLE_INSETS_FRAME = 0; - + /** * Option for {@link #setTouchableInsets(int)}: the area inside of * the content insets can be touched. */ public static final int TOUCHABLE_INSETS_CONTENT = 1; - + /** * Option for {@link #setTouchableInsets(int)}: the area inside of * the visible insets can be touched. @@ -195,7 +201,7 @@ public final class ViewTreeObserver { } int mTouchableInsets; - + void reset() { contentInsets.setEmpty(); visibleInsets.setEmpty(); @@ -231,7 +237,7 @@ public final class ViewTreeObserver { mTouchableInsets = other.mTouchableInsets; } } - + /** * Interface definition for a callback to be invoked when layout has * completed and the client can compute its interior insets. @@ -363,7 +369,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnGlobalLayoutListeners == null) { - mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>(); + mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>(); } mOnGlobalLayoutListeners.add(listener); @@ -413,7 +419,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnPreDrawListeners == null) { - mOnPreDrawListeners = new ArrayList<OnPreDrawListener>(); + mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>(); } mOnPreDrawListeners.add(listener); @@ -485,7 +491,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnScrollChangedListeners == null) { - mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>(); + mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>(); } mOnScrollChangedListeners.add(listener); @@ -558,7 +564,7 @@ public final class ViewTreeObserver { if (mOnComputeInternalInsetsListeners == null) { mOnComputeInternalInsetsListeners = - new CopyOnWriteArrayList<OnComputeInternalInsetsListener>(); + new CopyOnWriteArray<OnComputeInternalInsetsListener>(); } mOnComputeInternalInsetsListeners.add(listener); @@ -640,10 +646,16 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; + final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; if (listeners != null && listeners.size() > 0) { - for (OnGlobalLayoutListener listener : listeners) { - listener.onGlobalLayout(); + CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onGlobalLayout(); + } + } finally { + listeners.end(); } } } @@ -658,17 +670,17 @@ public final class ViewTreeObserver { */ @SuppressWarnings("unchecked") public final boolean dispatchOnPreDraw() { - // NOTE: we *must* clone the listener list to perform the dispatching. - // The clone is a safe guard against listeners that - // could mutate the list by calling the various add/remove methods. This prevents - // the array from being modified while we process it. boolean cancelDraw = false; - if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) { - final ArrayList<OnPreDrawListener> listeners = - (ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone(); - int numListeners = listeners.size(); - for (int i = 0; i < numListeners; ++i) { - cancelDraw |= !(listeners.get(i).onPreDraw()); + final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners; + if (listeners != null && listeners.size() > 0) { + CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + cancelDraw |= !(access.get(i).onPreDraw()); + } + } finally { + listeners.end(); } } return cancelDraw; @@ -710,10 +722,16 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners; + final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners; if (listeners != null && listeners.size() > 0) { - for (OnScrollChangedListener listener : listeners) { - listener.onScrollChanged(); + CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onScrollChanged(); + } + } finally { + listeners.end(); } } } @@ -722,11 +740,11 @@ public final class ViewTreeObserver { * Returns whether there are listeners for computing internal insets. */ final boolean hasComputeInternalInsetsListeners() { - final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = + final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; return (listeners != null && listeners.size() > 0); } - + /** * Calls all listeners to compute the current insets. */ @@ -735,12 +753,105 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = + final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; if (listeners != null && listeners.size() > 0) { - for (OnComputeInternalInsetsListener listener : listeners) { - listener.onComputeInternalInsets(inoutInfo); + CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onComputeInternalInsets(inoutInfo); + } + } finally { + listeners.end(); + } + } + } + + /** + * Copy on write array. This array is not thread safe, and only one loop can + * iterate over this array at any given time. This class avoids allocations + * until a concurrent modification happens. + * + * Usage: + * + * CopyOnWriteArray.Access<MyData> access = array.start(); + * try { + * for (int i = 0; i < access.size(); i++) { + * MyData d = access.get(i); + * } + * } finally { + * access.end(); + * } + */ + static class CopyOnWriteArray<T> { + private ArrayList<T> mData = new ArrayList<T>(); + private ArrayList<T> mDataCopy; + + private final Access<T> mAccess = new Access<T>(); + + private boolean mStart; + + static class Access<T> { + private ArrayList<T> mData; + private int mSize; + + T get(int index) { + return mData.get(index); + } + + int size() { + return mSize; + } + } + + CopyOnWriteArray() { + } + + private ArrayList<T> getArray() { + if (mStart) { + if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData); + return mDataCopy; } + return mData; + } + + Access<T> start() { + if (mStart) throw new IllegalStateException("Iteration already started"); + mStart = true; + mDataCopy = null; + mAccess.mData = mData; + mAccess.mSize = mData.size(); + return mAccess; + } + + void end() { + if (!mStart) throw new IllegalStateException("Iteration not started"); + mStart = false; + if (mDataCopy != null) { + mData = mDataCopy; + } + mDataCopy = null; + } + + int size() { + return getArray().size(); + } + + void add(T item) { + getArray().add(item); + } + + void addAll(CopyOnWriteArray<T> array) { + getArray().addAll(array.mData); + } + + void remove(T item) { + getArray().remove(item); + } + + void clear() { + getArray().clear(); } } } diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java index cf9bcdd..d7c7f46 100644 --- a/core/java/android/view/VolumePanel.java +++ b/core/java/android/view/VolumePanel.java @@ -18,6 +18,7 @@ package android.view; import com.android.internal.R; +import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface.OnDismissListener; import android.content.BroadcastReceiver; @@ -92,6 +93,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private static final int MSG_REMOTE_VOLUME_CHANGED = 8; private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; + private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11; // Pseudo stream type for master volume private static final int STREAM_MASTER = -100; @@ -104,6 +106,9 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private boolean mShowCombinedVolumes; private boolean mVoiceCapable; + // True if we want to play tones on the system stream when the master stream is specified. + private final boolean mPlayMasterStreamTones; + /** Dialog containing all the sliders */ private final Dialog mDialog; /** Dialog's content view */ @@ -208,6 +213,38 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private ToneGenerator mToneGenerators[]; private Vibrator mVibrator; + private static AlertDialog sConfirmSafeVolumeDialog; + private static Object sConfirmSafeVolumeLock = new Object(); + + private static class WarningDialogReceiver extends BroadcastReceiver + implements DialogInterface.OnDismissListener { + private Context mContext; + private Dialog mDialog; + + WarningDialogReceiver(Context context, Dialog dialog) { + mContext = context; + mDialog = dialog; + IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + mDialog.cancel(); + synchronized (sConfirmSafeVolumeLock) { + sConfirmSafeVolumeDialog = null; + } + } + + public void onDismiss(DialogInterface unused) { + mContext.unregisterReceiver(this); + synchronized (sConfirmSafeVolumeLock) { + sConfirmSafeVolumeDialog = null; + } + } + } + + public VolumePanel(final Context context, AudioService volumeService) { mContext = context; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); @@ -282,6 +319,13 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie mMoreButton.setOnClickListener(this); } + boolean masterVolumeOnly = context.getResources().getBoolean( + com.android.internal.R.bool.config_useMasterVolume); + boolean masterVolumeKeySounds = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_useVolumeKeySounds); + + mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds; + listenToRingerMode(); } @@ -518,6 +562,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie postMuteChanged(STREAM_MASTER, flags); } + public void postDisplaySafeVolumeWarning() { + if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return; + obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget(); + } + /** * Override this if you have other work to do when the volume changes (for * example, vibrating, playing a sound, etc.). Make sure to call through to @@ -650,9 +699,12 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie if (sc.seekbarView.getMax() != max) { sc.seekbarView.setMax(max); } + sc.seekbarView.setProgress(index); - if (streamType != mAudioManager.getMasterStreamType() - && streamType != AudioService.STREAM_REMOTE_MUSIC && isMuted(streamType)) { + if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) || + (streamType != mAudioManager.getMasterStreamType() && + streamType != AudioService.STREAM_REMOTE_MUSIC && + isMuted(streamType))) { sc.seekbarView.setEnabled(false); } else { sc.seekbarView.setEnabled(true); @@ -786,11 +838,46 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } } + protected void onDisplaySafeVolumeWarning() { + synchronized (sConfirmSafeVolumeLock) { + if (sConfirmSafeVolumeDialog != null) { + return; + } + sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext) + .setMessage(com.android.internal.R.string.safe_media_volume_warning) + .setPositiveButton(com.android.internal.R.string.yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mAudioService.disableSafeMediaVolume(); + } + }) + .setNegativeButton(com.android.internal.R.string.no, null) + .setIconAttribute(android.R.attr.alertDialogIcon) + .create(); + final WarningDialogReceiver warning = new WarningDialogReceiver(mContext, + sConfirmSafeVolumeDialog); + + sConfirmSafeVolumeDialog.setOnDismissListener(warning); + sConfirmSafeVolumeDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + sConfirmSafeVolumeDialog.show(); + } + } + /** * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. */ private ToneGenerator getOrCreateToneGenerator(int streamType) { - if (streamType == STREAM_MASTER) return null; + if (streamType == STREAM_MASTER) { + // For devices that use the master volume setting only but still want to + // play a volume-changed tone, direct the master volume pseudostream to + // the system stream's tone generator. + if (mPlayMasterStreamTones) { + streamType = AudioManager.STREAM_SYSTEM; + } else { + return null; + } + } synchronized (this) { if (mToneGenerators[streamType] == null) { try { @@ -891,6 +978,10 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie case MSG_SLIDER_VISIBILITY_CHANGED: onSliderVisibilityChanged(msg.arg1, msg.arg2); break; + + case MSG_DISPLAY_SAFE_VOLUME_WARNING: + onDisplaySafeVolumeWarning(); + break; } } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index b0e90db..06974d3 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -16,9 +16,7 @@ package android.view; -import android.app.Application; import android.content.Context; -import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.PixelFormat; @@ -27,7 +25,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.SystemProperties; -import android.util.Slog; import android.view.accessibility.AccessibilityEvent; /** @@ -92,6 +89,13 @@ public abstract class Window { * If overlay is enabled, the action mode UI will be allowed to cover existing window content. */ public static final int FEATURE_ACTION_MODE_OVERLAY = 10; + + /** + * Max value used as a feature ID + * @hide + */ + public static final int FEATURE_MAX = FEATURE_ACTION_MODE_OVERLAY; + /** Flag for setting the progress bar's visibility to VISIBLE */ public static final int PROGRESS_VISIBILITY_ON = -1; /** Flag for setting the progress bar's visibility to GONE */ @@ -119,6 +123,8 @@ public abstract class Window { */ public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; + private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw"; + private final Context mContext; private TypedArray mWindowStyle; @@ -126,6 +132,7 @@ public abstract class Window { private WindowManager mWindowManager; private IBinder mAppToken; private String mAppName; + private boolean mHardwareAccelerated; private Window mContainer; private Window mActiveChild; private boolean mIsActive = false; @@ -454,7 +461,7 @@ public abstract class Window { * display panels. This is <em>not</em> used for displaying the * Window itself -- that must be done by the client. * - * @param wm The ViewManager for adding new windows. + * @param wm The window manager for adding new windows. */ public void setWindowManager(WindowManager wm, IBinder appToken, String appName) { setWindowManager(wm, appToken, appName, false); @@ -465,86 +472,64 @@ public abstract class Window { * display panels. This is <em>not</em> used for displaying the * Window itself -- that must be done by the client. * - * @param wm The ViewManager for adding new windows. + * @param wm The window manager for adding new windows. */ public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; + mHardwareAccelerated = hardwareAccelerated + || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { - wm = WindowManagerImpl.getDefault(); + wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } - mWindowManager = new LocalWindowManager(wm, hardwareAccelerated); + mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); } - static CompatibilityInfoHolder getCompatInfo(Context context) { - Application app = (Application)context.getApplicationContext(); - return app != null ? app.mLoadedApk.mCompatibilityInfo : new CompatibilityInfoHolder(); - } - - private class LocalWindowManager extends WindowManagerImpl.CompatModeWrapper { - private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw"; - - private final boolean mHardwareAccelerated; - - LocalWindowManager(WindowManager wm, boolean hardwareAccelerated) { - super(wm, getCompatInfo(mContext)); - mHardwareAccelerated = hardwareAccelerated || - SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); - } - - public boolean isHardwareAccelerated() { - return mHardwareAccelerated; - } - - public final void addView(View view, ViewGroup.LayoutParams params) { - // Let this throw an exception on a bad params. - WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params; - CharSequence curTitle = wp.getTitle(); - if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && - wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { - if (wp.token == null) { - View decor = peekDecorView(); - if (decor != null) { - wp.token = decor.getWindowToken(); - } + void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { + CharSequence curTitle = wp.getTitle(); + if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && + wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + if (wp.token == null) { + View decor = peekDecorView(); + if (decor != null) { + wp.token = decor.getWindowToken(); } - if (curTitle == null || curTitle.length() == 0) { - String title; - if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) { - title="Media"; - } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) { - title="MediaOvr"; - } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { - title="Panel"; - } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) { - title="SubPanel"; - } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) { - title="AtchDlg"; - } else { - title=Integer.toString(wp.type); - } - if (mAppName != null) { - title += ":" + mAppName; - } - wp.setTitle(title); - } - } else { - if (wp.token == null) { - wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; + } + if (curTitle == null || curTitle.length() == 0) { + String title; + if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) { + title="Media"; + } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) { + title="MediaOvr"; + } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { + title="Panel"; + } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) { + title="SubPanel"; + } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) { + title="AtchDlg"; + } else { + title=Integer.toString(wp.type); } - if ((curTitle == null || curTitle.length() == 0) - && mAppName != null) { - wp.setTitle(mAppName); + if (mAppName != null) { + title += ":" + mAppName; } - } - if (wp.packageName == null) { - wp.packageName = mContext.getPackageName(); + wp.setTitle(title); } - if (mHardwareAccelerated) { - wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + } else { + if (wp.token == null) { + wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; + } + if ((curTitle == null || curTitle.length() == 0) + && mAppName != null) { + wp.setTitle(mAppName); } - super.addView(view, params); + } + if (wp.packageName == null) { + wp.packageName = mContext.getPackageName(); + } + if (mHardwareAccelerated) { + wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } @@ -718,6 +703,7 @@ public abstract class Window { * per {@link #setFlags}. * @param flags The flag bits to be set. * @see #setFlags + * @see #clearFlags */ public void addFlags(int flags) { setFlags(flags, flags); @@ -728,6 +714,7 @@ public abstract class Window { * per {@link #setFlags}. * @param flags The flag bits to be cleared. * @see #setFlags + * @see #addFlags */ public void clearFlags(int flags) { setFlags(0, flags); @@ -749,6 +736,8 @@ public abstract class Window { * * @param flags The new window flags (see WindowManager.LayoutParams). * @param mask Which of the window flag bits to modify. + * @see #addFlags + * @see #clearFlags */ public void setFlags(int flags, int mask) { final WindowManager.LayoutParams attrs = getAttributes(); diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/WindowInfo.aidl new file mode 100644 index 0000000..23e927a --- /dev/null +++ b/core/java/android/view/WindowInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.view; + +parcelable WindowInfo; diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java new file mode 100644 index 0000000..7d16e14 --- /dev/null +++ b/core/java/android/view/WindowInfo.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.graphics.Rect; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Information the state of a window. + * + * @hide + */ +public class WindowInfo implements Parcelable { + + private static final int MAX_POOL_SIZE = 20; + + private static int UNDEFINED = -1; + + private static Object sPoolLock = new Object(); + private static WindowInfo sPool; + private static int sPoolSize; + + private WindowInfo mNext; + private boolean mInPool; + + public IBinder token; + + public final Rect frame = new Rect(); + + public final Rect touchableRegion = new Rect(); + + public int type = UNDEFINED; + + public float compatibilityScale = UNDEFINED; + + public boolean visible; + + public int displayId = UNDEFINED; + + public int layer = UNDEFINED; + + private WindowInfo() { + /* do nothing - reduce visibility */ + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeStrongBinder(token); + parcel.writeParcelable(frame, 0); + parcel.writeParcelable(touchableRegion, 0); + parcel.writeInt(type); + parcel.writeFloat(compatibilityScale); + parcel.writeInt(visible ? 1 : 0); + parcel.writeInt(displayId); + parcel.writeInt(layer); + recycle(); + } + + private void initFromParcel(Parcel parcel) { + token = parcel.readStrongBinder(); + frame.set((Rect) parcel.readParcelable(null)); + touchableRegion.set((Rect) parcel.readParcelable(null)); + type = parcel.readInt(); + compatibilityScale = parcel.readFloat(); + visible = (parcel.readInt() == 1); + displayId = parcel.readInt(); + layer = parcel.readInt(); + } + + public static WindowInfo obtain(WindowInfo other) { + WindowInfo info = obtain(); + info.token = other.token; + info.frame.set(other.frame); + info.touchableRegion.set(other.touchableRegion); + info.type = other.type; + info.compatibilityScale = other.compatibilityScale; + info.visible = other.visible; + info.displayId = other.displayId; + info.layer = other.layer; + return info; + } + + public static WindowInfo obtain() { + synchronized (sPoolLock) { + if (sPoolSize > 0) { + WindowInfo info = sPool; + sPool = info.mNext; + info.mNext = null; + info.mInPool = false; + sPoolSize--; + return info; + } else { + return new WindowInfo(); + } + } + } + + public void recycle() { + if (mInPool) { + throw new IllegalStateException("Already recycled."); + } + clear(); + synchronized (sPoolLock) { + if (sPoolSize < MAX_POOL_SIZE) { + mNext = sPool; + sPool = this; + mInPool = true; + sPoolSize++; + } + } + } + + private void clear() { + token = null; + frame.setEmpty(); + touchableRegion.setEmpty(); + type = UNDEFINED; + compatibilityScale = UNDEFINED; + visible = false; + displayId = UNDEFINED; + layer = UNDEFINED; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Window [token:").append((token != null) ? token.hashCode() : null); + builder.append(", displayId:").append(displayId); + builder.append(", type:").append(type); + builder.append(", visible:").append(visible); + builder.append(", layer:").append(layer); + builder.append(", compatibilityScale:").append(compatibilityScale); + builder.append(", frame:").append(frame); + builder.append(", touchableRegion:").append(touchableRegion); + builder.append("]"); + return builder.toString(); + } + + /** + * @see Parcelable.Creator + */ + public static final Parcelable.Creator<WindowInfo> CREATOR = + new Parcelable.Creator<WindowInfo>() { + public WindowInfo createFromParcel(Parcel parcel) { + WindowInfo info = WindowInfo.obtain(); + info.initFromParcel(parcel); + return info; + } + + public WindowInfo[] newArray(int size) { + return new WindowInfo[size]; + } + }; +} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index d94275b..6e51270 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -16,6 +16,8 @@ package android.view; +import android.app.Presentation; +import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.PixelFormat; import android.os.IBinder; @@ -29,6 +31,17 @@ import android.util.Log; * The interface that apps use to talk to the window manager. * <p> * Use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> to get one of these. + * </p><p> + * Each window manager instance is bound to a particular {@link Display}. + * To obtain a {@link WindowManager} for a different display, use + * {@link Context#createDisplayContext} to obtain a {@link Context} for that + * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> + * to get the WindowManager. + * </p><p> + * The simplest way to show a window on another display is to create a + * {@link Presentation}. The presentation will automatically obtain a + * {@link WindowManager} and {@link Context} for that display. + * </p> * * @see android.content.Context#getSystemService * @see android.content.Context#WINDOW_SERVICE @@ -49,12 +62,24 @@ public interface WindowManager extends ViewManager { } /** - * Use this method to get the default Display object. - * - * @return default Display object + * Returns the {@link Display} upon which this {@link WindowManager} instance + * will create new windows. + * <p> + * Despite the name of this method, the display that is returned is not + * necessarily the primary display of the system (see {@link Display#DEFAULT_DISPLAY}). + * The returned display could instead be a secondary display that this + * window manager instance is managing. Think of it as the display that + * this {@link WindowManager} instance uses by default. + * </p><p> + * To create windows on a different display, you need to obtain a + * {@link WindowManager} for that {@link Display}. (See the {@link WindowManager} + * class documentation for more information.) + * </p> + * + * @return The display that this window manager is managing. */ public Display getDefaultDisplay(); - + /** * Special variation of {@link #removeView} that immediately invokes * the given view hierarchy's {@link View#onDetachedFromWindow() @@ -64,15 +89,7 @@ public interface WindowManager extends ViewManager { * @param view The view to be removed. */ public void removeViewImmediate(View view); - - /** - * Return true if this window manager is configured to request hardware - * accelerated windows. This does <em>not</em> guarantee that they will - * actually be accelerated, since that depends on the device supporting them. - * @hide - */ - public boolean isHardwareAccelerated(); - + public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { /** @@ -162,6 +179,7 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA, to = "TYPE_APPLICATION_MEDIA"), @ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL, to = "TYPE_APPLICATION_SUB_PANEL"), @ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG, to = "TYPE_APPLICATION_ATTACHED_DIALOG"), + @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY, to = "TYPE_APPLICATION_MEDIA_OVERLAY"), @ViewDebug.IntToString(from = TYPE_STATUS_BAR, to = "TYPE_STATUS_BAR"), @ViewDebug.IntToString(from = TYPE_SEARCH_BAR, to = "TYPE_SEARCH_BAR"), @ViewDebug.IntToString(from = TYPE_PHONE, to = "TYPE_PHONE"), @@ -170,8 +188,6 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_TOAST, to = "TYPE_TOAST"), @ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY, to = "TYPE_SYSTEM_OVERLAY"), @ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE, to = "TYPE_PRIORITY_PHONE"), - @ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL, to = "TYPE_STATUS_BAR_PANEL"), - @ViewDebug.IntToString(from = TYPE_STATUS_BAR_SUB_PANEL, to = "TYPE_STATUS_BAR_SUB_PANEL"), @ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG, to = "TYPE_SYSTEM_DIALOG"), @ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG, to = "TYPE_KEYGUARD_DIALOG"), @ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR, to = "TYPE_SYSTEM_ERROR"), @@ -185,7 +201,12 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_POINTER, to = "TYPE_POINTER"), @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR, to = "TYPE_NAVIGATION_BAR"), @ViewDebug.IntToString(from = TYPE_VOLUME_OVERLAY, to = "TYPE_VOLUME_OVERLAY"), - @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS, to = "TYPE_BOOT_PROGRESS") + @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS, to = "TYPE_BOOT_PROGRESS"), + @ViewDebug.IntToString(from = TYPE_HIDDEN_NAV_CONSUMER, to = "TYPE_HIDDEN_NAV_CONSUMER"), + @ViewDebug.IntToString(from = TYPE_DREAM, to = "TYPE_DREAM"), + @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"), + @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY"), + @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY") }) public int type; @@ -435,6 +456,25 @@ public interface WindowManager extends ViewManager { public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24; /** + * Window type: Behind the universe of the real windows. + * @hide + */ + public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25; + + /** + * Window type: Display overlay window. Used to simulate secondary display devices. + * @hide + */ + public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26; + + /** + * Window type: Magnification overlay window. Used to highlight the magnified + * portion of a display when accessibility magnification is enabled. + * @hide + */ + public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27; + + /** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW = 2999; @@ -530,7 +570,9 @@ public interface WindowManager extends ViewManager { public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800; /** Window flag: turn on dithering when compositing this window to - * the screen. */ + * the screen. + * @deprecated This flag is no longer used. */ + @Deprecated public static final int FLAG_DITHER = 0x00001000; /** Window flag: don't allow screen shots while this window is @@ -718,7 +760,6 @@ public interface WindowManager extends ViewManager { * @see #FLAG_LAYOUT_NO_LIMITS * @see #FLAG_FULLSCREEN * @see #FLAG_FORCE_NOT_FULLSCREEN - * @see #FLAG_DITHER * @see #FLAG_SECURE * @see #FLAG_SCALED * @see #FLAG_IGNORE_CHEEK_PRESSES @@ -1135,14 +1176,42 @@ public interface WindowManager extends ViewManager { public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002; /** + * When this window has focus, does not call user activity for all input events so + * the application will have to do it itself. Should only be used by + * the keyguard and phone app. + * <p> + * Should only be used by the keyguard and phone app. + * </p> + * + * @hide + */ + public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004; + + /** * Control special features of the input subsystem. * * @see #INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES * @see #INPUT_FEATURE_NO_INPUT_CHANNEL + * @see #INPUT_FEATURE_DISABLE_USER_ACTIVITY * @hide */ public int inputFeatures; + /** + * Sets the number of milliseconds before the user activity timeout occurs + * when this window has focus. A value of -1 uses the standard timeout. + * A value of 0 uses the minimum support display timeout. + * <p> + * This property can only be used to reduce the user specified display timeout; + * it can never make the timeout longer than it normally would be. + * </p><p> + * Should only be used by the keyguard and phone app. + * </p> + * + * @hide + */ + public long userActivityTimeout = -1; + public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; @@ -1227,6 +1296,7 @@ public interface WindowManager extends ViewManager { out.writeInt(subtreeSystemUiVisibility); out.writeInt(hasSystemUiListeners ? 1 : 0); out.writeInt(inputFeatures); + out.writeLong(userActivityTimeout); } public static final Parcelable.Creator<LayoutParams> CREATOR @@ -1267,6 +1337,7 @@ public interface WindowManager extends ViewManager { subtreeSystemUiVisibility = in.readInt(); hasSystemUiListeners = in.readInt() != 0; inputFeatures = in.readInt(); + userActivityTimeout = in.readLong(); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -1293,6 +1364,8 @@ public interface WindowManager extends ViewManager { /** {@hide} */ public static final int PRIVATE_FLAGS_CHANGED = 1<<16; /** {@hide} */ + public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<17; + /** {@hide} */ public static final int EVERYTHING_CHANGED = 0xffffffff; // internal buffer to backup/restore parameters under compatibility mode. @@ -1414,6 +1487,11 @@ public interface WindowManager extends ViewManager { changes |= INPUT_FEATURES_CHANGED; } + if (userActivityTimeout != o.userActivityTimeout) { + userActivityTimeout = o.userActivityTimeout; + changes |= USER_ACTIVITY_TIMEOUT_CHANGED; + } + return changes; } @@ -1506,6 +1584,9 @@ public interface WindowManager extends ViewManager { if (inputFeatures != 0) { sb.append(" if=0x").append(Integer.toHexString(inputFeatures)); } + if (userActivityTimeout >= 0) { + sb.append(" userActivityTimeout=").append(userActivityTimeout); + } sb.append('}'); return sb.toString(); } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java new file mode 100644 index 0000000..7855763c --- /dev/null +++ b/core/java/android/view/WindowManagerGlobal.java @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.animation.ValueAnimator; +import android.app.ActivityManager; +import android.content.ComponentCallbacks2; +import android.content.res.Configuration; +import android.opengl.ManagedEGLContext; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.util.AndroidRuntimeException; +import android.util.Log; +import android.view.inputmethod.InputMethodManager; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.PrintWriter; + +/** + * Provides low-level communication with the system window manager for + * operations that are not associated with any particular context. + * + * This class is only used internally to implement global functions where + * the caller already knows the display and relevant compatibility information + * for the operation. For most purposes, you should use {@link WindowManager} instead + * since it is bound to a context. + * + * @see WindowManagerImpl + * @hide + */ +public final class WindowManagerGlobal { + private static final String TAG = "WindowManager"; + + /** + * The user is navigating with keys (not the touch screen), so + * navigational focus should be shown. + */ + public static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1; + + /** + * This is the first time the window is being drawn, + * so the client must call drawingFinished() when done + */ + public static final int RELAYOUT_RES_FIRST_TIME = 0x2; + + /** + * The window manager has changed the surface from the last call. + */ + public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4; + + /** + * The window manager is currently animating. It will call + * IWindow.doneAnimating() when done. + */ + public static final int RELAYOUT_RES_ANIMATING = 0x8; + + /** + * Flag for relayout: the client will be later giving + * internal insets; as a result, the window will not impact other window + * layouts until the insets are given. + */ + public static final int RELAYOUT_INSETS_PENDING = 0x1; + + /** + * Flag for relayout: the client may be currently using the current surface, + * so if it is to be destroyed as a part of the relayout the destroy must + * be deferred until later. The client will call performDeferredDestroy() + * when it is okay. + */ + public static final int RELAYOUT_DEFER_SURFACE_DESTROY = 0x2; + + public static final int ADD_FLAG_APP_VISIBLE = 0x2; + public static final int ADD_FLAG_IN_TOUCH_MODE = RELAYOUT_RES_IN_TOUCH_MODE; + + public static final int ADD_OKAY = 0; + public static final int ADD_BAD_APP_TOKEN = -1; + public static final int ADD_BAD_SUBWINDOW_TOKEN = -2; + public static final int ADD_NOT_APP_TOKEN = -3; + public static final int ADD_APP_EXITING = -4; + public static final int ADD_DUPLICATE_ADD = -5; + public static final int ADD_STARTING_NOT_NEEDED = -6; + public static final int ADD_MULTIPLE_SINGLETON = -7; + public static final int ADD_PERMISSION_DENIED = -8; + + private static WindowManagerGlobal sDefaultWindowManager; + private static IWindowManager sWindowManagerService; + private static IWindowSession sWindowSession; + + private final Object mLock = new Object(); + + private View[] mViews; + private ViewRootImpl[] mRoots; + private WindowManager.LayoutParams[] mParams; + private boolean mNeedsEglTerminate; + + private Runnable mSystemPropertyUpdater; + + private WindowManagerGlobal() { + } + + public static WindowManagerGlobal getInstance() { + synchronized (WindowManagerGlobal.class) { + if (sDefaultWindowManager == null) { + sDefaultWindowManager = new WindowManagerGlobal(); + } + return sDefaultWindowManager; + } + } + + public static IWindowManager getWindowManagerService() { + synchronized (WindowManagerGlobal.class) { + if (sWindowManagerService == null) { + sWindowManagerService = IWindowManager.Stub.asInterface( + ServiceManager.getService("window")); + } + return sWindowManagerService; + } + } + + public static IWindowSession getWindowSession(Looper mainLooper) { + synchronized (WindowManagerGlobal.class) { + if (sWindowSession == null) { + try { + InputMethodManager imm = InputMethodManager.getInstance(mainLooper); + IWindowManager windowManager = getWindowManagerService(); + sWindowSession = windowManager.openSession( + imm.getClient(), imm.getInputContext()); + float animatorScale = windowManager.getAnimationScale(2); + ValueAnimator.setDurationScale(animatorScale); + } catch (RemoteException e) { + Log.e(TAG, "Failed to open window session", e); + } + } + return sWindowSession; + } + } + + public static IWindowSession peekWindowSession() { + synchronized (WindowManagerGlobal.class) { + return sWindowSession; + } + } + + public void addView(View view, ViewGroup.LayoutParams params, + Display display, Window parentWindow) { + if (view == null) { + throw new IllegalArgumentException("view must not be null"); + } + if (display == null) { + throw new IllegalArgumentException("display must not be null"); + } + if (!(params instanceof WindowManager.LayoutParams)) { + throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); + } + + final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; + if (parentWindow != null) { + parentWindow.adjustLayoutParamsForSubWindow(wparams); + } + + ViewRootImpl root; + View panelParentView = null; + + synchronized (mLock) { + // Start watching for system property changes. + if (mSystemPropertyUpdater == null) { + mSystemPropertyUpdater = new Runnable() { + @Override public void run() { + synchronized (mLock) { + for (ViewRootImpl root : mRoots) { + root.loadSystemProperties(); + } + } + } + }; + SystemProperties.addChangeCallback(mSystemPropertyUpdater); + } + + int index = findViewLocked(view, false); + if (index >= 0) { + throw new IllegalStateException("View " + view + + " has already been added to the window manager."); + } + + // If this is a panel window, then find the window it is being + // attached to for future reference. + if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && + wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + final int count = mViews != null ? mViews.length : 0; + for (int i=0; i<count; i++) { + if (mRoots[i].mWindow.asBinder() == wparams.token) { + panelParentView = mViews[i]; + } + } + } + + root = new ViewRootImpl(view.getContext(), display); + + view.setLayoutParams(wparams); + + if (mViews == null) { + index = 1; + mViews = new View[1]; + mRoots = new ViewRootImpl[1]; + mParams = new WindowManager.LayoutParams[1]; + } else { + index = mViews.length + 1; + Object[] old = mViews; + mViews = new View[index]; + System.arraycopy(old, 0, mViews, 0, index-1); + old = mRoots; + mRoots = new ViewRootImpl[index]; + System.arraycopy(old, 0, mRoots, 0, index-1); + old = mParams; + mParams = new WindowManager.LayoutParams[index]; + System.arraycopy(old, 0, mParams, 0, index-1); + } + index--; + + mViews[index] = view; + mRoots[index] = root; + mParams[index] = wparams; + } + + // do this last because it fires off messages to start doing things + root.setView(view, wparams, panelParentView); + } + + public void updateViewLayout(View view, ViewGroup.LayoutParams params) { + if (view == null) { + throw new IllegalArgumentException("view must not be null"); + } + if (!(params instanceof WindowManager.LayoutParams)) { + throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); + } + + final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; + + view.setLayoutParams(wparams); + + synchronized (mLock) { + int index = findViewLocked(view, true); + ViewRootImpl root = mRoots[index]; + mParams[index] = wparams; + root.setLayoutParams(wparams, false); + } + } + + public void removeView(View view, boolean immediate) { + if (view == null) { + throw new IllegalArgumentException("view must not be null"); + } + + synchronized (mLock) { + int index = findViewLocked(view, true); + View curView = removeViewLocked(index, immediate); + if (curView == view) { + return; + } + + throw new IllegalStateException("Calling with view " + view + + " but the ViewAncestor is attached to " + curView); + } + } + + public void closeAll(IBinder token, String who, String what) { + synchronized (mLock) { + if (mViews == null) + return; + + int count = mViews.length; + //Log.i("foo", "Closing all windows of " + token); + for (int i=0; i<count; i++) { + //Log.i("foo", "@ " + i + " token " + mParams[i].token + // + " view " + mRoots[i].getView()); + if (token == null || mParams[i].token == token) { + ViewRootImpl root = mRoots[i]; + + //Log.i("foo", "Force closing " + root); + if (who != null) { + WindowLeaked leak = new WindowLeaked( + what + " " + who + " has leaked window " + + root.getView() + " that was originally added here"); + leak.setStackTrace(root.getLocation().getStackTrace()); + Log.e(TAG, leak.getMessage(), leak); + } + + removeViewLocked(i, false); + i--; + count--; + } + } + } + } + + private View removeViewLocked(int index, boolean immediate) { + ViewRootImpl root = mRoots[index]; + View view = root.getView(); + + if (view != null) { + InputMethodManager imm = InputMethodManager.getInstance(view.getContext()); + if (imm != null) { + imm.windowDismissed(mViews[index].getWindowToken()); + } + } + root.die(immediate); + + final int count = mViews.length; + + // remove it from the list + View[] tmpViews = new View[count-1]; + removeItem(tmpViews, mViews, index); + mViews = tmpViews; + + ViewRootImpl[] tmpRoots = new ViewRootImpl[count-1]; + removeItem(tmpRoots, mRoots, index); + mRoots = tmpRoots; + + WindowManager.LayoutParams[] tmpParams + = new WindowManager.LayoutParams[count-1]; + removeItem(tmpParams, mParams, index); + mParams = tmpParams; + + if (view != null) { + view.assignParent(null); + // func doesn't allow null... does it matter if we clear them? + //view.setLayoutParams(null); + } + return view; + } + + private static void removeItem(Object[] dst, Object[] src, int index) { + if (dst.length > 0) { + if (index > 0) { + System.arraycopy(src, 0, dst, 0, index); + } + if (index < dst.length) { + System.arraycopy(src, index+1, dst, index, src.length-index-1); + } + } + } + + private int findViewLocked(View view, boolean required) { + synchronized (mLock) { + if (mViews != null) { + final int count = mViews.length; + for (int i = 0; i < count; i++) { + if (mViews[i] == view) { + return i; + } + } + } + if (required) { + throw new IllegalArgumentException("View not attached to window manager"); + } + return -1; + } + } + + public void startTrimMemory(int level) { + if (HardwareRenderer.isAvailable()) { + // On low-end gfx devices we trim when memory is moderate; + // on high-end devices we do this when low. + if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE + || (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE + && !ActivityManager.isHighEndGfx())) { + // Destroy all hardware surfaces and resources associated to + // known windows + synchronized (mLock) { + if (mViews == null) return; + int count = mViews.length; + for (int i = 0; i < count; i++) { + mRoots[i].terminateHardwareResources(); + } + } + // Force a full memory flush + mNeedsEglTerminate = true; + HardwareRenderer.startTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); + return; + } + + HardwareRenderer.startTrimMemory(level); + } + } + + public void endTrimMemory() { + HardwareRenderer.endTrimMemory(); + + if (mNeedsEglTerminate) { + ManagedEGLContext.doTerminate(); + mNeedsEglTerminate = false; + } + } + + public void trimLocalMemory() { + synchronized (mLock) { + if (mViews == null) return; + int count = mViews.length; + for (int i = 0; i < count; i++) { + mRoots[i].destroyHardwareLayers(); + } + } + } + + public void dumpGfxInfo(FileDescriptor fd) { + FileOutputStream fout = new FileOutputStream(fd); + PrintWriter pw = new PrintWriter(fout); + try { + synchronized (mLock) { + if (mViews != null) { + final int count = mViews.length; + + pw.println("Profile data in ms:"); + + for (int i = 0; i < count; i++) { + ViewRootImpl root = mRoots[i]; + String name = getWindowName(root); + pw.printf("\n\t%s", name); + + HardwareRenderer renderer = + root.getView().mAttachInfo.mHardwareRenderer; + if (renderer != null) { + renderer.dumpGfxInfo(pw); + } + } + + pw.println("\nView hierarchy:\n"); + + int viewsCount = 0; + int displayListsSize = 0; + int[] info = new int[2]; + + for (int i = 0; i < count; i++) { + ViewRootImpl root = mRoots[i]; + root.dumpGfxInfo(info); + + String name = getWindowName(root); + pw.printf(" %s\n %d views, %.2f kB of display lists", + name, info[0], info[1] / 1024.0f); + HardwareRenderer renderer = + root.getView().mAttachInfo.mHardwareRenderer; + if (renderer != null) { + pw.printf(", %d frames rendered", renderer.getFrameCount()); + } + pw.printf("\n\n"); + + viewsCount += info[0]; + displayListsSize += info[1]; + } + + pw.printf("\nTotal ViewRootImpl: %d\n", count); + pw.printf("Total Views: %d\n", viewsCount); + pw.printf("Total DisplayList: %.2f kB\n\n", displayListsSize / 1024.0f); + } + } + } finally { + pw.flush(); + } + } + + private static String getWindowName(ViewRootImpl root) { + return root.mWindowAttributes.getTitle() + "/" + + root.getClass().getName() + '@' + Integer.toHexString(root.hashCode()); + } + + public void setStoppedState(IBinder token, boolean stopped) { + synchronized (mLock) { + if (mViews != null) { + int count = mViews.length; + for (int i=0; i < count; i++) { + if (token == null || mParams[i].token == token) { + ViewRootImpl root = mRoots[i]; + root.setStopped(stopped); + } + } + } + } + } + + public void reportNewConfiguration(Configuration config) { + synchronized (mLock) { + if (mViews != null) { + int count = mViews.length; + config = new Configuration(config); + for (int i=0; i < count; i++) { + ViewRootImpl root = mRoots[i]; + root.requestUpdateConfiguration(config); + } + } + } + } +} + +final class WindowLeaked extends AndroidRuntimeException { + public WindowLeaked(String msg) { + super(msg); + } +} diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index dd6b537..52d79f8 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -16,36 +16,17 @@ package android.view; -import android.app.ActivityManager; -import android.content.ComponentCallbacks2; -import android.content.res.CompatibilityInfo; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.opengl.ManagedEGLContext; -import android.os.IBinder; -import android.os.SystemProperties; -import android.util.AndroidRuntimeException; -import android.util.Log; -import android.view.inputmethod.InputMethodManager; - -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.HashMap; - -final class WindowLeaked extends AndroidRuntimeException { - public WindowLeaked(String msg) { - super(msg); - } -} - /** - * Low-level communication with the global system window manager. It implements - * the ViewManager interface, allowing you to add any View subclass as a - * top-level window on the screen. Additional window manager specific layout - * parameters are defined for control over how windows are displayed. - * It also implemens the WindowManager interface, allowing you to control the - * displays attached to the device. + * Provides low-level communication with the system window manager for + * operations that are bound to a particular context, display or parent window. + * Instances of this object are sensitive to the compatibility info associated + * with the running application. + * + * This object implements the {@link ViewManager} interface, + * allowing you to add any View subclass as a top-level window on the screen. + * Additional window manager specific layout parameters are defined for + * control over how windows are displayed. It also implements the {@link WindowManager} + * interface, allowing you to control the displays attached to the device. * * <p>Applications will not normally use WindowManager directly, instead relying * on the higher-level facilities in {@link android.app.Activity} and @@ -53,607 +34,58 @@ final class WindowLeaked extends AndroidRuntimeException { * * <p>Even for low-level window manager access, it is almost never correct to use * this class. For example, {@link android.app.Activity#getWindowManager} - * provides a ViewManager for adding windows that are associated with that + * provides a window manager for adding windows that are associated with that * activity -- the window manager will not normally allow you to add arbitrary * windows that are not associated with an activity. - * + * + * @see WindowManager + * @see WindowManagerGlobal * @hide */ -public class WindowManagerImpl implements WindowManager { - /** - * The user is navigating with keys (not the touch screen), so - * navigational focus should be shown. - */ - public static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1; - /** - * This is the first time the window is being drawn, - * so the client must call drawingFinished() when done - */ - public static final int RELAYOUT_RES_FIRST_TIME = 0x2; - /** - * The window manager has changed the surface from the last call. - */ - public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4; - /** - * The window manager is currently animating. It will call - * IWindow.doneAnimating() when done. - */ - public static final int RELAYOUT_RES_ANIMATING = 0x8; - - /** - * Flag for relayout: the client will be later giving - * internal insets; as a result, the window will not impact other window - * layouts until the insets are given. - */ - public static final int RELAYOUT_INSETS_PENDING = 0x1; - - /** - * Flag for relayout: the client may be currently using the current surface, - * so if it is to be destroyed as a part of the relayout the destroy must - * be deferred until later. The client will call performDeferredDestroy() - * when it is okay. - */ - public static final int RELAYOUT_DEFER_SURFACE_DESTROY = 0x2; - - public static final int ADD_FLAG_APP_VISIBLE = 0x2; - public static final int ADD_FLAG_IN_TOUCH_MODE = RELAYOUT_RES_IN_TOUCH_MODE; - - public static final int ADD_OKAY = 0; - public static final int ADD_BAD_APP_TOKEN = -1; - public static final int ADD_BAD_SUBWINDOW_TOKEN = -2; - public static final int ADD_NOT_APP_TOKEN = -3; - public static final int ADD_APP_EXITING = -4; - public static final int ADD_DUPLICATE_ADD = -5; - public static final int ADD_STARTING_NOT_NEEDED = -6; - public static final int ADD_MULTIPLE_SINGLETON = -7; - public static final int ADD_PERMISSION_DENIED = -8; - - private View[] mViews; - private ViewRootImpl[] mRoots; - private WindowManager.LayoutParams[] mParams; - private boolean mNeedsEglTerminate; - - private Runnable mSystemPropertyUpdater = null; - - private final static Object sLock = new Object(); - private final static WindowManagerImpl sWindowManager = new WindowManagerImpl(); - private final static HashMap<CompatibilityInfo, WindowManager> sCompatWindowManagers - = new HashMap<CompatibilityInfo, WindowManager>(); - - static class CompatModeWrapper implements WindowManager { - private final WindowManagerImpl mWindowManager; - private final Display mDefaultDisplay; - private final CompatibilityInfoHolder mCompatibilityInfo; - - CompatModeWrapper(WindowManager wm, CompatibilityInfoHolder ci) { - mWindowManager = wm instanceof CompatModeWrapper - ? ((CompatModeWrapper)wm).mWindowManager : (WindowManagerImpl)wm; - - // Use the original display if there is no compatibility mode - // to apply, or the underlying window manager is already a - // compatibility mode wrapper. (We assume that if it is a - // wrapper, it is applying the same compatibility mode.) - if (ci == null) { - mDefaultDisplay = mWindowManager.getDefaultDisplay(); - } else { - //mDefaultDisplay = mWindowManager.getDefaultDisplay(); - mDefaultDisplay = Display.createCompatibleDisplay( - mWindowManager.getDefaultDisplay().getDisplayId(), ci); - } - - mCompatibilityInfo = ci; - } - - @Override - public void addView(View view, android.view.ViewGroup.LayoutParams params) { - mWindowManager.addView(view, params, mCompatibilityInfo); - } - - @Override - public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) { - mWindowManager.updateViewLayout(view, params); - - } - - @Override - public void removeView(View view) { - mWindowManager.removeView(view); - } - - @Override - public Display getDefaultDisplay() { - return mDefaultDisplay; - } - - @Override - public void removeViewImmediate(View view) { - mWindowManager.removeViewImmediate(view); - } - - @Override - public boolean isHardwareAccelerated() { - return mWindowManager.isHardwareAccelerated(); - } +public final class WindowManagerImpl implements WindowManager { + private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); + private final Display mDisplay; + private final Window mParentWindow; + public WindowManagerImpl(Display display) { + this(display, null); } - public static WindowManagerImpl getDefault() { - return sWindowManager; + private WindowManagerImpl(Display display, Window parentWindow) { + mDisplay = display; + mParentWindow = parentWindow; } - public static WindowManager getDefault(CompatibilityInfo compatInfo) { - CompatibilityInfoHolder cih = new CompatibilityInfoHolder(); - cih.set(compatInfo); - if (cih.getIfNeeded() == null) { - return sWindowManager; - } - - synchronized (sLock) { - // NOTE: It would be cleaner to move the implementation of - // WindowManagerImpl into a static inner class, and have this - // public impl just call into that. Then we can make multiple - // instances of WindowManagerImpl for compat mode rather than - // having to make wrappers. - WindowManager wm = sCompatWindowManagers.get(compatInfo); - if (wm == null) { - wm = new CompatModeWrapper(sWindowManager, cih); - sCompatWindowManagers.put(compatInfo, wm); - } - return wm; - } + public WindowManagerImpl createLocalWindowManager(Window parentWindow) { + return new WindowManagerImpl(mDisplay, parentWindow); } - public static WindowManager getDefault(CompatibilityInfoHolder compatInfo) { - return new CompatModeWrapper(sWindowManager, compatInfo); - } - - public boolean isHardwareAccelerated() { - return false; - } - - public void addView(View view) { - addView(view, new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE)); + public WindowManagerImpl createPresentationWindowManager(Display display) { + return new WindowManagerImpl(display, mParentWindow); } + @Override public void addView(View view, ViewGroup.LayoutParams params) { - addView(view, params, null, false); - } - - public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) { - addView(view, params, cih, false); - } - - private void addView(View view, ViewGroup.LayoutParams params, - CompatibilityInfoHolder cih, boolean nest) { - if (false) Log.v("WindowManager", "addView view=" + view); - - if (!(params instanceof WindowManager.LayoutParams)) { - throw new IllegalArgumentException( - "Params must be WindowManager.LayoutParams"); - } - - final WindowManager.LayoutParams wparams - = (WindowManager.LayoutParams)params; - - ViewRootImpl root; - View panelParentView = null; - - synchronized (this) { - // Start watching for system property changes. - if (mSystemPropertyUpdater == null) { - mSystemPropertyUpdater = new Runnable() { - @Override public void run() { - synchronized (this) { - synchronized (this) { - for (ViewRootImpl root : mRoots) { - root.loadSystemProperties(); - } - } - } - } - }; - SystemProperties.addChangeCallback(mSystemPropertyUpdater); - } - - // Here's an odd/questionable case: if someone tries to add a - // view multiple times, then we simply bump up a nesting count - // and they need to remove the view the corresponding number of - // times to have it actually removed from the window manager. - // This is useful specifically for the notification manager, - // which can continually add/remove the same view as a - // notification gets updated. - int index = findViewLocked(view, false); - if (index >= 0) { - if (!nest) { - throw new IllegalStateException("View " + view - + " has already been added to the window manager."); - } - root = mRoots[index]; - root.mAddNesting++; - // Update layout parameters. - view.setLayoutParams(wparams); - root.setLayoutParams(wparams, true); - return; - } - - // If this is a panel window, then find the window it is being - // attached to for future reference. - if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && - wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { - final int count = mViews != null ? mViews.length : 0; - for (int i=0; i<count; i++) { - if (mRoots[i].mWindow.asBinder() == wparams.token) { - panelParentView = mViews[i]; - } - } - } - - root = new ViewRootImpl(view.getContext()); - root.mAddNesting = 1; - if (cih == null) { - root.mCompatibilityInfo = new CompatibilityInfoHolder(); - } else { - root.mCompatibilityInfo = cih; - } - - view.setLayoutParams(wparams); - - if (mViews == null) { - index = 1; - mViews = new View[1]; - mRoots = new ViewRootImpl[1]; - mParams = new WindowManager.LayoutParams[1]; - } else { - index = mViews.length + 1; - Object[] old = mViews; - mViews = new View[index]; - System.arraycopy(old, 0, mViews, 0, index-1); - old = mRoots; - mRoots = new ViewRootImpl[index]; - System.arraycopy(old, 0, mRoots, 0, index-1); - old = mParams; - mParams = new WindowManager.LayoutParams[index]; - System.arraycopy(old, 0, mParams, 0, index-1); - } - index--; - - mViews[index] = view; - mRoots[index] = root; - mParams[index] = wparams; - } - // do this last because it fires off messages to start doing things - root.setView(view, wparams, panelParentView); + mGlobal.addView(view, params, mDisplay, mParentWindow); } + @Override public void updateViewLayout(View view, ViewGroup.LayoutParams params) { - if (!(params instanceof WindowManager.LayoutParams)) { - throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); - } - - final WindowManager.LayoutParams wparams - = (WindowManager.LayoutParams)params; - - view.setLayoutParams(wparams); - - synchronized (this) { - int index = findViewLocked(view, true); - ViewRootImpl root = mRoots[index]; - mParams[index] = wparams; - root.setLayoutParams(wparams, false); - } + mGlobal.updateViewLayout(view, params); } + @Override public void removeView(View view) { - synchronized (this) { - int index = findViewLocked(view, true); - View curView = removeViewLocked(index); - if (curView == view) { - return; - } - - throw new IllegalStateException("Calling with view " + view - + " but the ViewAncestor is attached to " + curView); - } + mGlobal.removeView(view, false); } + @Override public void removeViewImmediate(View view) { - synchronized (this) { - int index = findViewLocked(view, true); - ViewRootImpl root = mRoots[index]; - View curView = root.getView(); - - root.mAddNesting = 0; - - if (view != null) { - InputMethodManager imm = InputMethodManager.getInstance(view.getContext()); - if (imm != null) { - imm.windowDismissed(mViews[index].getWindowToken()); - } - } - - root.die(true); - finishRemoveViewLocked(curView, index); - if (curView == view) { - return; - } - - throw new IllegalStateException("Calling with view " + view - + " but the ViewAncestor is attached to " + curView); - } + mGlobal.removeView(view, true); } - - View removeViewLocked(int index) { - ViewRootImpl root = mRoots[index]; - View view = root.getView(); - // Don't really remove until we have matched all calls to add(). - root.mAddNesting--; - if (root.mAddNesting > 0) { - return view; - } - - if (view != null) { - InputMethodManager imm = InputMethodManager.getInstance(view.getContext()); - if (imm != null) { - imm.windowDismissed(mViews[index].getWindowToken()); - } - } - root.die(false); - finishRemoveViewLocked(view, index); - return view; - } - - void finishRemoveViewLocked(View view, int index) { - final int count = mViews.length; - - // remove it from the list - View[] tmpViews = new View[count-1]; - removeItem(tmpViews, mViews, index); - mViews = tmpViews; - - ViewRootImpl[] tmpRoots = new ViewRootImpl[count-1]; - removeItem(tmpRoots, mRoots, index); - mRoots = tmpRoots; - - WindowManager.LayoutParams[] tmpParams - = new WindowManager.LayoutParams[count-1]; - removeItem(tmpParams, mParams, index); - mParams = tmpParams; - - if (view != null) { - view.assignParent(null); - // func doesn't allow null... does it matter if we clear them? - //view.setLayoutParams(null); - } - } - - public void closeAll(IBinder token, String who, String what) { - synchronized (this) { - if (mViews == null) - return; - - int count = mViews.length; - //Log.i("foo", "Closing all windows of " + token); - for (int i=0; i<count; i++) { - //Log.i("foo", "@ " + i + " token " + mParams[i].token - // + " view " + mRoots[i].getView()); - if (token == null || mParams[i].token == token) { - ViewRootImpl root = mRoots[i]; - root.mAddNesting = 1; - - //Log.i("foo", "Force closing " + root); - if (who != null) { - WindowLeaked leak = new WindowLeaked( - what + " " + who + " has leaked window " - + root.getView() + " that was originally added here"); - leak.setStackTrace(root.getLocation().getStackTrace()); - Log.e("WindowManager", leak.getMessage(), leak); - } - - removeViewLocked(i); - i--; - count--; - } - } - } - } - - /** - * @param level See {@link android.content.ComponentCallbacks} - * - * @hide - */ - public void startTrimMemory(int level) { - if (HardwareRenderer.isAvailable()) { - // On low-end gfx devices we trim when memory is moderate; - // on high-end devices we do this when low. - if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE - || (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE - && !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(); - } - } - // Force a full memory flush - mNeedsEglTerminate = true; - HardwareRenderer.startTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); - return; - } - - HardwareRenderer.startTrimMemory(level); - } - } - - /** - * @hide - */ - public void endTrimMemory() { - HardwareRenderer.endTrimMemory(); - - if (mNeedsEglTerminate) { - ManagedEGLContext.doTerminate(); - mNeedsEglTerminate = false; - } - } - - /** - * @hide - */ - public void trimLocalMemory() { - synchronized (this) { - if (mViews == null) return; - int count = mViews.length; - for (int i = 0; i < count; i++) { - mRoots[i].destroyHardwareLayers(); - } - } - } - - /** - * @hide - */ - public void dumpGfxInfo(FileDescriptor fd) { - FileOutputStream fout = new FileOutputStream(fd); - PrintWriter pw = new PrintWriter(fout); - try { - synchronized (this) { - if (mViews != null) { - final int count = mViews.length; - - pw.println("Profile data in ms:"); - - for (int i = 0; i < count; i++) { - ViewRootImpl root = mRoots[i]; - String name = getWindowName(root); - pw.printf("\n\t%s", name); - - HardwareRenderer renderer = root.getView().mAttachInfo.mHardwareRenderer; - if (renderer != null) { - renderer.dumpGfxInfo(pw); - } - } - - pw.println("\nView hierarchy:\n"); - - int viewsCount = 0; - int displayListsSize = 0; - int[] info = new int[2]; - - for (int i = 0; i < count; i++) { - ViewRootImpl root = mRoots[i]; - root.dumpGfxInfo(info); - - String name = getWindowName(root); - pw.printf(" %s\n %d views, %.2f kB of display lists", - name, info[0], info[1] / 1024.0f); - HardwareRenderer renderer = root.getView().mAttachInfo.mHardwareRenderer; - if (renderer != null) { - pw.printf(", %d frames rendered", renderer.getFrameCount()); - } - pw.printf("\n\n"); - - viewsCount += info[0]; - displayListsSize += info[1]; - } - - pw.printf("\nTotal ViewRootImpl: %d\n", count); - pw.printf("Total Views: %d\n", viewsCount); - pw.printf("Total DisplayList: %.2f kB\n\n", displayListsSize / 1024.0f); - } - } - } finally { - pw.flush(); - } - } - - private static String getWindowName(ViewRootImpl root) { - return root.mWindowAttributes.getTitle() + "/" + - root.getClass().getName() + '@' + Integer.toHexString(root.hashCode()); - } - - public void setStoppedState(IBinder token, boolean stopped) { - synchronized (this) { - if (mViews == null) - return; - int count = mViews.length; - for (int i=0; i<count; i++) { - if (token == null || mParams[i].token == token) { - ViewRootImpl root = mRoots[i]; - root.setStopped(stopped); - } - } - } - } - - public void reportNewConfiguration(Configuration config) { - synchronized (this) { - int count = mViews.length; - config = new Configuration(config); - for (int i=0; i<count; i++) { - ViewRootImpl root = mRoots[i]; - root.requestUpdateConfiguration(config); - } - } - } - - public WindowManager.LayoutParams getRootViewLayoutParameter(View view) { - ViewParent vp = view.getParent(); - while (vp != null && !(vp instanceof ViewRootImpl)) { - vp = vp.getParent(); - } - - if (vp == null) return null; - - ViewRootImpl vr = (ViewRootImpl)vp; - - int N = mRoots.length; - for (int i = 0; i < N; ++i) { - if (mRoots[i] == vr) { - return mParams[i]; - } - } - - return null; - } - - public void closeAll() { - closeAll(null, null, null); - } - + @Override public Display getDefaultDisplay() { - return new Display(Display.DEFAULT_DISPLAY, null); - } - - private static void removeItem(Object[] dst, Object[] src, int index) { - if (dst.length > 0) { - if (index > 0) { - System.arraycopy(src, 0, dst, 0, index); - } - if (index < dst.length) { - System.arraycopy(src, index+1, dst, index, src.length-index-1); - } - } - } - - private int findViewLocked(View view, boolean required) { - synchronized (this) { - final int count = mViews != null ? mViews.length : 0; - for (int i=0; i<count; i++) { - if (mViews[i] == view) { - return i; - } - } - if (required) { - throw new IllegalArgumentException( - "View not attached to window manager"); - } - return -1; - } + return mDisplay; } } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 09948b8..82f07c7 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -22,7 +22,6 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.RectF; import android.os.IBinder; -import android.os.LocalPowerManager; import android.os.Looper; import android.view.animation.Animation; @@ -115,16 +114,16 @@ public interface WindowManagerPolicy { public final static int ACTION_PASS_TO_USER = 0x00000001; /** - * This key event should extend the user activity timeout and turn the lights on. + * This key event should wake the device. * To be returned from {@link #interceptKeyBeforeQueueing}. * Do not return this and {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}. */ - public final static int ACTION_POKE_USER_ACTIVITY = 0x00000002; + public final static int ACTION_WAKE_UP = 0x00000002; /** * This key event should put the device to sleep (and engage keyguard if necessary) * To be returned from {@link #interceptKeyBeforeQueueing}. - * Do not return this and {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}. + * Do not return this and {@link #ACTION_WAKE_UP} or {@link #ACTION_PASS_TO_USER}. */ public final static int ACTION_GO_TO_SLEEP = 0x00000004; @@ -339,6 +338,12 @@ public interface WindowManagerPolicy { * Check whether the process hosting this window is currently alive. */ public boolean isAlive(); + + /** + * Check if window is on {@link Display#DEFAULT_DISPLAY}. + * @return true if window is on default display. + */ + public boolean isDefaultDisplay(); } /** @@ -391,8 +396,8 @@ public interface WindowManagerPolicy { */ public void switchKeyboardLayout(int deviceId, int direction); - public void shutdown(); - public void rebootSafeMode(); + public void shutdown(boolean confirm); + public void rebootSafeMode(boolean confirm); } /** @@ -473,26 +478,24 @@ public interface WindowManagerPolicy { * Perform initialization of the policy. * * @param context The system context we are running in. - * @param powerManager */ public void init(Context context, IWindowManager windowManager, - WindowManagerFuncs windowManagerFuncs, - LocalPowerManager powerManager); + WindowManagerFuncs windowManagerFuncs); /** * Called by window manager once it has the initial, default native * display dimensions. */ - public void setInitialDisplaySize(Display display, int width, int height); + public void setInitialDisplaySize(Display display, int width, int height, int density); /** * Check permissions when adding a window. * * @param attrs The window's LayoutParams. * - * @return {@link WindowManagerImpl#ADD_OKAY} if the add can proceed; + * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed; * else an error code, usually - * {@link WindowManagerImpl#ADD_PERMISSION_DENIED}, to abort the add. + * {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add. */ public int checkAddPermission(WindowManager.LayoutParams attrs); @@ -553,7 +556,14 @@ public interface WindowManagerPolicy { * allowed to be in. */ public int getMaxWallpaperLayer(); - + + /** + * Return the window layer at which windows appear above the normal + * universe (that is no longer impacted by the universe background + * transform). + */ + public int getAboveUniverseLayer(); + /** * Return true if the policy desires a full unified system nav bar. Otherwise, * it is a phone-style status bar with optional nav bar. @@ -658,7 +668,7 @@ public interface WindowManagerPolicy { * @param win The window being added. * @param attrs The window's LayoutParams. * - * @return {@link WindowManagerImpl#ADD_OKAY} if the add can proceed, else an + * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed, else an * error code to abort the add. */ public int prepareAddWindowLw(WindowState win, @@ -703,7 +713,7 @@ public interface WindowManagerPolicy { * @param isScreenOn True if the screen is already on * * @return The bitwise or of the {@link #ACTION_PASS_TO_USER}, - * {@link #ACTION_POKE_USER_ACTIVITY} and {@link #ACTION_GO_TO_SLEEP} flags. + * {@link #ACTION_WAKE_UP} and {@link #ACTION_GO_TO_SLEEP} flags. */ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn); @@ -717,7 +727,7 @@ public interface WindowManagerPolicy { * @param policyFlags The policy flags associated with the motion. * * @return The bitwise or of the {@link #ACTION_PASS_TO_USER}, - * {@link #ACTION_POKE_USER_ACTIVITY} and {@link #ACTION_GO_TO_SLEEP} flags. + * {@link #ACTION_WAKE_UP} and {@link #ACTION_GO_TO_SLEEP} flags. */ public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags); @@ -758,12 +768,14 @@ public interface WindowManagerPolicy { /** * Called when layout of the windows is about to start. * + * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}. * @param displayWidth The current full width of the screen. * @param displayHeight The current full height of the screen. * @param displayRotation The current rotation being applied to the base * window. */ - public void beginLayoutLw(int displayWidth, int displayHeight, int displayRotation); + public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight, + int displayRotation); /** * Return the rectangle of the screen currently covered by system decorations. @@ -823,32 +835,33 @@ public interface WindowManagerPolicy { static final int FINISH_LAYOUT_REDO_ANIM = 0x0008; /** - * Called when animation of the windows is about to start. + * Called following layout of all windows before each window has policy applied. * * @param displayWidth The current full width of the screen. * @param displayHeight The current full height of the screen. */ - public void beginAnimationLw(int displayWidth, int displayHeight); + public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight); /** - * Called each time a window is animating. + * Called following layout of all window to apply policy to each window. * * @param win The window being positioned. * @param attrs The LayoutParams of the window. */ - public void animatingWindowLw(WindowState win, + public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs); /** - * Called when animation of the windows is finished. If in this function you do - * something that may have modified the animation state of another window, - * be sure to return true in order to perform another animation frame. + * Called following layout of all windows and after policy has been applied + * to each window. If in this function you do + * something that may have modified the animation state of another window, + * be sure to return non-zero in order to perform another pass through layout. * * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT}, * {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER}, * or {@link #FINISH_LAYOUT_REDO_ANIM}. */ - public int finishAnimationLw(); + public int finishPostLayoutPolicyLw(); /** * Return true if it is okay to perform animations for an app transition @@ -1061,9 +1074,9 @@ public interface WindowManagerPolicy { * Inform the policy that the user has chosen a preferred orientation ("rotation lock"). * * @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or - * {@link * WindowManagerPolicy#USER_ROTATION_FREE}. + * {@link WindowManagerPolicy#USER_ROTATION_FREE}. * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, - * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}. + * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}. */ public void setUserRotationMode(int mode, int rotation); @@ -1086,36 +1099,27 @@ public interface WindowManagerPolicy { public void lockNow(); /** - * Check to see if a screensaver should be run instead of powering off the screen on timeout. - * - * @return true if the screensaver should run, false if the screen should turn off. - * - * @hide - */ - public boolean isScreenSaverEnabled(); - - /** - * Start the screensaver (if it is enabled and not yet running). - * - * @return Whether the screensaver was successfully started. - * + * Set the last used input method window state. This state is used to make IME transition + * smooth. * @hide */ - public boolean startScreenSaver(); + public void setLastInputMethodWindowLw(WindowState ime, WindowState target); /** - * Stop the screensaver if it is running. - * - * @hide + * Returns whether magnification can be applied to the given window type. + * + * @param attrs The window's LayoutParams. + * @return Whether magnification can be applied. */ - public void stopScreenSaver(); + public boolean canMagnifyWindowLw(WindowManager.LayoutParams attrs); /** - * Set the last used input method window state. This state is used to make IME transition - * smooth. - * @hide + * Called when the current user changes. Guaranteed to be called before the broadcast + * of the new user id is made to all listeners. + * + * @param newUserId The id of the incoming user. */ - public void setLastInputMethodWindowLw(WindowState ime, WindowState target); + public void setCurrentUserLw(int newUserId); /** * Print the WindowManagerPolicy's state into the given stream. diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 1a2a194..1500905 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -424,6 +424,28 @@ import java.util.List; * </ul> * </p> * <p> + * <b>Touch interaction start</b> - represents the event of starting a touch + * interaction, which is the user starts touching the screen.</br> + * <em>Type:</em> {@link #TYPE_TOUCH_INTERACTION_START}</br> + * <em>Properties:</em></br> + * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> + * </ul> + * <em>Note:</em> This event is fired only by the system and is not passed to the + * view tree to be populated.</br> + * </p> + * <p> + * <b>Touch interaction end</b> - represents the event of ending a touch + * interaction, which is the user stops touching the screen.</br> + * <em>Type:</em> {@link #TYPE_TOUCH_INTERACTION_END}</br> + * <em>Properties:</em></br> + * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> + * </ul> + * <em>Note:</em> This event is fired only by the system and is not passed to the + * view tree to be populated.</br> + * </p> + * <p> * <b>Touch exploration gesture start</b> - represents the event of starting a touch * exploring gesture.</br> * <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_START}</br> @@ -431,15 +453,8 @@ import java.util.List; * <ul> * <li>{@link #getEventType()} - The type of the event.</li> * </ul> - * <em>Note:</em> This event type is not dispatched to descendants though - * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) - * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event - * source {@link android.view.View} and the sub-tree rooted at it will not receive - * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent) - * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add - * text content to such events is by setting the - * {@link android.R.styleable#View_contentDescription contentDescription} of the source - * view.</br> + * <em>Note:</em> This event is fired only by the system and is not passed to the + * view tree to be populated.</br> * </p> * <p> * <b>Touch exploration gesture end</b> - represents the event of ending a touch @@ -449,15 +464,30 @@ import java.util.List; * <ul> * <li>{@link #getEventType()} - The type of the event.</li> * </ul> - * <em>Note:</em> This event type is not dispatched to descendants though - * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) - * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event - * source {@link android.view.View} and the sub-tree rooted at it will not receive - * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent) - * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add - * text content to such events is by setting the - * {@link android.R.styleable#View_contentDescription contentDescription} of the source - * view.</br> + * <em>Note:</em> This event is fired only by the system and is not passed to the + * view tree to be populated.</br> + * </p> + * <p> + * <b>Touch gesture detection start</b> - represents the event of starting a user + * gesture detection.</br> + * <em>Type:</em> {@link #TYPE_GESTURE_DETECTION_START}</br> + * <em>Properties:</em></br> + * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> + * </ul> + * <em>Note:</em> This event is fired only by the system and is not passed to the + * view tree to be populated.</br> + * </p> + * <p> + * <b>Touch gesture detection end</b> - represents the event of ending a user + * gesture detection.</br> + * <em>Type:</em> {@link #TYPE_GESTURE_DETECTION_END}</br> + * <em>Properties:</em></br> + * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> + * </ul> + * <em>Note:</em> This event is fired only by the system and is not passed to the + * view tree to be populated.</br> * </p> * <p> * <b>MISCELLANEOUS TYPES</b></br> @@ -610,6 +640,26 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000; /** + * Represents the event of beginning gesture detection. + */ + public static final int TYPE_GESTURE_DETECTION_START = 0x00040000; + + /** + * Represents the event of ending gesture detection. + */ + public static final int TYPE_GESTURE_DETECTION_END = 0x00080000; + + /** + * Represents the event of the user starting to touch the screen. + */ + public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000; + + /** + * Represents the event of the user ending to touch the screen. + */ + public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000; + + /** * Mask for {@link AccessibilityEvent} all types. * * @see #TYPE_VIEW_CLICKED @@ -628,6 +678,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @see #TYPE_VIEW_TEXT_SELECTION_CHANGED * @see #TYPE_ANNOUNCEMENT * @see #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY + * @see #TYPE_GESTURE_DETECTION_START + * @see #TYPE_GESTURE_DETECTION_END + * @see #TYPE_TOUCH_INTERACTION_START + * @see #TYPE_TOUCH_INTERACTION_END */ public static final int TYPES_ALL_MASK = 0xFFFFFFFF; @@ -1120,6 +1174,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED"; case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: return "TYPE_CURRENT_AT_GRANULARITY_MOVEMENT_CHANGED"; + case TYPE_GESTURE_DETECTION_START: + return "TYPE_GESTURE_DETECTION_START"; + case TYPE_GESTURE_DETECTION_END: + return "TYPE_GESTURE_DETECTION_END"; + case TYPE_TOUCH_INTERACTION_START: + return "TYPE_TOUCH_INTERACTION_START"; + case TYPE_TOUCH_INTERACTION_END: + return "TYPE_TOUCH_INTERACTION_END"; default: return null; } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 77fd12a..732699b 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -27,6 +27,7 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.util.Log; import android.view.IWindow; import android.view.View; @@ -79,6 +80,8 @@ public final class AccessibilityManager { final IAccessibilityManager mService; + final int mUserId; + final Handler mHandler; boolean mIsEnabled; @@ -129,35 +132,72 @@ public final class AccessibilityManager { } /** + * Creates the singleton AccessibilityManager to be shared across users. This + * has to be called before the local AccessibilityManager is created to ensure + * it registers itself in the system correctly. + * <p> + * Note: Calling this method requires INTERACT_ACROSS_USERS_FULL or + * INTERACT_ACROSS_USERS permission. + * </p> + * @param context Context in which this manager operates. + * @throws IllegalStateException if not called before the local + * AccessibilityManager is instantiated. + * + * @hide + */ + public static void createAsSharedAcrossUsers(Context context) { + synchronized (sInstanceSync) { + if (sInstance != null) { + throw new IllegalStateException("AccessibilityManager already created."); + } + createSingletonInstance(context, UserHandle.USER_CURRENT); + } + } + + /** * Get an AccessibilityManager instance (create one if necessary). * + * @param context Context in which this manager operates. + * * @hide */ public static AccessibilityManager getInstance(Context context) { synchronized (sInstanceSync) { if (sInstance == null) { - IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); - IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); - sInstance = new AccessibilityManager(context, service); + createSingletonInstance(context, UserHandle.myUserId()); } } return sInstance; } /** + * Creates the singleton instance. + * + * @param context Context in which this manager operates. + * @param userId The user id under which to operate. + */ + private static void createSingletonInstance(Context context, int userId) { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder); + sInstance = new AccessibilityManager(context, service, userId); + } + + /** * Create an instance. * * @param context A {@link Context}. * @param service An interface to the backing service. + * @param userId User id under which to run. * * @hide */ - public AccessibilityManager(Context context, IAccessibilityManager service) { + public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { mHandler = new MyHandler(context.getMainLooper()); mService = service; + mUserId = userId; try { - final int stateFlags = mService.addClient(mClient); + final int stateFlags = mService.addClient(mClient, userId); setState(stateFlags); } catch (RemoteException re) { Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); @@ -222,7 +262,7 @@ public final class AccessibilityManager { // client using it is called through Binder from another process. Example: MMS // app adds a SMS notification and the NotificationManagerService calls this method long identityToken = Binder.clearCallingIdentity(); - doRecycle = mService.sendAccessibilityEvent(event); + doRecycle = mService.sendAccessibilityEvent(event, mUserId); Binder.restoreCallingIdentity(identityToken); if (DEBUG) { Log.i(LOG_TAG, event + " sent"); @@ -244,7 +284,7 @@ public final class AccessibilityManager { throw new IllegalStateException("Accessibility off. Did you forget to check that?"); } try { - mService.interrupt(); + mService.interrupt(mUserId); if (DEBUG) { Log.i(LOG_TAG, "Requested interrupt from all services"); } @@ -280,7 +320,7 @@ public final class AccessibilityManager { public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { List<AccessibilityServiceInfo> services = null; try { - services = mService.getInstalledAccessibilityServiceList(); + services = mService.getInstalledAccessibilityServiceList(mUserId); if (DEBUG) { Log.i(LOG_TAG, "Installed AccessibilityServices " + services); } @@ -307,7 +347,7 @@ public final class AccessibilityManager { int feedbackTypeFlags) { List<AccessibilityServiceInfo> services = null; try { - services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags); + services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId); if (DEBUG) { Log.i(LOG_TAG, "Installed AccessibilityServices " + services); } @@ -385,7 +425,7 @@ public final class AccessibilityManager { public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { try { - return mService.addAccessibilityInteractionConnection(windowToken, connection); + return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 3ad3a55..1dc2487 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -365,6 +365,8 @@ public class AccessibilityNodeInfo implements Parcelable { private int mWindowId = UNDEFINED; private long mSourceNodeId = ROOT_NODE_ID; private long mParentNodeId = ROOT_NODE_ID; + private long mLabelForId = ROOT_NODE_ID; + private long mLabeledById = ROOT_NODE_ID; private int mBooleanProperties; private final Rect mBoundsInParent = new Rect(); @@ -382,10 +384,6 @@ public class AccessibilityNodeInfo implements Parcelable { private int mConnectionId = UNDEFINED; - // TODO: These are a workaround for 6623031. Remove when fixed. - private int mActualAndReportedWindowLeftDelta; - private int mActualAndReportedWindowTopDelta; - /** * Hide constructor from clients. */ @@ -432,10 +430,6 @@ public class AccessibilityNodeInfo implements Parcelable { final int rootAccessibilityViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED; mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); - if (root != null) { - mActualAndReportedWindowLeftDelta = root.getActualAndReportedWindowLeftDelta(); - mActualAndReportedWindowTopDelta = root.getActualAndReportedWindowTopDelta(); - } } /** @@ -833,7 +827,6 @@ public class AccessibilityNodeInfo implements Parcelable { public void setBoundsInScreen(Rect bounds) { enforceNotSealed(); mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom); - mBoundsInScreen.offset(mActualAndReportedWindowLeftDelta, mActualAndReportedWindowTopDelta); } /** @@ -1242,6 +1235,120 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Sets the view for which the view represented by this info serves as a + * label for accessibility purposes. + * + * @param labeled The view for which this info serves as a label. + */ + public void setLabelFor(View labeled) { + setLabelFor(labeled, UNDEFINED); + } + + /** + * Sets the view for which the view represented by this info serves as a + * label for accessibility purposes. If <code>virtualDescendantId</code> + * is {@link View#NO_ID} the root is set as the labeled. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report themselves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param root The root whose virtual descendant serves as a label. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setLabelFor(View root, int virtualDescendantId) { + enforceNotSealed(); + final int rootAccessibilityViewId = (root != null) + ? root.getAccessibilityViewId() : UNDEFINED; + mLabelForId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + } + + /** + * Gets the node info for which the view represented by this info serves as + * a label for accessibility purposes. + * <p> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling {@link AccessibilityNodeInfo#recycle()} + * to avoid creating of multiple instances. + * </p> + * + * @return The labeled info. + */ + public AccessibilityNodeInfo getLabelFor() { + enforceSealed(); + if (!canPerformRequestOverConnection(mLabelForId)) { + return null; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, + mWindowId, mLabelForId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + } + + /** + * Sets the view which serves as the label of the view represented by + * this info for accessibility purposes. + * + * @param label The view that labels this node's source. + */ + public void setLabeledBy(View label) { + setLabeledBy(label, UNDEFINED); + } + + /** + * Sets the view which serves as the label of the view represented by + * this info for accessibility purposes. If <code>virtualDescendantId</code> + * is {@link View#NO_ID} the root is set as the label. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report themselves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param root The root whose virtual descendant labels this node's source. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setLabeledBy(View root, int virtualDescendantId) { + enforceNotSealed(); + final int rootAccessibilityViewId = (root != null) + ? root.getAccessibilityViewId() : UNDEFINED; + mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + } + + /** + * Gets the node info which serves as the label of the view represented by + * this info for accessibility purposes. + * <p> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling {@link AccessibilityNodeInfo#recycle()} + * to avoid creating of multiple instances. + * </p> + * + * @return The label. + */ + public AccessibilityNodeInfo getLabeledBy() { + enforceSealed(); + if (!canPerformRequestOverConnection(mLabeledById)) { + return null; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, + mWindowId, mLabeledById, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + } + + /** * Gets the value of a boolean property. * * @param property The property. @@ -1343,12 +1450,6 @@ public class AccessibilityNodeInfo implements Parcelable { case View.FOCUS_RIGHT: case View.FOCUS_FORWARD: case View.FOCUS_BACKWARD: - case View.ACCESSIBILITY_FOCUS_DOWN: - case View.ACCESSIBILITY_FOCUS_UP: - case View.ACCESSIBILITY_FOCUS_LEFT: - case View.ACCESSIBILITY_FOCUS_RIGHT: - case View.ACCESSIBILITY_FOCUS_FORWARD: - case View.ACCESSIBILITY_FOCUS_BACKWARD: return; default: throw new IllegalArgumentException("Unknown direction: " + direction); @@ -1477,6 +1578,8 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeLong(mSourceNodeId); parcel.writeInt(mWindowId); parcel.writeLong(mParentNodeId); + parcel.writeLong(mLabelForId); + parcel.writeLong(mLabeledById); parcel.writeInt(mConnectionId); SparseLongArray childIds = mChildNodeIds; @@ -1522,6 +1625,8 @@ public class AccessibilityNodeInfo implements Parcelable { mSealed = other.mSealed; mSourceNodeId = other.mSourceNodeId; mParentNodeId = other.mParentNodeId; + mLabelForId = other.mLabelForId; + mLabeledById = other.mLabeledById; mWindowId = other.mWindowId; mConnectionId = other.mConnectionId; mBoundsInParent.set(other.mBoundsInParent); @@ -1549,6 +1654,8 @@ public class AccessibilityNodeInfo implements Parcelable { mSourceNodeId = parcel.readLong(); mWindowId = parcel.readInt(); mParentNodeId = parcel.readLong(); + mLabelForId = parcel.readLong(); + mLabeledById = parcel.readLong(); mConnectionId = parcel.readInt(); SparseLongArray childIds = mChildNodeIds; @@ -1587,6 +1694,8 @@ public class AccessibilityNodeInfo implements Parcelable { mSealed = false; mSourceNodeId = ROOT_NODE_ID; mParentNodeId = ROOT_NODE_ID; + mLabelForId = ROOT_NODE_ID; + mLabeledById = ROOT_NODE_ID; mWindowId = UNDEFINED; mConnectionId = UNDEFINED; mMovementGranularities = 0; diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java index b3f3cee..688cbdf 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java +++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java @@ -132,60 +132,4 @@ public abstract class AccessibilityNodeProvider { int virtualViewId) { return null; } - - /** - * Finds the accessibility focused {@link AccessibilityNodeInfo}. The search is - * relative to the virtual view, i.e. a descendant of the host View, with the - * given <code>virtualViewId</code> or the host View itself - * <code>virtualViewId</code> equals to {@link View#NO_ID}. - * - * <strong>Note:</strong> Normally the system is responsible to transparently find - * accessibility focused view starting from a given root but for virtual view - * hierarchies it is a responsibility of this provider's implementor to find - * the accessibility focused virtual view. - * - * @param virtualViewId A client defined virtual view id which defined - * the root of the tree in which to perform the search. - * @return A list of node info. - * - * @see #createAccessibilityNodeInfo(int) - * @see AccessibilityNodeInfo - * - * @hide - */ - public AccessibilityNodeInfo findAccessibilityFocus(int virtualViewId) { - return null; - } - - /** - * Finds {@link AccessibilityNodeInfo} to take accessibility focus in the given - * <code>direction</code>. The search is relative to the virtual view, i.e. a - * descendant of the host View, with the given <code>virtualViewId</code> or - * the host View itself <code>virtualViewId</code> equals to {@link View#NO_ID}. - * - * <strong>Note:</strong> Normally the system is responsible to transparently find - * the next view to take accessibility focus but for virtual view hierarchies - * it is a responsibility of this provider's implementor to compute the next - * focusable. - * - * @param direction The direction in which to search for a focus candidate. - * Values are - * {@link View#ACCESSIBILITY_FOCUS_FORWARD}, - * {@link View#ACCESSIBILITY_FOCUS_BACKWARD}, - * {@link View#ACCESSIBILITY_FOCUS_UP}, - * {@link View#ACCESSIBILITY_FOCUS_DOWN}, - * {@link View#ACCESSIBILITY_FOCUS_LEFT}, - * {@link View#ACCESSIBILITY_FOCUS_RIGHT}. - * @param virtualViewId A client defined virtual view id which defined - * the root of the tree in which to perform the search. - * @return A list of node info. - * - * @see #createAccessibilityNodeInfo(int) - * @see AccessibilityNodeInfo - * - * @hide - */ - public AccessibilityNodeInfo accessibilityFocusSearch(int direction, int virtualViewId) { - return null; - } } diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index 292702a..9b39300 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -28,25 +28,25 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback; */ oneway interface IAccessibilityInteractionConnection { - void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int windowLeft, - int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, - int flags, int interrogatingPid, long interrogatingTid); + void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, + long interrogatingTid); - void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int windowLeft, - int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, - int flags, int interrogatingPid, long interrogatingTid); + void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, + long interrogatingTid); - void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int windowLeft, - int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, - int flags, int interrogatingPid, long interrogatingTid); + void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, + long interrogatingTid); - void findFocus(long accessibilityNodeId, int focusType, int windowLeft, int windowTop, - int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid); + void findFocus(long accessibilityNodeId, int focusType, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, + long interrogatingTid); - void focusSearch(long accessibilityNodeId, int direction, int windowLeft, int windowTop, - int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid); + void focusSearch(long accessibilityNodeId, int direction, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, + long interrogatingTid); void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 5b5134a..c3ef54c 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -20,6 +20,7 @@ package android.view.accessibility; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IAccessibilityServiceClient; +import android.content.ComponentName; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnection; @@ -34,18 +35,18 @@ import android.view.IWindow; */ interface IAccessibilityManager { - int addClient(IAccessibilityManagerClient client); + int addClient(IAccessibilityManagerClient client, int userId); - boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent); + boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId); - List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(); + List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId); - List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType); + List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId); - void interrupt(); + void interrupt(int userId); int addAccessibilityInteractionConnection(IWindow windowToken, - in IAccessibilityInteractionConnection connection); + in IAccessibilityInteractionConnection connection, int userId); void removeAccessibilityInteractionConnection(IWindow windowToken); @@ -53,4 +54,7 @@ interface IAccessibilityManager { in AccessibilityServiceInfo info); void unregisterUiTestAutomationService(IAccessibilityServiceClient client); + + void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service, + boolean touchExplorationEnabled); } diff --git a/core/java/android/view/animation/RotateAnimation.java b/core/java/android/view/animation/RotateAnimation.java index 67e0374..3c325d9 100644 --- a/core/java/android/view/animation/RotateAnimation.java +++ b/core/java/android/view/animation/RotateAnimation.java @@ -22,7 +22,7 @@ import android.util.AttributeSet; /** * An animation that controls the rotation of an object. This rotation takes - * place int the X-Y plane. You can specify the point to use for the center of + * place in the X-Y plane. You can specify the point to use for the center of * the rotation, where (0,0) is the top left point. If not specified, (0,0) is * the default rotation point. * diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 131f0ae..08e30aa 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -34,6 +34,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Printer; +import android.util.Slog; import android.util.Xml; import java.io.IOException; @@ -169,7 +170,10 @@ public final class InputMethodInfo implements Parcelable { a.getBoolean(com.android.internal.R.styleable .InputMethod_Subtype_isAuxiliary, false), a.getBoolean(com.android.internal.R.styleable - .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false)); + .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false), + a.getInt(com.android.internal.R.styleable + .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */) + ); if (!subtype.isAuxiliary()) { mIsAuxIme = false; } @@ -194,6 +198,9 @@ public final class InputMethodInfo implements Parcelable { final InputMethodSubtype subtype = additionalSubtypes.get(i); if (!mSubtypes.contains(subtype)) { mSubtypes.add(subtype); + } else { + Slog.w(TAG, "Duplicated subtype definition found: " + + subtype.getLocale() + ", " + subtype.getMode()); } } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index d2cc2d8..3ea6df3 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -16,7 +16,7 @@ package android.view.inputmethod; -import com.android.internal.os.HandlerCaller; +import com.android.internal.os.SomeArgs; import com.android.internal.view.IInputConnectionWrapper; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodCallback; @@ -35,6 +35,7 @@ import android.os.Message; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.SystemClock; import android.text.style.SuggestionSpan; import android.util.Log; import android.util.PrintWriterPrinter; @@ -225,6 +226,13 @@ public final class InputMethodManager { */ public static final int CONTROL_START_INITIAL = 1<<8; + /** + * Timeout in milliseconds for delivering a key to an IME. + */ + static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500; + + private static final int MAX_PENDING_EVENT_POOL_SIZE = 4; + final IInputMethodManager mService; final Looper mMainLooper; @@ -312,12 +320,17 @@ public final class InputMethodManager { */ IInputMethodSession mCurMethod; + PendingEvent mPendingEventPool; + int mPendingEventPoolSize; + PendingEvent mFirstPendingEvent; + // ----------------------------------------------------------- static final int MSG_DUMP = 1; static final int MSG_BIND = 2; static final int MSG_UNBIND = 3; static final int MSG_SET_ACTIVE = 4; + static final int MSG_EVENT_TIMEOUT = 5; class H extends Handler { H(Looper looper) { @@ -328,7 +341,7 @@ public final class InputMethodManager { public void handleMessage(Message msg) { switch (msg.what) { case MSG_DUMP: { - HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + SomeArgs args = (SomeArgs)msg.obj; try { doDump((FileDescriptor)args.arg1, (PrintWriter)args.arg2, (String[])args.arg3); @@ -338,10 +351,14 @@ public final class InputMethodManager { synchronized (args.arg4) { ((CountDownLatch)args.arg4).countDown(); } + args.recycle(); return; } case MSG_BIND: { final InputBindResult res = (InputBindResult)msg.obj; + if (DEBUG) { + Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id); + } synchronized (mH) { if (mBindSequence < 0 || mBindSequence != res.sequence) { Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence @@ -358,6 +375,9 @@ public final class InputMethodManager { } case MSG_UNBIND: { final int sequence = msg.arg1; + if (DEBUG) { + Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence); + } boolean startInput = false; synchronized (mH) { if (mBindSequence == sequence) { @@ -390,6 +410,9 @@ public final class InputMethodManager { } case MSG_SET_ACTIVE: { final boolean active = msg.arg1 != 0; + if (DEBUG) { + Log.i(TAG, "handleMessage: MSG_SET_ACTIVE " + active + ", was " + mActive); + } synchronized (mH) { mActive = active; mFullscreenMode = false; @@ -407,12 +430,32 @@ public final class InputMethodManager { // Check focus again in case that "onWindowFocus" is called before // handling this message. if (mServedView != null && mServedView.hasWindowFocus()) { - checkFocus(mHasBeenInactive); + // "finishComposingText" has been already called above. So we + // should not call mServedInputConnection.finishComposingText here. + // Also, please note that this handler thread could be different + // from a thread that created mServedView. That could happen + // the current activity is running in the system process. + // In that case, we really should not call + // mServedInputConnection.finishComposingText. + if (checkFocusNoStartInput(mHasBeenInactive, false)) { + startInputInner(null, 0, 0, 0); + } } } } return; } + case MSG_EVENT_TIMEOUT: { + // Even though the message contains both the sequence number + // and the PendingEvent object itself, we only pass the + // sequence number to the timeoutEvent function because it's + // possible for the PendingEvent object to be dequeued and + // recycled concurrently. To avoid a possible race, we make + // a point of always looking up the PendingEvent within the + // queue given only the sequence number of the event. + timeoutEvent(msg.arg1); + return; + } } } } @@ -444,7 +487,7 @@ public final class InputMethodManager { // interface to the system. CountDownLatch latch = new CountDownLatch(1); - HandlerCaller.SomeArgs sargs = new HandlerCaller.SomeArgs(); + SomeArgs sargs = SomeArgs.obtain(); sargs.arg1 = fd; sargs.arg2 = fout; sargs.arg3 = args; @@ -476,6 +519,18 @@ public final class InputMethodManager { }; final InputConnection mDummyInputConnection = new BaseInputConnection(this, false); + + final IInputMethodCallback mInputMethodCallback = new IInputMethodCallback.Stub() { + @Override + public void finishedEvent(int seq, boolean handled) { + InputMethodManager.this.finishedEvent(seq, handled); + } + + @Override + public void sessionCreated(IInputMethodSession session) { + // Stub -- not for use in the client. + } + }; InputMethodManager(IInputMethodManager service, Looper looper) { mService = service; @@ -1105,6 +1160,7 @@ public final class InputMethodManager { if (res.id != null) { mBindSequence = res.sequence; mCurMethod = res.method; + mCurId = res.id; } else if (mCurMethod == null) { // This means there is no input method available. if (DEBUG) Log.v(TAG, "ABORT input: no input method!"); @@ -1194,20 +1250,16 @@ public final class InputMethodManager { } } - private void checkFocus(boolean forceNewFocus) { - if (checkFocusNoStartInput(forceNewFocus)) { - startInputInner(null, 0, 0, 0); - } - } - /** * @hide */ public void checkFocus() { - checkFocus(false); + if (checkFocusNoStartInput(false, true)) { + startInputInner(null, 0, 0, 0); + } } - private boolean checkFocusNoStartInput(boolean forceNewFocus) { + private boolean checkFocusNoStartInput(boolean forceNewFocus, boolean finishComposingText) { // This is called a lot, so short-circuit before locking. if (mServedView == mNextServedView && !forceNewFocus) { return false; @@ -1241,7 +1293,7 @@ public final class InputMethodManager { mServedConnecting = true; } - if (ic != null) { + if (finishComposingText && ic != null) { ic.finishComposingText(); } @@ -1286,7 +1338,7 @@ public final class InputMethodManager { controlFlags |= CONTROL_WINDOW_FIRST; } - if (checkFocusNoStartInput(forceNewFocus)) { + if (checkFocusNoStartInput(forceNewFocus, true)) { // We need to restart input on the current focus view. This // should be done in conjunction with telling the system service // about the window gaining focus, to help make the transition @@ -1511,76 +1563,184 @@ public final class InputMethodManager { * @hide */ public void dispatchKeyEvent(Context context, int seq, KeyEvent key, - IInputMethodCallback callback) { + FinishedEventCallback callback) { + boolean handled = false; synchronized (mH) { if (DEBUG) Log.d(TAG, "dispatchKeyEvent"); - - if (mCurMethod == null) { - try { - callback.finishedEvent(seq, false); - } catch (RemoteException e) { + + if (mCurMethod != null) { + if (key.getAction() == KeyEvent.ACTION_DOWN + && key.getKeyCode() == KeyEvent.KEYCODE_SYM) { + showInputMethodPickerLocked(); + handled = true; + } else { + try { + if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod); + final long startTime = SystemClock.uptimeMillis(); + enqueuePendingEventLocked(startTime, seq, mCurId, callback); + mCurMethod.dispatchKeyEvent(seq, key, mInputMethodCallback); + return; + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e); + } } - return; } - - if (key.getAction() == KeyEvent.ACTION_DOWN - && key.getKeyCode() == KeyEvent.KEYCODE_SYM) { - showInputMethodPicker(); + } + + callback.finishedEvent(seq, handled); + } + + /** + * @hide + */ + public void dispatchTrackballEvent(Context context, int seq, MotionEvent motion, + FinishedEventCallback callback) { + synchronized (mH) { + if (DEBUG) Log.d(TAG, "dispatchTrackballEvent"); + + if (mCurMethod != null && mCurrentTextBoxAttribute != null) { try { - callback.finishedEvent(seq, true); + if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod); + final long startTime = SystemClock.uptimeMillis(); + enqueuePendingEventLocked(startTime, seq, mCurId, callback); + mCurMethod.dispatchTrackballEvent(seq, motion, mInputMethodCallback); + return; } catch (RemoteException e) { - } - return; - } - try { - if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod); - mCurMethod.dispatchKeyEvent(seq, key, callback); - } catch (RemoteException e) { - Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e); - try { - callback.finishedEvent(seq, false); - } catch (RemoteException ex) { + Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e); } } } + + callback.finishedEvent(seq, false); } /** * @hide */ - void dispatchTrackballEvent(Context context, int seq, MotionEvent motion, - IInputMethodCallback callback) { + public void dispatchGenericMotionEvent(Context context, int seq, MotionEvent motion, + FinishedEventCallback callback) { synchronized (mH) { - if (DEBUG) Log.d(TAG, "dispatchTrackballEvent"); - - if (mCurMethod == null || mCurrentTextBoxAttribute == null) { + if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent"); + + if (mCurMethod != null && mCurrentTextBoxAttribute != null) { try { - callback.finishedEvent(seq, false); + if (DEBUG) Log.v(TAG, "DISPATCH GENERIC MOTION: " + mCurMethod); + final long startTime = SystemClock.uptimeMillis(); + enqueuePendingEventLocked(startTime, seq, mCurId, callback); + mCurMethod.dispatchGenericMotionEvent(seq, motion, mInputMethodCallback); + return; } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId + " dropping generic motion: " + motion, e); } - return; } - - try { - if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod); - mCurMethod.dispatchTrackballEvent(seq, motion, callback); - } catch (RemoteException e) { - Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e); - try { - callback.finishedEvent(seq, false); - } catch (RemoteException ex) { - } + } + + callback.finishedEvent(seq, false); + } + + void finishedEvent(int seq, boolean handled) { + final FinishedEventCallback callback; + synchronized (mH) { + PendingEvent p = dequeuePendingEventLocked(seq); + if (p == null) { + return; // spurious, event already finished or timed out } + mH.removeMessages(MSG_EVENT_TIMEOUT, p); + callback = p.mCallback; + recyclePendingEventLocked(p); } + callback.finishedEvent(seq, handled); } - public void showInputMethodPicker() { + void timeoutEvent(int seq) { + final FinishedEventCallback callback; synchronized (mH) { - try { - mService.showInputMethodPickerFromClient(mClient); - } catch (RemoteException e) { - Log.w(TAG, "IME died: " + mCurId, e); + PendingEvent p = dequeuePendingEventLocked(seq); + if (p == null) { + return; // spurious, event already finished or timed out } + long delay = SystemClock.uptimeMillis() - p.mStartTime; + Log.w(TAG, "Timeout waiting for IME to handle input event after " + + delay + "ms: " + p.mInputMethodId); + callback = p.mCallback; + recyclePendingEventLocked(p); + } + callback.finishedEvent(seq, false); + } + + private void enqueuePendingEventLocked( + long startTime, int seq, String inputMethodId, FinishedEventCallback callback) { + PendingEvent p = obtainPendingEventLocked(startTime, seq, inputMethodId, callback); + p.mNext = mFirstPendingEvent; + mFirstPendingEvent = p; + + Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, seq, 0, p); + msg.setAsynchronous(true); + mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT); + } + + private PendingEvent dequeuePendingEventLocked(int seq) { + PendingEvent p = mFirstPendingEvent; + if (p == null) { + return null; + } + if (p.mSeq == seq) { + mFirstPendingEvent = p.mNext; + } else { + PendingEvent prev; + do { + prev = p; + p = p.mNext; + if (p == null) { + return null; + } + } while (p.mSeq != seq); + prev.mNext = p.mNext; + } + p.mNext = null; + return p; + } + + private PendingEvent obtainPendingEventLocked( + long startTime, int seq, String inputMethodId, FinishedEventCallback callback) { + PendingEvent p = mPendingEventPool; + if (p != null) { + mPendingEventPoolSize -= 1; + mPendingEventPool = p.mNext; + p.mNext = null; + } else { + p = new PendingEvent(); + } + + p.mStartTime = startTime; + p.mSeq = seq; + p.mInputMethodId = inputMethodId; + p.mCallback = callback; + return p; + } + + private void recyclePendingEventLocked(PendingEvent p) { + p.mInputMethodId = null; + p.mCallback = null; + + if (mPendingEventPoolSize < MAX_PENDING_EVENT_POOL_SIZE) { + mPendingEventPoolSize += 1; + p.mNext = mPendingEventPool; + mPendingEventPool = p; + } + } + + public void showInputMethodPicker() { + synchronized (mH) { + showInputMethodPickerLocked(); + } + } + + private void showInputMethodPickerLocked() { + try { + mService.showInputMethodPickerFromClient(mClient); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); } } @@ -1773,4 +1933,22 @@ public final class InputMethodManager { + " mCursorCandStart=" + mCursorCandStart + " mCursorCandEnd=" + mCursorCandEnd); } + + /** + * Callback that is invoked when an input event that was dispatched to + * the IME has been finished. + * @hide + */ + public interface FinishedEventCallback { + public void finishedEvent(int seq, boolean handled); + } + + private static final class PendingEvent { + public PendingEvent mNext; + + public long mStartTime; + public int mSeq; + public String mInputMethodId; + public FinishedEventCallback mCallback; + } } diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java index ea6f5ee..6386299 100644 --- a/core/java/android/view/inputmethod/InputMethodSession.java +++ b/core/java/android/view/inputmethod/InputMethodSession.java @@ -138,6 +138,21 @@ public interface InputMethodSession { public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback); /** + * This method is called when there is a generic motion event. + * + * <p> + * If the input method wants to handle this event, return true, otherwise + * return false and the caller (i.e. the application) will handle the event. + * + * @param event The motion event. + * + * @return Whether the input method wants to handle this event. + * + * @see android.view.MotionEvent + */ + public void dispatchGenericMotionEvent(int seq, MotionEvent event, EventCallback callback); + + /** * Process a private command sent from the application to the input method. * This can be used to provide domain-specific features that are * only known between certain input methods and their clients. diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index b7c94a3..7895e6f 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -55,13 +55,14 @@ public final class InputMethodSubtype implements Parcelable { private final int mSubtypeHashCode; private final int mSubtypeIconResId; private final int mSubtypeNameResId; + private final int mSubtypeId; private final String mSubtypeLocale; private final String mSubtypeMode; private final String mSubtypeExtraValue; private volatile HashMap<String, String> mExtraValueHashMapCache; /** - * Constructor. + * Constructor with no subtype ID specified, overridesImplicitlyEnabledSubtype not specified. * @param nameId Resource ID of the subtype name string. The string resource may have exactly * one %s in it. If there is, the %s part will be replaced with the locale's display name by * the formatter. Please refer to {@link #getDisplayName} for details. @@ -87,7 +88,7 @@ public final class InputMethodSubtype implements Parcelable { } /** - * Constructor. + * Constructor with no subtype ID specified. * @param nameId Resource ID of the subtype name string. The string resource may have exactly * one %s in it. If there is, the %s part will be replaced with the locale's display name by * the formatter. Please refer to {@link #getDisplayName} for details. @@ -112,6 +113,41 @@ public final class InputMethodSubtype implements Parcelable { */ public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) { + this(nameId, iconId, locale, mode, extraValue, isAuxiliary, + overridesImplicitlyEnabledSubtype, 0); + } + + /** + * Constructor. + * @param nameId Resource ID of the subtype name string. The string resource may have exactly + * one %s in it. If there is, the %s part will be replaced with the locale's display name by + * the formatter. Please refer to {@link #getDisplayName} for details. + * @param iconId Resource ID of the subtype icon drawable. + * @param locale The locale supported by the subtype + * @param mode The mode supported by the subtype + * @param extraValue The extra value of the subtype. This string is free-form, but the API + * supplies tools to deal with a key-value comma-separated list; see + * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. + * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary + * subtype will not be shown in the list of enabled IMEs for choosing the current IME in + * the Settings even when this subtype is enabled. Please note that this subtype will still + * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch + * to this subtype while an IME is shown. The framework will never switch the current IME to + * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. + * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as + * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). + * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default + * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this + * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler. + * Having an "automatic" subtype is an example use of this flag. + * @param id The unique ID for the subtype. The input method framework keeps track of enabled + * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if + * other attributes are different. If the ID is unspecified or 0, + * Arrays.hashCode(new Object[] {locale, mode, extraValue, + * isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead. + */ + public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, + boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) { mSubtypeNameResId = nameId; mSubtypeIconResId = iconId; mSubtypeLocale = locale != null ? locale : ""; @@ -119,8 +155,11 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeExtraValue = extraValue != null ? extraValue : ""; mIsAuxiliary = isAuxiliary; mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; - mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue, - mIsAuxiliary, mOverridesImplicitlyEnabledSubtype); + // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype, + // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0. + mSubtypeHashCode = id != 0 ? id : hashCodeInternal(mSubtypeLocale, mSubtypeMode, + mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype); + mSubtypeId = id; } InputMethodSubtype(Parcel source) { @@ -135,8 +174,8 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeExtraValue = s != null ? s : ""; mIsAuxiliary = (source.readInt() == 1); mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1); - mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue, - mIsAuxiliary, mOverridesImplicitlyEnabledSubtype); + mSubtypeHashCode = source.readInt(); + mSubtypeId = source.readInt(); } /** @@ -288,6 +327,9 @@ public final class InputMethodSubtype implements Parcelable { public boolean equals(Object o) { if (o instanceof InputMethodSubtype) { InputMethodSubtype subtype = (InputMethodSubtype) o; + if (subtype.mSubtypeId != 0 || mSubtypeId != 0) { + return (subtype.hashCode() == hashCode()); + } return (subtype.hashCode() == hashCode()) && (subtype.getNameResId() == getNameResId()) && (subtype.getMode().equals(getMode())) @@ -313,6 +355,8 @@ public final class InputMethodSubtype implements Parcelable { dest.writeString(mSubtypeExtraValue); dest.writeInt(mIsAuxiliary ? 1 : 0); dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0); + dest.writeInt(mSubtypeHashCode); + dest.writeInt(mSubtypeId); } public static final Parcelable.Creator<InputMethodSubtype> CREATOR diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java index 7dfb5bb..a51a8f6 100644 --- a/core/java/android/webkit/AccessibilityInjector.java +++ b/core/java/android/webkit/AccessibilityInjector.java @@ -34,6 +34,7 @@ import org.json.JSONObject; import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -53,7 +54,7 @@ class AccessibilityInjector { private final WebView mWebView; // The Java objects that are exposed to JavaScript. - private TextToSpeech mTextToSpeech; + private TextToSpeechWrapper mTextToSpeech; private CallbackHandler mCallback; // Lazily loaded helper objects. @@ -279,6 +280,7 @@ class AccessibilityInjector { } if (!shouldInjectJavaScript(url)) { + mAccessibilityScriptInjected = false; toggleFallbackAccessibilityInjector(true); return; } @@ -292,6 +294,23 @@ class AccessibilityInjector { } /** + * Adjusts the accessibility injection state to reflect changes in the + * JavaScript enabled state. + * + * @param enabled Whether JavaScript is enabled. + */ + public void updateJavaScriptEnabled(boolean enabled) { + if (enabled) { + addAccessibilityApisIfNecessary(); + } else { + removeAccessibilityApisIfNecessary(); + } + + // We have to reload the page after adding or removing APIs. + mWebView.reload(); + } + + /** * Toggles the non-JavaScript method for handling accessibility. * * @param enabled {@code true} to enable the non-JavaScript method, or @@ -349,10 +368,7 @@ class AccessibilityInjector { if (mTextToSpeech != null) { return; } - - final String pkgName = mContext.getPackageName(); - - mTextToSpeech = new TextToSpeech(mContext, null, null, pkgName + ".**webview**", true); + mTextToSpeech = new TextToSpeechWrapper(mContext); mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE); } @@ -508,6 +524,41 @@ class AccessibilityInjector { } /** + * Used to protect the TextToSpeech class, only exposing the methods we want to expose. + */ + private static class TextToSpeechWrapper { + private TextToSpeech mTextToSpeech; + + public TextToSpeechWrapper(Context context) { + final String pkgName = context.getPackageName(); + mTextToSpeech = new TextToSpeech(context, null, null, pkgName + ".**webview**", true); + } + + @JavascriptInterface + @SuppressWarnings("unused") + public boolean isSpeaking() { + return mTextToSpeech.isSpeaking(); + } + + @JavascriptInterface + @SuppressWarnings("unused") + public int speak(String text, int queueMode, HashMap<String, String> params) { + return mTextToSpeech.speak(text, queueMode, params); + } + + @JavascriptInterface + @SuppressWarnings("unused") + public int stop() { + return mTextToSpeech.stop(); + } + + @SuppressWarnings("unused") + protected void shutdown() { + mTextToSpeech.shutdown(); + } + } + + /** * Exposes result interface to JavaScript. */ private static class CallbackHandler { @@ -603,6 +654,7 @@ class AccessibilityInjector { * @param id The result id of the request as a {@link String}. * @param result The result of the request as a {@link String}. */ + @JavascriptInterface @SuppressWarnings("unused") public void onResult(String id, String result) { final long resultId; diff --git a/core/java/android/webkit/BrowserDownloadListener.java b/core/java/android/webkit/BrowserDownloadListener.java new file mode 100644 index 0000000..724cc62 --- /dev/null +++ b/core/java/android/webkit/BrowserDownloadListener.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +/** + * An abstract download listener that allows passing extra information as + * part of onDownloadStart callback. + * @hide + */ +public abstract class BrowserDownloadListener implements DownloadListener { + + /** + * Notify the host application that a file should be downloaded + * @param url The full url to the content that should be downloaded + * @param userAgent the user agent to be used for the download. + * @param contentDisposition Content-disposition http header, if + * present. + * @param mimetype The mimetype of the content reported by the server + * @param referer The referer associated with this url + * @param contentLength The file size reported by the server + */ + public abstract void onDownloadStart(String url, String userAgent, + String contentDisposition, String mimetype, String referer, + long contentLength); + + + /** + * Notify the host application that a file should be downloaded + * @param url The full url to the content that should be downloaded + * @param userAgent the user agent to be used for the download. + * @param contentDisposition Content-disposition http header, if + * present. + * @param mimetype The mimetype of the content reported by the server + * @param contentLength The file size reported by the server + */ + @Override + public void onDownloadStart(String url, String userAgent, + String contentDisposition, String mimetype, long contentLength) { + + onDownloadStart(url, userAgent, contentDisposition, mimetype, null, + contentLength); + } +} diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 5108990..4dbca23 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -74,6 +74,7 @@ class BrowserFrame extends Handler { private final CallbackProxy mCallbackProxy; private final WebSettingsClassic mSettings; private final Context mContext; + private final WebViewDatabaseClassic mDatabase; private final WebViewCore mWebViewCore; /* package */ boolean mLoadInitFromJava; private int mLoadType; @@ -88,16 +89,24 @@ class BrowserFrame extends Handler { // Is this frame the main frame? private boolean mIsMainFrame; + // Javascript interface object + private class JSObject { + Object object; + boolean requireAnnotation; + + public JSObject(Object object, boolean requireAnnotation) { + this.object = object; + this.requireAnnotation = requireAnnotation; + } + } + // Attached Javascript interfaces - private Map<String, Object> mJavaScriptObjects; + private Map<String, JSObject> mJavaScriptObjects; private Set<Object> mRemovedJavaScriptObjects; // Key store handler when Chromium HTTP stack is used. private KeyStoreHandler mKeyStoreHandler = null; - // Implementation of the searchbox API. - private final SearchBoxImpl mSearchBox; - // message ids // a message posted when a frame loading is completed static final int FRAME_COMPLETED = 1001; @@ -233,20 +242,16 @@ class BrowserFrame extends Handler { } sConfigCallback.addHandler(this); - mJavaScriptObjects = javascriptInterfaces; - if (mJavaScriptObjects == null) { - mJavaScriptObjects = new HashMap<String, Object>(); - } + mJavaScriptObjects = new HashMap<String, JSObject>(); + addJavaScriptObjects(javascriptInterfaces); mRemovedJavaScriptObjects = new HashSet<Object>(); mSettings = settings; mContext = context; mCallbackProxy = proxy; + mDatabase = WebViewDatabaseClassic.getInstance(appContext); mWebViewCore = w; - mSearchBox = new SearchBoxImpl(mWebViewCore, mCallbackProxy); - mJavaScriptObjects.put(SearchBoxImpl.JS_INTERFACE_NAME, mSearchBox); - AssetManager am = context.getAssets(); nativeCreateFrame(w, am, proxy.getBackForwardList()); @@ -424,8 +429,7 @@ class BrowserFrame extends Handler { if (h != null) { String url = WebTextView.urlForAutoCompleteData(h.getUrl()); if (url != null) { - WebViewDatabaseClassic.getInstance(mContext).setFormData( - url, data); + mDatabase.setFormData(url, data); } } } @@ -497,9 +501,8 @@ class BrowserFrame extends Handler { if (item != null) { WebAddress uri = new WebAddress(item.getUrl()); String schemePlusHost = uri.getScheme() + uri.getHost(); - String[] up = - WebViewDatabaseClassic.getInstance(mContext) - .getUsernamePassword(schemePlusHost); + String[] up = mDatabase.getUsernamePassword( + schemePlusHost); if (up != null && up[0] != null) { setUsernamePassword(up[0], up[1]); } @@ -591,15 +594,34 @@ class BrowserFrame extends Handler { Iterator<String> iter = mJavaScriptObjects.keySet().iterator(); while (iter.hasNext()) { String interfaceName = iter.next(); - Object object = mJavaScriptObjects.get(interfaceName); - if (object != null) { + JSObject jsobject = mJavaScriptObjects.get(interfaceName); + if (jsobject != null && jsobject.object != null) { nativeAddJavascriptInterface(nativeFramePointer, - mJavaScriptObjects.get(interfaceName), interfaceName); + jsobject.object, interfaceName, jsobject.requireAnnotation); } } mRemovedJavaScriptObjects.clear(); + } - stringByEvaluatingJavaScriptFromString(SearchBoxImpl.JS_BRIDGE); + /* + * Add javascript objects to the internal list of objects. The default behavior + * is to allow access to inherited methods (no annotation needed). This is only + * used when js objects are passed through a constructor (via a hidden constructor). + * + * @TODO change the default behavior to be compatible with the public addjavascriptinterface + */ + private void addJavaScriptObjects(Map<String, Object> javascriptInterfaces) { + + // TODO in a separate CL provide logic to enable annotations for API level JB_MR1 and above. + if (javascriptInterfaces == null) return; + Iterator<String> iter = javascriptInterfaces.keySet().iterator(); + while (iter.hasNext()) { + String interfaceName = iter.next(); + Object object = javascriptInterfaces.get(interfaceName); + if (object != null) { + mJavaScriptObjects.put(interfaceName, new JSObject(object, false)); + } + } } /** @@ -619,11 +641,11 @@ class BrowserFrame extends Handler { } } - public void addJavascriptInterface(Object obj, String interfaceName) { + public void addJavascriptInterface(Object obj, String interfaceName, + boolean requireAnnotation) { assert obj != null; removeJavascriptInterface(interfaceName); - - mJavaScriptObjects.put(interfaceName, obj); + mJavaScriptObjects.put(interfaceName, new JSObject(obj, requireAnnotation)); } public void removeJavascriptInterface(String interfaceName) { @@ -800,10 +822,10 @@ class BrowserFrame extends Handler { // the post data (there could be another form on the // page and that was posted instead. String postString = new String(postData); - WebViewDatabaseClassic db = WebViewDatabaseClassic.getInstance(mContext); if (postString.contains(URLEncoder.encode(username)) && postString.contains(URLEncoder.encode(password))) { - String[] saved = db.getUsernamePassword(schemePlusHost); + String[] saved = mDatabase.getUsernamePassword( + schemePlusHost); if (saved != null) { // null username implies that user has chosen not to // save password @@ -811,8 +833,7 @@ class BrowserFrame extends Handler { // non-null username implies that user has // chosen to save password, so update the // recorded password - db.setUsernamePassword(schemePlusHost, username, - password); + mDatabase.setUsernamePassword(schemePlusHost, username, password); } } else { // CallbackProxy will handle creating the resume @@ -1004,7 +1025,7 @@ class BrowserFrame extends Handler { } private float density() { - return mContext.getResources().getDisplayMetrics().density; + return WebViewCore.getFixedDisplayDensity(mContext); } /** @@ -1138,7 +1159,7 @@ class BrowserFrame extends Handler { * DownloadListener. */ private void downloadStart(String url, String userAgent, - String contentDisposition, String mimeType, long contentLength) { + String contentDisposition, String mimeType, String referer, long contentLength) { // This will only work if the url ends with the filename if (mimeType.isEmpty()) { try { @@ -1158,7 +1179,7 @@ class BrowserFrame extends Handler { mKeyStoreHandler = new KeyStoreHandler(mimeType); } else { mCallbackProxy.onDownloadStart(url, userAgent, - contentDisposition, mimeType, contentLength); + contentDisposition, mimeType, referer, contentLength); } } @@ -1191,10 +1212,6 @@ class BrowserFrame extends Handler { } } - /*package*/ SearchBox getSearchBox() { - return mSearchBox; - } - /** * Called by JNI when processing the X-Auto-Login header. */ @@ -1249,7 +1266,7 @@ class BrowserFrame extends Handler { * Add a javascript interface to the main frame. */ private native void nativeAddJavascriptInterface(int nativeFramePointer, - Object obj, String interfaceName); + Object obj, String interfaceName, boolean requireAnnotation); public native void clearCache(); diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index f0e6ff0..52f41e6 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -32,6 +32,7 @@ import java.util.Map; /** * Manages the HTTP cache used by an application's {@link WebView} instances. * @deprecated Access to the HTTP cache will be removed in a future release. + * @hide Since {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ // The class CacheManager provides the persistent cache of content that is // received over the network. The component handles parsing of HTTP headers and diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 2d9f60d..a326da2 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -71,7 +71,7 @@ class CallbackProxy extends Handler { // Start with 100 to indicate it is not in load for the empty page. private volatile int mLatestProgress = 100; // Back/Forward list - private final WebBackForwardList mBackForwardList; + private final WebBackForwardListClassic mBackForwardList; // Back/Forward list client private volatile WebBackForwardListClient mWebBackForwardListClient; // Used to call startActivity during url override. @@ -117,12 +117,8 @@ class CallbackProxy extends Handler { private static final int ADD_HISTORY_ITEM = 135; private static final int HISTORY_INDEX_CHANGED = 136; private static final int AUTH_CREDENTIALS = 137; - private static final int SET_INSTALLABLE_WEBAPP = 138; - private static final int NOTIFY_SEARCHBOX_LISTENERS = 139; private static final int AUTO_LOGIN = 140; private static final int CLIENT_CERT_REQUEST = 141; - private static final int SEARCHBOX_IS_SUPPORTED_CALLBACK = 142; - private static final int SEARCHBOX_DISPATCH_COMPLETE_CALLBACK = 143; private static final int PROCEEDED_AFTER_SSL_ERROR = 144; // Message triggered by the client to resume execution @@ -188,7 +184,7 @@ class CallbackProxy extends Handler { // Used to start a default activity. mContext = context; mWebView = w; - mBackForwardList = new WebBackForwardList(this); + mBackForwardList = new WebBackForwardListClassic(this); } protected synchronized void blockMessages() { @@ -249,7 +245,7 @@ class CallbackProxy extends Handler { * Get the Back/Forward list to return to the user or to update the cached * history list. */ - public WebBackForwardList getBackForwardList() { + public WebBackForwardListClassic getBackForwardList() { return mBackForwardList; } @@ -403,17 +399,18 @@ class CallbackProxy extends Handler { break; case PROCEEDED_AFTER_SSL_ERROR: - if (mWebViewClient != null) { - mWebViewClient.onProceededAfterSslError(mWebView.getWebView(), + if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) { + ((WebViewClientClassicExt) mWebViewClient).onProceededAfterSslError( + mWebView.getWebView(), (SslError) msg.obj); } break; case CLIENT_CERT_REQUEST: - if (mWebViewClient != null) { - HashMap<String, Object> map = - (HashMap<String, Object>) msg.obj; - mWebViewClient.onReceivedClientCertRequest(mWebView.getWebView(), + if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) { + HashMap<String, Object> map = (HashMap<String, Object>) msg.obj; + ((WebViewClientClassicExt) mWebViewClient).onReceivedClientCertRequest( + mWebView.getWebView(), (ClientCertRequestHandler) map.get("handler"), (String) map.get("host_and_port")); } @@ -452,10 +449,16 @@ class CallbackProxy extends Handler { String contentDisposition = msg.getData().getString("contentDisposition"); String mimetype = msg.getData().getString("mimetype"); + String referer = msg.getData().getString("referer"); Long contentLength = msg.getData().getLong("contentLength"); - mDownloadListener.onDownloadStart(url, userAgent, - contentDisposition, mimetype, contentLength); + if (mDownloadListener instanceof BrowserDownloadListener) { + ((BrowserDownloadListener) mDownloadListener).onDownloadStart(url, + userAgent, contentDisposition, mimetype, referer, contentLength); + } else { + mDownloadListener.onDownloadStart(url, userAgent, + contentDisposition, mimetype, contentLength); + } } break; @@ -736,6 +739,14 @@ class CallbackProxy extends Handler { res.cancel(); } }) + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + @Override + public void onCancel( + DialogInterface dialog) { + res.cancel(); + } + }) .show(); } receiver.setReady(); @@ -857,19 +868,6 @@ class CallbackProxy extends Handler { host, realm, username, password); break; } - case SET_INSTALLABLE_WEBAPP: - if (mWebChromeClient != null) { - mWebChromeClient.setInstallableWebApp(); - } - break; - case NOTIFY_SEARCHBOX_LISTENERS: { - SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox(); - - @SuppressWarnings("unchecked") - List<String> suggestions = (List<String>) msg.obj; - searchBox.handleSuggestions(msg.getData().getString("query"), suggestions); - break; - } case AUTO_LOGIN: { if (mWebViewClient != null) { String realm = msg.getData().getString("realm"); @@ -880,19 +878,6 @@ class CallbackProxy extends Handler { } break; } - case SEARCHBOX_IS_SUPPORTED_CALLBACK: { - SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox(); - Boolean supported = (Boolean) msg.obj; - searchBox.handleIsSupportedCallback(supported); - break; - } - case SEARCHBOX_DISPATCH_COMPLETE_CALLBACK: { - SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox(); - Boolean success = (Boolean) msg.obj; - searchBox.handleDispatchCompleteCallback(msg.getData().getString("function"), - msg.getData().getInt("id"), success); - break; - } } } @@ -1081,7 +1066,7 @@ class CallbackProxy extends Handler { } public void onProceededAfterSslError(SslError error) { - if (mWebViewClient == null) { + if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) { return; } Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR); @@ -1092,7 +1077,7 @@ class CallbackProxy extends Handler { public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) { // Do an unsynchronized quick check to avoid posting if no callback has // been set. - if (mWebViewClient == null) { + if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) { handler.cancel(); return; } @@ -1176,7 +1161,8 @@ class CallbackProxy extends Handler { * return false. */ public boolean onDownloadStart(String url, String userAgent, - String contentDisposition, String mimetype, long contentLength) { + String contentDisposition, String mimetype, String referer, + long contentLength) { // Do an unsynchronized quick check to avoid posting if no callback has // been set. if (mDownloadListener == null) { @@ -1189,6 +1175,7 @@ class CallbackProxy extends Handler { bundle.putString("url", url); bundle.putString("userAgent", userAgent); bundle.putString("mimetype", mimetype); + bundle.putString("referer", referer); bundle.putLong("contentLength", contentLength); bundle.putString("contentDisposition", contentDisposition); sendMessage(msg); @@ -1301,7 +1288,7 @@ class CallbackProxy extends Handler { public void onReceivedIcon(Bitmap icon) { // The current item might be null if the icon was already stored in the // database and this is a new WebView. - WebHistoryItem i = mBackForwardList.getCurrentItem(); + WebHistoryItemClassic i = mBackForwardList.getCurrentItem(); if (i != null) { i.setFavicon(icon); } @@ -1316,7 +1303,7 @@ class CallbackProxy extends Handler { /* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) { // We should have a current item but we do not want to crash so check // for null. - WebHistoryItem i = mBackForwardList.getCurrentItem(); + WebHistoryItemClassic i = mBackForwardList.getCurrentItem(); if (i != null) { i.setTouchIconUrl(url, precomposed); } @@ -1608,13 +1595,6 @@ class CallbackProxy extends Handler { sendMessage(msg); } - void setInstallableWebApp() { - if (mWebChromeClient == null) { - return; - } - sendMessage(obtainMessage(SET_INSTALLABLE_WEBAPP)); - } - boolean canShowAlertDialog() { // We can only display the alert dialog if mContext is // an Activity context. @@ -1625,29 +1605,6 @@ class CallbackProxy extends Handler { return mContext instanceof Activity; } - void onSearchboxSuggestionsReceived(String query, List<String> suggestions) { - Message msg = obtainMessage(NOTIFY_SEARCHBOX_LISTENERS); - msg.obj = suggestions; - msg.getData().putString("query", query); - - sendMessage(msg); - } - - void onIsSupportedCallback(boolean isSupported) { - Message msg = obtainMessage(SEARCHBOX_IS_SUPPORTED_CALLBACK); - msg.obj = Boolean.valueOf(isSupported); - sendMessage(msg); - } - - void onSearchboxDispatchCompleteCallback(String function, int id, boolean success) { - Message msg = obtainMessage(SEARCHBOX_DISPATCH_COMPLETE_CALLBACK); - msg.obj = Boolean.valueOf(success); - msg.getData().putString("function", function); - msg.getData().putInt("id", id); - - sendMessage(msg); - } - private synchronized void sendMessageToUiThreadSync(Message msg) { sendMessage(msg); WebCoreThreadWatchdog.pause(); diff --git a/core/java/android/webkit/CertTool.java b/core/java/android/webkit/CertTool.java index a2325c3..e4d09a9 100644 --- a/core/java/android/webkit/CertTool.java +++ b/core/java/android/webkit/CertTool.java @@ -16,6 +16,7 @@ package android.webkit; +import com.android.org.bouncycastle.asn1.ASN1Encoding; import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier; import com.android.org.bouncycastle.jce.netscape.NetscapeCertRequest; @@ -57,7 +58,7 @@ final class CertTool { NetscapeCertRequest request = new NetscapeCertRequest(challenge, MD5_WITH_RSA, pair.getPublic()); request.sign(pair.getPrivate()); - byte[] signed = request.toASN1Object().getDEREncoded(); + byte[] signed = request.toASN1Primitive().getEncoded(ASN1Encoding.DER); Credentials.getInstance().install(context, pair); return new String(Base64.encode(signed)); diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 30c713e..2b75d83 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -37,8 +37,8 @@ public class CookieManager { /** * Gets the singleton CookieManager instance. If this method is used * before the application instantiates a {@link WebView} instance, - * {@link CookieSyncManager#createInstance(Context)} must be called - * first. + * {@link CookieSyncManager#createInstance CookieSyncManager.createInstance(Context)} + * must be called first. * * @return the singleton CookieManager instance */ diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java index 4e99335..276bcae 100644 --- a/core/java/android/webkit/CookieSyncManager.java +++ b/core/java/android/webkit/CookieSyncManager.java @@ -86,10 +86,8 @@ public final class CookieSyncManager extends WebSyncManager { throw new IllegalArgumentException("Invalid context argument"); } - JniUtil.setContext(context); - Context appContext = context.getApplicationContext(); if (sRef == null) { - sRef = new CookieSyncManager(appContext); + sRef = new CookieSyncManager(context); } return sRef; } diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java index 0e8ad7e..82c13ae 100644 --- a/core/java/android/webkit/DateSorter.java +++ b/core/java/android/webkit/DateSorter.java @@ -21,6 +21,9 @@ import android.content.res.Resources; import java.util.Calendar; import java.util.Date; +import java.util.Locale; + +import libcore.icu.LocaleData; /** * Sorts dates into the following groups: @@ -63,8 +66,13 @@ public class DateSorter { mBins[3] = c.getTimeInMillis(); // One month ago // build labels - mLabels[0] = context.getText(com.android.internal.R.string.today).toString(); - mLabels[1] = context.getText(com.android.internal.R.string.yesterday).toString(); + Locale locale = resources.getConfiguration().locale; + if (locale == null) { + locale = Locale.getDefault(); + } + LocaleData localeData = LocaleData.get(locale); + mLabels[0] = localeData.today; + mLabels[1] = localeData.yesterday; int resId = com.android.internal.R.plurals.last_num_days; String format = resources.getQuantityString(resId, NUM_DAYS_AGO); diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java index 2e8656c..a4d240d 100755 --- a/core/java/android/webkit/DeviceOrientationService.java +++ b/core/java/android/webkit/DeviceOrientationService.java @@ -123,7 +123,7 @@ final class DeviceOrientationService implements SensorEventListener { // The angles are in radians float[] rotationAngles = new float[3]; SensorManager.getOrientation(deviceRotationMatrix, rotationAngles); - double alpha = Math.toDegrees(-rotationAngles[0]) - 90.0; + double alpha = Math.toDegrees(-rotationAngles[0]); while (alpha < 0.0) { alpha += 360.0; } // [0, 360) double beta = Math.toDegrees(-rotationAngles[1]); while (beta < -180.0) { beta += 360.0; } // [-180, 180) diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 33eaad6..9b93805 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package android.webkit; @@ -75,9 +90,10 @@ public class HTML5VideoFullScreen extends HTML5VideoView // ratio is correct. private int mVideoWidth; private int mVideoHeight; - + private boolean mPlayingWhenDestroyed = false; SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { + @Override public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { @@ -91,6 +107,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView } } + @Override public void surfaceCreated(SurfaceHolder holder) { mSurfaceHolder = holder; @@ -99,14 +116,14 @@ public class HTML5VideoFullScreen extends HTML5VideoView prepareForFullScreen(); } + @Override public void surfaceDestroyed(SurfaceHolder holder) { - // After we return from this we can't use the surface any more. - // The current Video View will be destroy when we play a new video. + mPlayingWhenDestroyed = mPlayer.isPlaying(); pauseAndDispatch(mProxy); - // TODO: handle full screen->inline mode transition without a reload. - mPlayer.release(); - mPlayer = null; + // We need to set the display to null before switching into inline + // mode to avoid error. + mPlayer.setDisplay(null); mSurfaceHolder = null; if (mMediaController != null) { mMediaController.hide(); @@ -194,18 +211,6 @@ public class HTML5VideoFullScreen extends HTML5VideoView mCanPause = mCanSeekBack = mCanSeekForward = true; } - if (mProgressView != null) { - mProgressView.setVisibility(View.GONE); - } - - mVideoWidth = mp.getVideoWidth(); - mVideoHeight = mp.getVideoHeight(); - // This will trigger the onMeasure to get the display size right. - mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); - // Call into the native to ask for the state, if still in play mode, - // this will trigger the video to play. - mProxy.dispatchOnRestoreState(); - if (getStartWhenPrepared()) { mPlayer.start(); // Clear the flag. @@ -219,20 +224,31 @@ public class HTML5VideoFullScreen extends HTML5VideoView mMediaController.setEnabled(true); mMediaController.show(); } + + if (mProgressView != null) { + mProgressView.setVisibility(View.GONE); + } + + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + // This will trigger the onMeasure to get the display size right. + mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); + } + @Override public boolean fullScreenExited() { return (mLayout == null); } private final WebChromeClient.CustomViewCallback mCallback = new WebChromeClient.CustomViewCallback() { + @Override public void onCustomViewHidden() { // It listens to SurfaceHolder.Callback.SurfaceDestroyed event // which happens when the video view is detached from its parent // view. This happens in the WebChromeClient before this method // is invoked. - mProxy.dispatchOnStopFullScreen(); mLayout.removeView(getSurfaceView()); if (mProgressView != null) { @@ -242,12 +258,11 @@ public class HTML5VideoFullScreen extends HTML5VideoView mLayout = null; // Re enable plugin views. mProxy.getWebView().getViewManager().showAll(); - - mProxy = null; - // Don't show the controller after exiting the full screen. mMediaController = null; - mCurrentState = STATE_RESETTED; + // Continue the inline mode playing if necessary. + mProxy.dispatchOnStopFullScreen(mPlayingWhenDestroyed); + mProxy = null; } }; @@ -264,7 +279,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView mVideoSurfaceView.setFocusable(true); mVideoSurfaceView.setFocusableInTouchMode(true); mVideoSurfaceView.requestFocus(); - + mVideoSurfaceView.setOnKeyListener(mProxy); // Create a FrameLayout that will contain the VideoView and the // progress view (if any). mLayout = new FrameLayout(mProxy.getContext()); @@ -296,6 +311,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView * @return true when we are in full screen mode, even the surface not fully * created. */ + @Override public boolean isFullScreenMode() { return true; } @@ -334,6 +350,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView // Other listeners functions: private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { + @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { mCurrentBufferPercentage = percent; } diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java index 2c7ea5d..2ab2ab9 100644 --- a/core/java/android/webkit/HTML5VideoInline.java +++ b/core/java/android/webkit/HTML5VideoInline.java @@ -1,10 +1,24 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package android.webkit; import android.Manifest.permission; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; -import android.media.MediaPlayer; import android.webkit.HTML5VideoView; import android.webkit.HTML5VideoViewProxy; import android.view.Surface; @@ -34,8 +48,8 @@ public class HTML5VideoInline extends HTML5VideoView{ } } - HTML5VideoInline(int videoLayerId, int position) { - init(videoLayerId, position, false); + HTML5VideoInline(int videoLayerId, int position, boolean skipPrepare) { + init(videoLayerId, position, skipPrepare); } @Override @@ -84,7 +98,7 @@ public class HTML5VideoInline extends HTML5VideoView{ return mSurfaceTexture; } - public boolean surfaceTextureDeleted() { + public static boolean surfaceTextureDeleted() { return (mSurfaceTexture == null); } @@ -110,7 +124,9 @@ public class HTML5VideoInline extends HTML5VideoView{ } private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) { - mSurfaceTexture.setOnFrameAvailableListener(l); + if (mSurfaceTexture != null) { + mSurfaceTexture.setOnFrameAvailableListener(l); + } } } diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java index 371feea..0e8a5db 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -1,11 +1,23 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package android.webkit; -import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.net.Uri; -import android.util.Log; -import android.view.SurfaceView; import android.webkit.HTML5VideoViewProxy; import java.io.IOException; import java.util.HashMap; @@ -35,6 +47,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { static final int STATE_PREPARED = 2; static final int STATE_PLAYING = 3; static final int STATE_RESETTED = 4; + static final int STATE_RELEASED = 5; protected HTML5VideoViewProxy mProxy; @@ -126,7 +139,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } public void reset() { - if (mCurrentState != STATE_RESETTED) { + if (mCurrentState < STATE_RESETTED) { mPlayer.reset(); } mCurrentState = STATE_RESETTED; @@ -138,6 +151,18 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { } } + public static void release() { + if (mPlayer != null && mCurrentState != STATE_RELEASED) { + mPlayer.release(); + mPlayer = null; + } + mCurrentState = STATE_RELEASED; + } + + public boolean isReleased() { + return mCurrentState == STATE_RELEASED; + } + public boolean getPauseDuringPreparing() { return mPauseDuringPreparing; } @@ -337,11 +362,6 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { // Only used in HTML5VideoFullScreen } - public boolean surfaceTextureDeleted() { - // Only meaningful for HTML5VideoInline - return false; - } - public boolean fullScreenExited() { // Only meaningful for HTML5VideoFullScreen return false; diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index ab884df..a3d62ae 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -31,6 +31,8 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; +import android.view.KeyEvent; +import android.view.View; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -47,7 +49,8 @@ class HTML5VideoViewProxy extends Handler MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener, - SurfaceTexture.OnFrameAvailableListener { + SurfaceTexture.OnFrameAvailableListener, + View.OnKeyListener { // Logging tag. private static final String LOGTAG = "HTML5VideoViewProxy"; @@ -59,6 +62,7 @@ class HTML5VideoViewProxy extends Handler private static final int LOAD_DEFAULT_POSTER = 104; private static final int BUFFERING_START = 105; private static final int BUFFERING_END = 106; + private static final int ENTER_FULLSCREEN = 107; // Message Ids to be handled on the WebCore thread private static final int PREPARED = 200; @@ -93,9 +97,6 @@ class HTML5VideoViewProxy extends Handler private static HTML5VideoView mHTML5VideoView; private static boolean isVideoSelfEnded = false; - // By using the baseLayer and the current video Layer ID, we can - // identify the exact layer on the UI thread to use the SurfaceTexture. - private static int mBaseLayer = 0; private static void setPlayerBuffering(boolean playerBuffering) { mHTML5VideoView.setPlayerBuffering(playerBuffering); @@ -108,9 +109,7 @@ class HTML5VideoViewProxy extends Handler // Don't do this for full screen mode. if (mHTML5VideoView != null && !mHTML5VideoView.isFullScreenMode() - && !mHTML5VideoView.surfaceTextureDeleted()) { - mBaseLayer = layer; - + && !mHTML5VideoView.isReleased()) { int currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); SurfaceTexture surfTexture = HTML5VideoInline.getSurfaceTexture(currentVideoLayerId); @@ -126,7 +125,6 @@ class HTML5VideoViewProxy extends Handler if (playerState >= HTML5VideoView.STATE_PREPARED && !foundInTree) { mHTML5VideoView.pauseAndDispatch(mCurrentProxy); - mHTML5VideoView.deleteSurfaceTexture(); } } } @@ -136,9 +134,6 @@ class HTML5VideoViewProxy extends Handler public static void pauseAndDispatch() { if (mHTML5VideoView != null) { mHTML5VideoView.pauseAndDispatch(mCurrentProxy); - // When switching out, clean the video content on the old page - // by telling the layer not readyToUseSurfTex. - setBaseLayer(mBaseLayer); } } @@ -217,9 +212,16 @@ class HTML5VideoViewProxy extends Handler } } + boolean skipPrepare = false; + boolean createInlineView = false; if (backFromFullScreenMode + && currentVideoLayerId == videoLayerId + && !mHTML5VideoView.isReleased()) { + skipPrepare = true; + createInlineView = true; + } else if(backFromFullScreenMode || currentVideoLayerId != videoLayerId - || mHTML5VideoView.surfaceTextureDeleted()) { + || HTML5VideoInline.surfaceTextureDeleted()) { // Here, we handle the case when switching to a new video, // either inside a WebView or across WebViews // For switching videos within a WebView or across the WebView, @@ -231,12 +233,18 @@ class HTML5VideoViewProxy extends Handler } mHTML5VideoView.reset(); } + createInlineView = true; + } + if (createInlineView) { mCurrentProxy = proxy; - mHTML5VideoView = new HTML5VideoInline(videoLayerId, time); + mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, skipPrepare); mHTML5VideoView.setVideoURI(url, mCurrentProxy); mHTML5VideoView.prepareDataAndDisplayMode(proxy); - } else if (mCurrentProxy == proxy) { + return; + } + + if (mCurrentProxy == proxy) { // Here, we handle the case when we keep playing with one video if (!mHTML5VideoView.isPlaying()) { mHTML5VideoView.seekTo(time); @@ -278,9 +286,6 @@ class HTML5VideoViewProxy extends Handler if (!mHTML5VideoView.isFullScreenMode()) { mHTML5VideoView.start(); } - if (mBaseLayer != 0) { - setBaseLayer(mBaseLayer); - } } public static void end() { @@ -297,6 +302,7 @@ class HTML5VideoViewProxy extends Handler // A bunch event listeners for our VideoView // MediaPlayer.OnPreparedListener + @Override public void onPrepared(MediaPlayer mp) { VideoPlayer.onPrepared(); Message msg = Message.obtain(mWebCoreHandler, PREPARED); @@ -309,6 +315,7 @@ class HTML5VideoViewProxy extends Handler } // MediaPlayer.OnCompletionListener; + @Override public void onCompletion(MediaPlayer mp) { // The video ended by itself, so we need to // send a message to the UI thread to dismiss @@ -318,6 +325,7 @@ class HTML5VideoViewProxy extends Handler } // MediaPlayer.OnErrorListener + @Override public boolean onError(MediaPlayer mp, int what, int extra) { sendMessage(obtainMessage(ERROR)); return false; @@ -333,8 +341,9 @@ class HTML5VideoViewProxy extends Handler mWebCoreHandler.sendMessage(msg); } - public void dispatchOnStopFullScreen() { + public void dispatchOnStopFullScreen(boolean stillPlaying) { Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN); + msg.arg1 = stillPlaying ? 1 : 0; mWebCoreHandler.sendMessage(msg); } @@ -369,6 +378,15 @@ class HTML5VideoViewProxy extends Handler } break; } + case ENTER_FULLSCREEN:{ + String url = (String) msg.obj; + WebChromeClient client = mWebView.getWebChromeClient(); + int videoLayerID = msg.arg1; + if (client != null) { + VideoPlayer.enterFullScreenVideo(videoLayerID, url, this, mWebView); + } + break; + } case SEEK: { Integer time = (Integer) msg.obj; mSeekPosition = time; @@ -473,6 +491,7 @@ class HTML5VideoViewProxy extends Handler releaseQueue(); } // EventHandler methods. Executed on the network thread. + @Override public void status(int major_version, int minor_version, int code, @@ -480,10 +499,12 @@ class HTML5VideoViewProxy extends Handler mStatusCode = code; } + @Override public void headers(Headers headers) { mHeaders = headers; } + @Override public void data(byte[] data, int len) { if (mPosterBytes == null) { mPosterBytes = new ByteArrayOutputStream(); @@ -491,6 +512,7 @@ class HTML5VideoViewProxy extends Handler mPosterBytes.write(data, 0, len); } + @Override public void endData() { if (mStatusCode == 200) { if (mPosterBytes.size() > 0) { @@ -508,6 +530,7 @@ class HTML5VideoViewProxy extends Handler } if (mUrl != null) { mHandler.post(new Runnable() { + @Override public void run() { if (mRequestHandle != null) { mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode, @@ -519,14 +542,17 @@ class HTML5VideoViewProxy extends Handler } } + @Override public void certificate(SslCertificate certificate) { // Don't care. } + @Override public void error(int id, String description) { cleanup(); } + @Override public boolean handleSslErrorRequest(SslError error) { // Don't care. If this happens, data() will never be called so // mPosterBytes will never be created, so no need to call cleanup. @@ -613,7 +639,7 @@ class HTML5VideoViewProxy extends Handler nativeOnTimeupdate(msg.arg1, mNativePointer); break; case STOPFULLSCREEN: - nativeOnStopFullscreen(mNativePointer); + nativeOnStopFullscreen(msg.arg1, mNativePointer); break; case RESTORESTATE: nativeOnRestoreState(mNativePointer); @@ -664,6 +690,21 @@ class HTML5VideoViewProxy extends Handler } /** + * Play a video stream in full screen mode. + * @param url is the URL of the video stream. + */ + public void enterFullscreenForVideoLayer(String url, int videoLayerID) { + if (url == null) { + return; + } + + Message message = obtainMessage(ENTER_FULLSCREEN); + message.arg1 = videoLayerID; + message.obj = url; + sendMessage(message); + } + + /** * Seek into the video stream. * @param time is the position in the video stream. */ @@ -748,7 +789,7 @@ class HTML5VideoViewProxy extends Handler private native void nativeOnPaused(int nativePointer); private native void nativeOnPosterFetched(Bitmap poster, int nativePointer); private native void nativeOnTimeupdate(int position, int nativePointer); - private native void nativeOnStopFullscreen(int nativePointer); + private native void nativeOnStopFullscreen(int stillPlaying, int nativePointer); private native void nativeOnRestoreState(int nativePointer); private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture, int baseLayer, int videoLayerId, int textureName, @@ -763,4 +804,17 @@ class HTML5VideoViewProxy extends Handler } return false; } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()) { + VideoPlayer.exitFullScreenVideo(this, mWebView); + return true; + } + } + return false; + } } diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java index 2fbd1d0..296d960 100644 --- a/core/java/android/webkit/HttpAuthHandler.java +++ b/core/java/android/webkit/HttpAuthHandler.java @@ -19,40 +19,51 @@ package android.webkit; import android.os.Handler; /** - * HTTP authentication request that must be handled by the user interface. - * WebView creates the object and hands it to the current {@link WebViewClient}, - * which must call either {@link #proceed(String, String)} or {@link #cancel()}. + * Represents a request for HTTP authentication. Instances of this class are + * created by the WebView and passed to + * {@link WebViewClient#onReceivedHttpAuthRequest}. The host application must + * call either {@link #proceed} or {@link #cancel} to set the WebView's + * response to the request. */ public class HttpAuthHandler extends Handler { /** - * Package-private constructor needed for API compatibility. + * @hide Only for use by WebViewProvider implementations. */ - HttpAuthHandler() { + public HttpAuthHandler() { } /** - * @return True if we can use user credentials on record - * (ie, if we did not fail trying to use them last time) + * Gets whether the credentials stored for the current host (i.e. the host + * for which {@link WebViewClient#onReceivedHttpAuthRequest} was called) + * are suitable for use. Credentials are not suitable if they have + * previously been rejected by the server for the current request. + * + * @return whether the credentials are suitable for use + * @see Webview#getHttpAuthUsernamePassword */ public boolean useHttpAuthUsernamePassword() { return false; } /** - * Cancel the authorization request. + * Instructs the WebView to cancel the authentication request. */ public void cancel() { } /** - * Proceed with the authorization with the given credentials. + * Instructs the WebView to proceed with the authentication with the given + * credentials. Credentials for use with this method can be retrieved from + * the WebView's store using {@link WebView#getHttpAuthUsernamePassword}. */ public void proceed(String username, String password) { } /** - * return true if the prompt dialog should be suppressed. + * Gets whether the prompt dialog should be suppressed. + * + * @return whether the prompt dialog should be suppressed * @hide */ public boolean suppressDialog() { diff --git a/core/java/android/webkit/JavascriptInterface.java b/core/java/android/webkit/JavascriptInterface.java new file mode 100644 index 0000000..6cd2a7b --- /dev/null +++ b/core/java/android/webkit/JavascriptInterface.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that allows exposing methods to JavaScript. Starting from API level + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} and above, only methods explicitly + * marked with this annotation are available to the Javascript code. See + * {@link android.webkit.WebView#addJavascriptInterface} for more information about it. + * + */ +@SuppressWarnings("javadoc") +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface JavascriptInterface { +}
\ No newline at end of file diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java index e3e6092..01a81c4 100644 --- a/core/java/android/webkit/JniUtil.java +++ b/core/java/android/webkit/JniUtil.java @@ -173,8 +173,8 @@ class JniUtil { checkInitialized(); // If the device has not checked in it won't have pulled down the system setting for the // Autofill Url. In that case we will not make autofill server requests. - return Settings.Secure.getString(sContext.getContentResolver(), - Settings.Secure.WEB_AUTOFILL_QUERY_URL); + return Settings.Global.getString(sContext.getContentResolver(), + Settings.Global.WEB_AUTOFILL_QUERY_URL); } private static boolean canSatisfyMemoryAllocation(long bytesRequested) { diff --git a/core/java/android/webkit/MockGeolocation.java b/core/java/android/webkit/MockGeolocation.java index fbda492..885c6c2 100644 --- a/core/java/android/webkit/MockGeolocation.java +++ b/core/java/android/webkit/MockGeolocation.java @@ -17,21 +17,29 @@ package android.webkit; /** - * This class is simply a container for the methods used to configure WebKit's - * mock Geolocation service for use in LayoutTests. + * Used to configure the mock Geolocation client for the LayoutTests. * @hide */ public final class MockGeolocation { + private WebViewCore mWebViewCore; - // Global instance of a MockGeolocation - private static MockGeolocation sMockGeolocation; + public MockGeolocation(WebViewCore webViewCore) { + mWebViewCore = webViewCore; + } + + /** + * Sets use of the mock Geolocation client. Also resets that client. + */ + public void setUseMock() { + nativeSetUseMock(mWebViewCore); + } /** * Set the position for the mock Geolocation service. */ public void setPosition(double latitude, double longitude, double accuracy) { // This should only ever be called on the WebKit thread. - nativeSetPosition(latitude, longitude, accuracy); + nativeSetPosition(mWebViewCore, latitude, longitude, accuracy); } /** @@ -39,21 +47,18 @@ public final class MockGeolocation { */ public void setError(int code, String message) { // This should only ever be called on the WebKit thread. - nativeSetError(code, message); + nativeSetError(mWebViewCore, code, message); } - /** - * Get the global instance of MockGeolocation. - * @return The global MockGeolocation instance. - */ - public static MockGeolocation getInstance() { - if (sMockGeolocation == null) { - sMockGeolocation = new MockGeolocation(); - } - return sMockGeolocation; + public void setPermission(boolean allow) { + // This should only ever be called on the WebKit thread. + nativeSetPermission(mWebViewCore, allow); } // Native functions - private static native void nativeSetPosition(double latitude, double longitude, double accuracy); - private static native void nativeSetError(int code, String message); + private static native void nativeSetUseMock(WebViewCore webViewCore); + private static native void nativeSetPosition(WebViewCore webViewCore, double latitude, + double longitude, double accuracy); + private static native void nativeSetError(WebViewCore webViewCore, int code, String message); + private static native void nativeSetPermission(WebViewCore webViewCore, boolean allow); } diff --git a/core/java/android/webkit/SearchBox.java b/core/java/android/webkit/SearchBox.java deleted file mode 100644 index 38a1740..0000000 --- a/core/java/android/webkit/SearchBox.java +++ /dev/null @@ -1,109 +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.webkit; - -import java.util.List; - -/** - * Defines the interaction between the browser/renderer and the page running on - * a given WebView frame, if the page supports the chromium SearchBox API. - * - * http://dev.chromium.org/searchbox - * - * The browser or container app can query the page for search results using - * SearchBox.query() and receive suggestions by registering a listener on the - * SearchBox object. - * - * @hide - */ -public interface SearchBox { - /** - * Sets the current searchbox query. Note that the caller must call - * onchange() to ensure that the search page processes this query. - */ - void setQuery(String query); - - /** - * Verbatim is true if the caller suggests that the search page - * treat the current query as a verbatim search query (as opposed to a - * partially typed search query). As with setQuery, onchange() must be - * called to ensure that the search page processes the query. - */ - void setVerbatim(boolean verbatim); - - /** - * These attributes must contain the offset to the characters that immediately - * follow the start and end of the selection in the search box. If there is - * no such selection, then both selectionStart and selectionEnd must be the offset - * to the character that immediately follows the text entry cursor. In the case - * that there is no explicit text entry cursor, the cursor is - * implicitly at the end of the input. - */ - void setSelection(int selectionStart, int selectionEnd); - - /** - * Sets the dimensions of the view (if any) that overlaps the current - * window object. This is to ensure that the page renders results in - * a manner that allows them to not be obscured by such a view. Note - * that a call to onresize() is required if these dimensions change. - */ - void setDimensions(int x, int y, int width, int height); - - /** - * Notify the search page of any changes to the searchbox. Such as - * a change in the typed query (onchange), the user commiting a given query - * (onsubmit), or a change in size of a suggestions dropdown (onresize). - * - * @param listener an optional listener to notify of the success of the operation, - * indicating if the javascript function existed and could be called or not. - * It will be called on the UI thread. - */ - void onchange(SearchBoxListener listener); - void onsubmit(SearchBoxListener listener); - void onresize(SearchBoxListener listener); - void oncancel(SearchBoxListener listener); - - /** - * Add and remove listeners to the given Searchbox. Listeners are notified - * of any suggestions to the query that the underlying search engine might - * provide. - */ - void addSearchBoxListener(SearchBoxListener l); - void removeSearchBoxListener(SearchBoxListener l); - - /** - * Indicates if the searchbox API is supported in the current page. - */ - void isSupported(IsSupportedCallback callback); - - /** - * Listeners (if any) will be called on the thread that created the - * webview. - */ - public abstract class SearchBoxListener { - public void onSuggestionsReceived(String query, List<String> suggestions) {} - public void onChangeComplete(boolean called) {} - public void onSubmitComplete(boolean called) {} - public void onResizeComplete(boolean called) {} - public void onCancelComplete(boolean called) {} - } - - interface IsSupportedCallback { - void searchBoxIsSupported(boolean supported); - } -} diff --git a/core/java/android/webkit/SearchBoxImpl.java b/core/java/android/webkit/SearchBoxImpl.java deleted file mode 100644 index 9942d25..0000000 --- a/core/java/android/webkit/SearchBoxImpl.java +++ /dev/null @@ -1,304 +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.webkit; - -import android.text.TextUtils; -import android.util.Log; -import android.webkit.WebViewCore.EventHub; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.json.JSONStringer; - -/** - * The default implementation of the SearchBox interface. Implemented - * as a java bridge object and a javascript adapter that is called into - * by the page hosted in the frame. - */ -final class SearchBoxImpl implements SearchBox { - private static final String TAG = "WebKit.SearchBoxImpl"; - - /* package */ static final String JS_INTERFACE_NAME = "searchBoxJavaBridge_"; - - /* package */ static final String JS_BRIDGE - = "(function()" - + "{" - + "if (!window.chrome) {" - + " window.chrome = {};" - + "}" - + "if (!window.chrome.searchBox) {" - + " var sb = window.chrome.searchBox = {};" - + " sb.setSuggestions = function(suggestions) {" - + " if (window.searchBoxJavaBridge_) {" - + " window.searchBoxJavaBridge_.setSuggestions(JSON.stringify(suggestions));" - + " }" - + " };" - + " sb.setValue = function(valueArray) { sb.value = valueArray[0]; };" - + " sb.value = '';" - + " sb.x = 0;" - + " sb.y = 0;" - + " sb.width = 0;" - + " sb.height = 0;" - + " sb.selectionStart = 0;" - + " sb.selectionEnd = 0;" - + " sb.verbatim = false;" - + "}" - + "})();"; - - private static final String SET_QUERY_SCRIPT - = "if (window.chrome && window.chrome.searchBox) {" - + " window.chrome.searchBox.setValue(%s);" - + "}"; - - private static final String SET_VERBATIM_SCRIPT - = "if (window.chrome && window.chrome.searchBox) {" - + " window.chrome.searchBox.verbatim = %1$s;" - + "}"; - - private static final String SET_SELECTION_SCRIPT - = "if (window.chrome && window.chrome.searchBox) {" - + " var f = window.chrome.searchBox;" - + " f.selectionStart = %d" - + " f.selectionEnd = %d" - + "}"; - - private static final String SET_DIMENSIONS_SCRIPT - = "if (window.chrome && window.chrome.searchBox) { " - + " var f = window.chrome.searchBox;" - + " f.x = %d;" - + " f.y = %d;" - + " f.width = %d;" - + " f.height = %d;" - + "}"; - - private static final String DISPATCH_EVENT_SCRIPT - = "if (window.chrome && window.chrome.searchBox && window.chrome.searchBox.on%1$s) {" - + " window.chrome.searchBox.on%1$s();" - + " window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, true);" - + "} else {" - + " window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, false);" - + "}"; - - private static final String EVENT_CHANGE = "change"; - private static final String EVENT_SUBMIT = "submit"; - private static final String EVENT_RESIZE = "resize"; - private static final String EVENT_CANCEL = "cancel"; - - private static final String IS_SUPPORTED_SCRIPT - = "if (window.searchBoxJavaBridge_) {" - + " if (window.chrome && window.chrome.sv) {" - + " window.searchBoxJavaBridge_.isSupportedCallback(true);" - + " } else {" - + " window.searchBoxJavaBridge_.isSupportedCallback(false);" - + " }}"; - - private final List<SearchBoxListener> mListeners; - private final WebViewCore mWebViewCore; - private final CallbackProxy mCallbackProxy; - private IsSupportedCallback mSupportedCallback; - private int mNextEventId = 1; - private final HashMap<Integer, SearchBoxListener> mEventCallbacks; - - SearchBoxImpl(WebViewCore webViewCore, CallbackProxy callbackProxy) { - mListeners = new ArrayList<SearchBoxListener>(); - mWebViewCore = webViewCore; - mCallbackProxy = callbackProxy; - mEventCallbacks = new HashMap<Integer, SearchBoxListener>(); - } - - @Override - public void setQuery(String query) { - final String formattedQuery = jsonSerialize(query); - if (formattedQuery != null) { - final String js = String.format(SET_QUERY_SCRIPT, formattedQuery); - dispatchJs(js); - } - } - - @Override - public void setVerbatim(boolean verbatim) { - final String js = String.format(SET_VERBATIM_SCRIPT, String.valueOf(verbatim)); - dispatchJs(js); - } - - - @Override - public void setSelection(int selectionStart, int selectionEnd) { - final String js = String.format(SET_SELECTION_SCRIPT, selectionStart, selectionEnd); - dispatchJs(js); - } - - @Override - public void setDimensions(int x, int y, int width, int height) { - final String js = String.format(SET_DIMENSIONS_SCRIPT, x, y, width, height); - dispatchJs(js); - } - - @Override - public void onchange(SearchBoxListener callback) { - dispatchEvent(EVENT_CHANGE, callback); - } - - @Override - public void onsubmit(SearchBoxListener callback) { - dispatchEvent(EVENT_SUBMIT, callback); - } - - @Override - public void onresize(SearchBoxListener callback) { - dispatchEvent(EVENT_RESIZE, callback); - } - - @Override - public void oncancel(SearchBoxListener callback) { - dispatchEvent(EVENT_CANCEL, callback); - } - - private void dispatchEvent(String eventName, SearchBoxListener callback) { - int eventId; - if (callback != null) { - synchronized(this) { - eventId = mNextEventId++; - mEventCallbacks.put(eventId, callback); - } - } else { - eventId = 0; - } - final String js = String.format(DISPATCH_EVENT_SCRIPT, eventName, eventId); - dispatchJs(js); - } - - private void dispatchJs(String js) { - mWebViewCore.sendMessage(EventHub.EXECUTE_JS, js); - } - - @Override - public void addSearchBoxListener(SearchBoxListener l) { - synchronized (mListeners) { - mListeners.add(l); - } - } - - @Override - public void removeSearchBoxListener(SearchBoxListener l) { - synchronized (mListeners) { - mListeners.remove(l); - } - } - - @Override - public void isSupported(IsSupportedCallback callback) { - mSupportedCallback = callback; - dispatchJs(IS_SUPPORTED_SCRIPT); - } - - // Called by Javascript through the Java bridge. - public void isSupportedCallback(boolean isSupported) { - mCallbackProxy.onIsSupportedCallback(isSupported); - } - - public void handleIsSupportedCallback(boolean isSupported) { - IsSupportedCallback callback = mSupportedCallback; - mSupportedCallback = null; - if (callback != null) { - callback.searchBoxIsSupported(isSupported); - } - } - - // Called by Javascript through the Java bridge. - public void dispatchCompleteCallback(String function, int id, boolean successful) { - mCallbackProxy.onSearchboxDispatchCompleteCallback(function, id, successful); - } - - public void handleDispatchCompleteCallback(String function, int id, boolean successful) { - if (id != 0) { - SearchBoxListener listener; - synchronized(this) { - listener = mEventCallbacks.get(id); - mEventCallbacks.remove(id); - } - if (listener != null) { - if (TextUtils.equals(EVENT_CHANGE, function)) { - listener.onChangeComplete(successful); - } else if (TextUtils.equals(EVENT_SUBMIT, function)) { - listener.onSubmitComplete(successful); - } else if (TextUtils.equals(EVENT_RESIZE, function)) { - listener.onResizeComplete(successful); - } else if (TextUtils.equals(EVENT_CANCEL, function)) { - listener.onCancelComplete(successful); - } - } - } - } - - // This is used as a hackish alternative to javascript escaping. - // There appears to be no such functionality in the core framework. - private static String jsonSerialize(String query) { - JSONStringer stringer = new JSONStringer(); - try { - stringer.array().value(query).endArray(); - } catch (JSONException e) { - Log.w(TAG, "Error serializing query : " + query); - return null; - } - return stringer.toString(); - } - - // Called by Javascript through the Java bridge. - public void setSuggestions(String jsonArguments) { - if (jsonArguments == null) { - return; - } - - String query = null; - List<String> suggestions = new ArrayList<String>(); - try { - JSONObject suggestionsJson = new JSONObject(jsonArguments); - query = suggestionsJson.getString("query"); - - final JSONArray suggestionsArray = suggestionsJson.getJSONArray("suggestions"); - for (int i = 0; i < suggestionsArray.length(); ++i) { - final JSONObject suggestion = suggestionsArray.getJSONObject(i); - final String value = suggestion.getString("value"); - if (value != null) { - suggestions.add(value); - } - // We currently ignore the "type" of the suggestion. This isn't - // documented anywhere in the API documents. - // final String type = suggestions.getString("type"); - } - } catch (JSONException je) { - Log.w(TAG, "Error parsing json [" + jsonArguments + "], exception = " + je); - return; - } - - mCallbackProxy.onSearchboxSuggestionsReceived(query, suggestions); - } - - /* package */ void handleSuggestions(String query, List<String> suggestions) { - synchronized (mListeners) { - for (int i = mListeners.size() - 1; i >= 0; i--) { - mListeners.get(i).onSuggestionsReceived(query, suggestions); - } - } - } -} diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java index 426145a..3a43950 100644 --- a/core/java/android/webkit/SslErrorHandler.java +++ b/core/java/android/webkit/SslErrorHandler.java @@ -26,9 +26,9 @@ import android.os.Handler; public class SslErrorHandler extends Handler { /** - * Package-private constructor needed for API compatibility. + * @hide Only for use by WebViewProvider implementations. */ - SslErrorHandler() {} + public SslErrorHandler() {} /** * Proceed with the SSL certificate. diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java index c161085..096d4cda 100644 --- a/core/java/android/webkit/ViewStateSerializer.java +++ b/core/java/android/webkit/ViewStateSerializer.java @@ -16,7 +16,6 @@ package android.webkit; import android.graphics.Point; -import android.graphics.Region; import android.webkit.WebViewCore.DrawData; import java.io.DataInputStream; @@ -68,6 +67,15 @@ class ViewStateSerializer { return draw; } + public static void dumpLayerHierarchy(int baseLayer, OutputStream out, int level) { + nativeDumpLayerHierarchy(baseLayer, level, out, + new byte[WORKING_STREAM_STORAGE]); + } + + + private static native void nativeDumpLayerHierarchy(int baseLayer, int level, + OutputStream out, byte[] storage); + private static native boolean nativeSerializeViewState(int baseLayer, OutputStream stream, byte[] storage); diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java index 79e634e..bfef2e7 100644 --- a/core/java/android/webkit/WebBackForwardList.java +++ b/core/java/android/webkit/WebBackForwardList.java @@ -17,7 +17,6 @@ package android.webkit; import java.io.Serializable; -import java.util.ArrayList; /** * This class contains the back/forward list for a WebView. @@ -25,22 +24,11 @@ import java.util.ArrayList; * inspect the entries in the list. */ public class WebBackForwardList implements Cloneable, Serializable { - // Current position in the list. - private int mCurrentIndex; - // ArrayList of WebHistoryItems for maintaining our copy. - private ArrayList<WebHistoryItem> mArray; - // Flag to indicate that the list is invalid - private boolean mClearPending; - // CallbackProxy to issue client callbacks. - private final CallbackProxy mCallbackProxy; /** - * Construct a back/forward list used by clients of WebView. + * @hide */ - /*package*/ WebBackForwardList(CallbackProxy proxy) { - mCurrentIndex = -1; - mArray = new ArrayList<WebHistoryItem>(); - mCallbackProxy = proxy; + public WebBackForwardList() { } /** @@ -49,7 +37,7 @@ public class WebBackForwardList implements Cloneable, Serializable { * @return The current history item. */ public synchronized WebHistoryItem getCurrentItem() { - return getItemAtIndex(mCurrentIndex); + throw new MustOverrideException(); } /** @@ -58,7 +46,7 @@ public class WebBackForwardList implements Cloneable, Serializable { * @return The current index from 0...n or -1 if the list is empty. */ public synchronized int getCurrentIndex() { - return mCurrentIndex; + throw new MustOverrideException(); } /** @@ -67,10 +55,7 @@ public class WebBackForwardList implements Cloneable, Serializable { * @param index The index to retrieve. */ public synchronized WebHistoryItem getItemAtIndex(int index) { - if (index < 0 || index >= getSize()) { - return null; - } - return mArray.get(index); + throw new MustOverrideException(); } /** @@ -78,78 +63,7 @@ public class WebBackForwardList implements Cloneable, Serializable { * @return The size of the list. */ public synchronized int getSize() { - return mArray.size(); - } - - /** - * Mark the back/forward list as having a pending clear. This is used on the - * UI side to mark the list as being invalid during the clearHistory method. - */ - /*package*/ synchronized void setClearPending() { - mClearPending = true; - } - - /** - * Return the status of the clear flag. This is used on the UI side to - * determine if the list is valid for checking things like canGoBack. - */ - /*package*/ synchronized boolean getClearPending() { - return mClearPending; - } - - /** - * Add a new history item to the list. This will remove all items after the - * current item and append the new item to the end of the list. Called from - * the WebCore thread only. Synchronized because the UI thread may be - * reading the array or the current index. - * @param item A new history item. - */ - /*package*/ synchronized void addHistoryItem(WebHistoryItem item) { - // Update the current position because we are going to add the new item - // in that slot. - ++mCurrentIndex; - // If the current position is not at the end, remove all history items - // after the current item. - final int size = mArray.size(); - final int newPos = mCurrentIndex; - if (newPos != size) { - for (int i = size - 1; i >= newPos; i--) { - final WebHistoryItem h = mArray.remove(i); - } - } - // Add the item to the list. - mArray.add(item); - if (mCallbackProxy != null) { - mCallbackProxy.onNewHistoryItem(item); - } - } - - /** - * Clear the back/forward list. Called from the WebCore thread. - */ - /*package*/ synchronized void close(int nativeFrame) { - // Clear the array first because nativeClose will call addHistoryItem - // with the current item. - mArray.clear(); - mCurrentIndex = -1; - nativeClose(nativeFrame); - // Reset the clear flag - mClearPending = false; - } - - /* Remove the item at the given index. Called by JNI only. */ - private synchronized void removeHistoryItem(int index) { - // XXX: This is a special case. Since the callback is only triggered - // when removing the first item, we can assert that the index is 0. - // This lets us change the current index without having to query the - // native BackForwardList. - if (DebugFlags.WEB_BACK_FORWARD_LIST && (index != 0)) { - throw new AssertionError(); - } - final WebHistoryItem h = mArray.remove(index); - // XXX: If we ever add another callback for removing history items at - // any index, this will no longer be valid. - mCurrentIndex--; + throw new MustOverrideException(); } /** @@ -158,39 +72,7 @@ public class WebBackForwardList implements Cloneable, Serializable { * webkit package classes. */ protected synchronized WebBackForwardList clone() { - WebBackForwardList l = new WebBackForwardList(null); - if (mClearPending) { - // If a clear is pending, return a copy with only the current item. - l.addHistoryItem(getCurrentItem()); - return l; - } - l.mCurrentIndex = mCurrentIndex; - int size = getSize(); - l.mArray = new ArrayList<WebHistoryItem>(size); - for (int i = 0; i < size; i++) { - // Add a copy of each WebHistoryItem - l.mArray.add(mArray.get(i).clone()); - } - return l; - } - - /** - * Set the new history index. - * @param newIndex The new history index. - */ - /*package*/ synchronized void setCurrentIndex(int newIndex) { - mCurrentIndex = newIndex; - if (mCallbackProxy != null) { - mCallbackProxy.onIndexChanged(getItemAtIndex(newIndex), newIndex); - } + throw new MustOverrideException(); } - /** - * Restore the history index. - */ - /*package*/ static native synchronized void restoreIndex(int nativeFrame, - int index); - - /* Close the native list. */ - private static native void nativeClose(int nativeFrame); } diff --git a/core/java/android/webkit/WebBackForwardListClassic.java b/core/java/android/webkit/WebBackForwardListClassic.java new file mode 100644 index 0000000..2a14e6b --- /dev/null +++ b/core/java/android/webkit/WebBackForwardListClassic.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +import java.io.Serializable; +import java.util.ArrayList; + +/* package */ class WebBackForwardListClassic extends WebBackForwardList implements Cloneable, + Serializable { + + // Current position in the list. + private int mCurrentIndex; + // ArrayList of WebHistoryItems for maintaining our copy. + private ArrayList<WebHistoryItemClassic> mArray; + // Flag to indicate that the list is invalid + private boolean mClearPending; + // CallbackProxy to issue client callbacks. + private final CallbackProxy mCallbackProxy; + + /*package*/ WebBackForwardListClassic(CallbackProxy proxy) { + mCurrentIndex = -1; + mArray = new ArrayList<WebHistoryItemClassic>(); + mCallbackProxy = proxy; + } + + public synchronized WebHistoryItemClassic getCurrentItem() { + return getItemAtIndex(mCurrentIndex); + } + + public synchronized int getCurrentIndex() { + return mCurrentIndex; + } + + public synchronized WebHistoryItemClassic getItemAtIndex(int index) { + if (index < 0 || index >= getSize()) { + return null; + } + return mArray.get(index); + } + + public synchronized int getSize() { + return mArray.size(); + } + + /** + * Mark the back/forward list as having a pending clear. This is used on the + * UI side to mark the list as being invalid during the clearHistory method. + */ + /*package*/ synchronized void setClearPending() { + mClearPending = true; + } + + /** + * Return the status of the clear flag. This is used on the UI side to + * determine if the list is valid for checking things like canGoBack. + */ + /*package*/ synchronized boolean getClearPending() { + return mClearPending; + } + + /** + * Add a new history item to the list. This will remove all items after the + * current item and append the new item to the end of the list. Called from + * the WebCore thread only. Synchronized because the UI thread may be + * reading the array or the current index. + * @param item A new history item. + */ + /*package*/ synchronized void addHistoryItem(WebHistoryItem item) { + // Update the current position because we are going to add the new item + // in that slot. + ++mCurrentIndex; + // If the current position is not at the end, remove all history items + // after the current item. + final int size = mArray.size(); + final int newPos = mCurrentIndex; + if (newPos != size) { + for (int i = size - 1; i >= newPos; i--) { + final WebHistoryItem h = mArray.remove(i); + } + } + // Add the item to the list. + mArray.add((WebHistoryItemClassic) item); + if (mCallbackProxy != null) { + mCallbackProxy.onNewHistoryItem(item); + } + } + + /** + * Clear the back/forward list. Called from the WebCore thread. + */ + /*package*/ synchronized void close(int nativeFrame) { + // Clear the array first because nativeClose will call addHistoryItem + // with the current item. + mArray.clear(); + mCurrentIndex = -1; + nativeClose(nativeFrame); + // Reset the clear flag + mClearPending = false; + } + + /* Remove the item at the given index. Called by JNI only. */ + private synchronized void removeHistoryItem(int index) { + // XXX: This is a special case. Since the callback is only triggered + // when removing the first item, we can assert that the index is 0. + // This lets us change the current index without having to query the + // native BackForwardList. + if (DebugFlags.WEB_BACK_FORWARD_LIST && (index != 0)) { + throw new AssertionError(); + } + final WebHistoryItem h = mArray.remove(index); + // XXX: If we ever add another callback for removing history items at + // any index, this will no longer be valid. + mCurrentIndex--; + } + + public synchronized WebBackForwardListClassic clone() { + WebBackForwardListClassic l = new WebBackForwardListClassic(null); + if (mClearPending) { + // If a clear is pending, return a copy with only the current item. + l.addHistoryItem(getCurrentItem()); + return l; + } + l.mCurrentIndex = mCurrentIndex; + int size = getSize(); + l.mArray = new ArrayList<WebHistoryItemClassic>(size); + for (int i = 0; i < size; i++) { + // Add a copy of each WebHistoryItem + l.mArray.add(mArray.get(i).clone()); + } + return l; + } + + /** + * Set the new history index. + * @param newIndex The new history index. + */ + /*package*/ synchronized void setCurrentIndex(int newIndex) { + mCurrentIndex = newIndex; + if (mCallbackProxy != null) { + mCallbackProxy.onIndexChanged(getItemAtIndex(newIndex), newIndex); + } + } + + /** + * Restore the history index. + */ + /*package*/ static native synchronized void restoreIndex(int nativeFrame, + int index); + + /* Close the native list. */ + private static native void nativeClose(int nativeFrame); +} diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 4e8790b..e93db09 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -245,8 +245,8 @@ public class WebChromeClient { } /** - * Tell the client that the quota has been reached for the Application Cache - * API and request a new quota. The client must respond by invoking the + * Notify the host application that the Application Cache has reached the + * maximum size. The client must respond by invoking the * {@link WebStorage.QuotaUpdater#updateQuota(long) updateQuota(long)} * method of the supplied {@link WebStorage.QuotaUpdater} instance. The * minimum value that can be set for the new quota is the current quota. The @@ -255,7 +255,7 @@ public class WebChromeClient { * @param requiredStorage The amount of storage required by the Application * Cache operation that triggered this notification, * in bytes. - * @param quota The quota, in bytes + * @param quota the current maximum Application Cache size, in bytes * @param quotaUpdater An instance of {@link WebStorage.QuotaUpdater} which * must be used to inform the WebView of the new quota. */ @@ -297,7 +297,12 @@ public class WebChromeClient { * will continue to occur if the script does not finish at the next check * point. * @return boolean Whether the JavaScript execution should be interrupted. + * @deprecated This method is no longer supported and will not be invoked. */ + // This method was only called when using the JSC javascript engine. V8 became + // the default JS engine with Froyo and support for building with JSC was + // removed in b/5495373. V8 does not have a mechanism for making a callback such + // as this. public boolean onJsTimeout() { return true; } @@ -372,13 +377,6 @@ public class WebChromeClient { } /** - * Tell the client that the page being viewed is web app capable, - * i.e. has specified the fullscreen-web-app-capable meta tag. - * @hide - */ - public void setInstallableWebApp() { } - - /** * Tell the client that the page being viewed has an autofillable * form and the user would like to set a profile up. * @param msg A Message to send once the user has successfully diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java index 788d05c..9a588e4 100644 --- a/core/java/android/webkit/WebHistoryItem.java +++ b/core/java/android/webkit/WebHistoryItem.java @@ -18,9 +18,6 @@ package android.webkit; import android.graphics.Bitmap; -import java.net.MalformedURLException; -import java.net.URL; - /** * A convenience class for accessing fields in an entry in the back/forward list * of a WebView. Each WebHistoryItem is a snapshot of the requested history @@ -28,67 +25,11 @@ import java.net.URL; * @see WebBackForwardList */ public class WebHistoryItem implements Cloneable { - // Global identifier count. - private static int sNextId = 0; - // Unique identifier. - private final int mId; - // A point to a native WebHistoryItem instance which contains the actual data - private int mNativeBridge; - // The favicon for this item. - private Bitmap mFavicon; - // The pre-flattened data used for saving the state. - private byte[] mFlattenedData; - // The apple-touch-icon url for use when adding the site to the home screen, - // as obtained from a <link> element in the page. - private String mTouchIconUrlFromLink; - // If no <link> is specified, this holds the default location of the - // apple-touch-icon. - private String mTouchIconUrlServerDefault; - // Custom client data that is not flattened or read by native code. - private Object mCustomData; - - /** - * Basic constructor that assigns a unique id to the item. Called by JNI - * only. - */ - private WebHistoryItem(int nativeBridge) { - synchronized (WebHistoryItem.class) { - mId = sNextId++; - } - mNativeBridge = nativeBridge; - nativeRef(mNativeBridge); - } - - protected void finalize() throws Throwable { - if (mNativeBridge != 0) { - nativeUnref(mNativeBridge); - mNativeBridge = 0; - } - } /** - * Construct a new WebHistoryItem with initial flattened data. - * @param data The pre-flattened data coming from restoreState. - */ - /*package*/ WebHistoryItem(byte[] data) { - mFlattenedData = data; - synchronized (WebHistoryItem.class) { - mId = sNextId++; - } - } - - /** - * Construct a clone of a WebHistoryItem from the given item. - * @param item The history item to clone. + * @hide */ - private WebHistoryItem(WebHistoryItem item) { - mFlattenedData = item.mFlattenedData; - mId = item.mId; - mFavicon = item.mFavicon; - mNativeBridge = item.mNativeBridge; - if (mNativeBridge != 0) { - nativeRef(mNativeBridge); - } + public WebHistoryItem() { } /** @@ -97,10 +38,11 @@ public class WebHistoryItem implements Cloneable { * same object. * @return The id for this item. * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public int getId() { - return mId; + throw new MustOverrideException(); } /** @@ -112,8 +54,7 @@ public class WebHistoryItem implements Cloneable { * to synchronize this method. */ public String getUrl() { - if (mNativeBridge == 0) return null; - return nativeGetUrl(mNativeBridge); + throw new MustOverrideException(); } /** @@ -123,8 +64,7 @@ public class WebHistoryItem implements Cloneable { * @return The original url of this history item. */ public String getOriginalUrl() { - if (mNativeBridge == 0) return null; - return nativeGetOriginalUrl(mNativeBridge); + throw new MustOverrideException(); } /** @@ -134,8 +74,7 @@ public class WebHistoryItem implements Cloneable { * to synchronize this method. */ public String getTitle() { - if (mNativeBridge == 0) return null; - return nativeGetTitle(mNativeBridge); + throw new MustOverrideException(); } /** @@ -145,119 +84,14 @@ public class WebHistoryItem implements Cloneable { * to synchronize this method. */ public Bitmap getFavicon() { - if (mFavicon == null && mNativeBridge != 0) { - mFavicon = nativeGetFavicon(mNativeBridge); - } - return mFavicon; - } - - /** - * Return the touch icon url. - * If no touch icon <link> tag was specified, returns - * <host>/apple-touch-icon.png. The DownloadTouchIcon class that - * attempts to retrieve the touch icon will handle the case where - * that file does not exist. An icon set by a <link> tag is always - * used in preference to an icon saved on the server. - * @hide - */ - public String getTouchIconUrl() { - if (mTouchIconUrlFromLink != null) { - return mTouchIconUrlFromLink; - } else if (mTouchIconUrlServerDefault != null) { - return mTouchIconUrlServerDefault; - } - - try { - URL url = new URL(getOriginalUrl()); - mTouchIconUrlServerDefault = new URL(url.getProtocol(), url.getHost(), url.getPort(), - "/apple-touch-icon.png").toString(); - } catch (MalformedURLException e) { - return null; - } - return mTouchIconUrlServerDefault; - } - - /** - * Return the custom data provided by the client. - * @hide - */ - public Object getCustomData() { - return mCustomData; - } - - /** - * Set the custom data field. - * @param data An Object containing any data the client wishes to associate - * with the item. - * @hide - */ - public void setCustomData(Object data) { - // NOTE: WebHistoryItems are used in multiple threads. However, the - // public facing apis are all getters with the exception of this one - // api. Since this api is exclusive to clients, we don't make any - // promises about thread safety. - mCustomData = data; - } - - /** - * Set the favicon. - * @param icon A Bitmap containing the favicon for this history item. - * Note: The VM ensures 32-bit atomic read/write operations so we don't have - * to synchronize this method. - */ - /*package*/ void setFavicon(Bitmap icon) { - mFavicon = icon; - } - - /** - * Set the touch icon url. Will not overwrite an icon that has been - * set already from a <link> tag, unless the new icon is precomposed. - * @hide - */ - /*package*/ void setTouchIconUrl(String url, boolean precomposed) { - if (precomposed || mTouchIconUrlFromLink == null) { - mTouchIconUrlFromLink = url; - } - } - - /** - * Get the pre-flattened data. - * Note: The VM ensures 32-bit atomic read/write operations so we don't have - * to synchronize this method. - */ - /*package*/ byte[] getFlattenedData() { - if (mNativeBridge != 0) { - return nativeGetFlattenedData(mNativeBridge); - } - return mFlattenedData; - } - - /** - * Inflate this item. - * Note: The VM ensures 32-bit atomic read/write operations so we don't have - * to synchronize this method. - */ - /*package*/ void inflate(int nativeFrame) { - mNativeBridge = inflate(nativeFrame, mFlattenedData); - mFlattenedData = null; + throw new MustOverrideException(); } /** * Clone the history item for use by clients of WebView. */ protected synchronized WebHistoryItem clone() { - return new WebHistoryItem(this); + throw new MustOverrideException(); } - /* Natively inflate this item, this method is called in the WebCore thread. - */ - private native int inflate(int nativeFrame, byte[] data); - private native void nativeRef(int nptr); - private native void nativeUnref(int nptr); - private native String nativeGetTitle(int nptr); - private native String nativeGetUrl(int nptr); - private native String nativeGetOriginalUrl(int nptr); - private native byte[] nativeGetFlattenedData(int nptr); - private native Bitmap nativeGetFavicon(int nptr); - } diff --git a/core/java/android/webkit/WebHistoryItemClassic.java b/core/java/android/webkit/WebHistoryItemClassic.java new file mode 100644 index 0000000..1620fbf --- /dev/null +++ b/core/java/android/webkit/WebHistoryItemClassic.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +import android.graphics.Bitmap; + +import java.net.MalformedURLException; +import java.net.URL; + +/* package */ class WebHistoryItemClassic extends WebHistoryItem implements Cloneable { + // Global identifier count. + private static int sNextId = 0; + // Unique identifier. + private final int mId; + // A point to a native WebHistoryItem instance which contains the actual data + private int mNativeBridge; + // The favicon for this item. + private Bitmap mFavicon; + // The pre-flattened data used for saving the state. + private byte[] mFlattenedData; + // The apple-touch-icon url for use when adding the site to the home screen, + // as obtained from a <link> element in the page. + private String mTouchIconUrlFromLink; + // If no <link> is specified, this holds the default location of the + // apple-touch-icon. + private String mTouchIconUrlServerDefault; + // Custom client data that is not flattened or read by native code. + private Object mCustomData; + + /** + * Basic constructor that assigns a unique id to the item. Called by JNI + * only. + */ + private WebHistoryItemClassic(int nativeBridge) { + synchronized (WebHistoryItemClassic.class) { + mId = sNextId++; + } + mNativeBridge = nativeBridge; + nativeRef(mNativeBridge); + } + + protected void finalize() throws Throwable { + if (mNativeBridge != 0) { + nativeUnref(mNativeBridge); + mNativeBridge = 0; + } + } + + /** + * Construct a new WebHistoryItem with initial flattened data. + * @param data The pre-flattened data coming from restoreState. + */ + /*package*/ WebHistoryItemClassic(byte[] data) { + mFlattenedData = data; + synchronized (WebHistoryItemClassic.class) { + mId = sNextId++; + } + } + + /** + * Construct a clone of a WebHistoryItem from the given item. + * @param item The history item to clone. + */ + private WebHistoryItemClassic(WebHistoryItemClassic item) { + mFlattenedData = item.mFlattenedData; + mId = item.mId; + mFavicon = item.mFavicon; + mNativeBridge = item.mNativeBridge; + if (mNativeBridge != 0) { + nativeRef(mNativeBridge); + } + } + + @Deprecated + public int getId() { + return mId; + } + + public String getUrl() { + if (mNativeBridge == 0) return null; + return nativeGetUrl(mNativeBridge); + } + + public String getOriginalUrl() { + if (mNativeBridge == 0) return null; + return nativeGetOriginalUrl(mNativeBridge); + } + + public String getTitle() { + if (mNativeBridge == 0) return null; + return nativeGetTitle(mNativeBridge); + } + + public Bitmap getFavicon() { + if (mFavicon == null && mNativeBridge != 0) { + mFavicon = nativeGetFavicon(mNativeBridge); + } + return mFavicon; + } + + /** + * Return the touch icon url. + * If no touch icon <link> tag was specified, returns + * <host>/apple-touch-icon.png. The DownloadTouchIcon class that + * attempts to retrieve the touch icon will handle the case where + * that file does not exist. An icon set by a <link> tag is always + * used in preference to an icon saved on the server. + * @hide + */ + public String getTouchIconUrl() { + if (mTouchIconUrlFromLink != null) { + return mTouchIconUrlFromLink; + } else if (mTouchIconUrlServerDefault != null) { + return mTouchIconUrlServerDefault; + } + + try { + URL url = new URL(getOriginalUrl()); + mTouchIconUrlServerDefault = new URL(url.getProtocol(), url.getHost(), url.getPort(), + "/apple-touch-icon.png").toString(); + } catch (MalformedURLException e) { + return null; + } + return mTouchIconUrlServerDefault; + } + + /** + * Return the custom data provided by the client. + * @hide + */ + public Object getCustomData() { + return mCustomData; + } + + /** + * Set the custom data field. + * @param data An Object containing any data the client wishes to associate + * with the item. + * @hide + */ + public void setCustomData(Object data) { + // NOTE: WebHistoryItems are used in multiple threads. However, the + // public facing apis are all getters with the exception of this one + // api. Since this api is exclusive to clients, we don't make any + // promises about thread safety. + mCustomData = data; + } + + /** + * Set the favicon. + * @param icon A Bitmap containing the favicon for this history item. + * Note: The VM ensures 32-bit atomic read/write operations so we don't have + * to synchronize this method. + */ + /*package*/ void setFavicon(Bitmap icon) { + mFavicon = icon; + } + + /** + * Set the touch icon url. Will not overwrite an icon that has been + * set already from a <link> tag, unless the new icon is precomposed. + * @hide + */ + /*package*/ void setTouchIconUrl(String url, boolean precomposed) { + if (precomposed || mTouchIconUrlFromLink == null) { + mTouchIconUrlFromLink = url; + } + } + + /** + * Get the pre-flattened data. + * Note: The VM ensures 32-bit atomic read/write operations so we don't have + * to synchronize this method. + */ + /*package*/ byte[] getFlattenedData() { + if (mNativeBridge != 0) { + return nativeGetFlattenedData(mNativeBridge); + } + return mFlattenedData; + } + + /** + * Inflate this item. + * Note: The VM ensures 32-bit atomic read/write operations so we don't have + * to synchronize this method. + */ + /*package*/ void inflate(int nativeFrame) { + mNativeBridge = inflate(nativeFrame, mFlattenedData); + mFlattenedData = null; + } + + public synchronized WebHistoryItemClassic clone() { + return new WebHistoryItemClassic(this); + } + + /* Natively inflate this item, this method is called in the WebCore thread. + */ + private native int inflate(int nativeFrame, byte[] data); + private native void nativeRef(int nptr); + private native void nativeUnref(int nptr); + private native String nativeGetTitle(int nptr); + private native String nativeGetUrl(int nptr); + private native String nativeGetOriginalUrl(int nptr); + private native byte[] nativeGetFlattenedData(int nptr); + private native Bitmap nativeGetFavicon(int nptr); + +} diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index fa3cb20..aa68904 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -16,8 +16,7 @@ package android.webkit; -import android.os.Message; -import android.os.Build; +import android.content.Context; /** * Manages settings state for a WebView. When a WebView is first created, it @@ -94,30 +93,38 @@ public abstract class WebSettings { } /** - * Default cache usage pattern. Use with {@link #setCacheMode}. + * Default cache usage mode. If the navigation type doesn't impose any + * specific behavior, use cached resources when they are available + * and not expired, otherwise load resources from the network. + * Use with {@link #setCacheMode}. */ public static final int LOAD_DEFAULT = -1; /** - * Normal cache usage pattern. Use with {@link #setCacheMode}. + * Normal cache usage mode. Use with {@link #setCacheMode}. + * + * @deprecated This value is obsolete, as from API level + * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and onwards it has the + * same effect as {@link #LOAD_DEFAULT}. */ + @Deprecated public static final int LOAD_NORMAL = 0; /** - * Use cache if content is there, even if expired (eg, history nav). - * If it is not in the cache, load from network. + * Use cached resources when they are available, even if they have expired. + * Otherwise load resources from the network. * Use with {@link #setCacheMode}. */ public static final int LOAD_CACHE_ELSE_NETWORK = 1; /** - * Don't use the cache, load from network. + * Don't use the cache, load from the network. * Use with {@link #setCacheMode}. */ public static final int LOAD_NO_CACHE = 2; /** - * Don't use the network, load from cache only. + * Don't use the network, load from the cache. * Use with {@link #setCacheMode}. */ public static final int LOAD_CACHE_ONLY = 3; @@ -153,9 +160,11 @@ public abstract class WebSettings { } /** - * Enables dumping the pages navigation cache to a text file. + * Enables dumping the pages navigation cache to a text file. The default + * is false. * * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public void setNavDump(boolean enabled) { @@ -165,7 +174,10 @@ public abstract class WebSettings { /** * Gets whether dumping the navigation cache is enabled. * + * @return whether dumping the navigation cache is enabled + * @see #setNavDump * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public boolean getNavDump() { @@ -196,6 +208,26 @@ public abstract class WebSettings { } /** + * Sets whether the WebView requires a user gesture to play media. + * The default is true. + * + * @param require whether the WebView requires a user gesture to play media + */ + public void setMediaPlaybackRequiresUserGesture(boolean require) { + throw new MustOverrideException(); + } + + /** + * Gets whether the WebView requires a user gesture to play media. + * + * @return true if the WebView requires a user gesture to play media + * @see #setMediaPlaybackRequiresUserGesture + */ + public boolean getMediaPlaybackRequiresUserGesture() { + throw new MustOverrideException(); + } + + /** * Sets whether the WebView should use its built-in zoom mechanisms. The * built-in zoom mechanisms comprise on-screen zoom controls, which are * displayed over the WebView's content, and the use of a pinch gesture to @@ -285,14 +317,18 @@ public abstract class WebSettings { } /** - * Sets whether the WebView loads a page with overview mode. + * Sets whether the WebView loads pages in overview mode. The default is + * false. */ public void setLoadWithOverviewMode(boolean overview) { throw new MustOverrideException(); } /** - * Gets whether this WebView loads pages with overview mode. + * Gets whether this WebView loads pages in overview mode. + * + * @return whether this WebView loads pages in overview mode + * @see #setLoadWithOverviewMode */ public boolean getLoadWithOverviewMode() { throw new MustOverrideException(); @@ -304,7 +340,10 @@ public abstract class WebSettings { * If it is true, WebView will choose a solution to maximize the performance. * e.g. the WebView's content may not be updated during the transition. * If it is false, WebView will keep its fidelity. The default value is false. + * + * @deprecated This method is now obsolete, and will become a no-op in future. */ + @Deprecated public void setEnableSmoothTransition(boolean enable) { throw new MustOverrideException(); } @@ -314,7 +353,10 @@ public abstract class WebSettings { * zooming. * * @see #setEnableSmoothTransition + * + * @deprecated This method is now obsolete, and will become a no-op in future. */ + @Deprecated public boolean enableSmoothTransition() { throw new MustOverrideException(); } @@ -325,6 +367,7 @@ public abstract class WebSettings { * internal pattern. Default is true. * * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public void setUseWebViewBackgroundForOverscrollBackground(boolean view) { @@ -337,6 +380,7 @@ public abstract class WebSettings { * * @see #setUseWebViewBackgroundForOverscrollBackground * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public boolean getUseWebViewBackgroundForOverscrollBackground() { @@ -344,38 +388,45 @@ public abstract class WebSettings { } /** - * Sets whether the WebView is saving form data. + * Sets whether the WebView should save form data. The default is true, + * unless in private browsing mode, when the value is always false. */ public void setSaveFormData(boolean save) { throw new MustOverrideException(); } /** - * Gets whether the WebView is saving form data and displaying prior - * entries/autofill++. Always false in private browsing mode. + * Gets whether the WebView saves form data. Always false in private + * browsing mode. + * + * @return whether the WebView saves form data + * @see #setSaveFormData */ public boolean getSaveFormData() { throw new MustOverrideException(); } /** - * Stores whether the WebView is saving password. + * Sets whether the WebView should save passwords. The default is true. */ public void setSavePassword(boolean save) { throw new MustOverrideException(); } /** - * Gets whether the WebView is saving password. + * Gets whether the WebView saves passwords. + * + * @return whether the WebView saves passwords + * @see #setSavePassword */ public boolean getSavePassword() { throw new MustOverrideException(); } /** - * Sets the text zoom of the page in percent. Default is 100. + * Sets the text zoom of the page in percent. The default is 100. * - * @param textZoom the percent value for increasing or decreasing the text + * @param textZoom the text zoom in percent */ public synchronized void setTextZoom(int textZoom) { throw new MustOverrideException(); @@ -384,53 +435,65 @@ public abstract class WebSettings { /** * Gets the text zoom of the page in percent. * - * @return a percent value describing the text zoom - * @see #setTextSizeZoom + * @return the text zoom of the page in percent + * @see #setTextZoom */ public synchronized int getTextZoom() { throw new MustOverrideException(); } /** - * Sets the text size of the page. + * Sets the text size of the page. The default is {@link TextSize#NORMAL}. * - * @param t the TextSize value for increasing or decreasing the text - * @see WebSettings.TextSize - * @deprecated Use {@link #setTextZoom(int)} instead. + * @param t the text size as a {@link TextSize} value + * @deprecated Use {@link #setTextZoom} instead. */ public synchronized void setTextSize(TextSize t) { - throw new MustOverrideException(); + setTextZoom(t.value); } /** * Gets the text size of the page. If the text size was previously specified - * in percent using {@link #setTextZoom(int)}, this will return - * the closest matching {@link TextSize}. + * in percent using {@link #setTextZoom}, this will return the closest + * matching {@link TextSize}. * - * @return a TextSize enum value describing the text size - * @see WebSettings.TextSize - * @deprecated Use {@link #getTextZoom()} instead. + * @return the text size as a {@link TextSize} value + * @see #setTextSize + * @deprecated Use {@link #getTextZoom} instead. */ public synchronized TextSize getTextSize() { - throw new MustOverrideException(); + TextSize closestSize = null; + int smallestDelta = Integer.MAX_VALUE; + int textSize = getTextZoom(); + for (TextSize size : TextSize.values()) { + int delta = Math.abs(textSize - size.value); + if (delta == 0) { + return size; + } + if (delta < smallestDelta) { + smallestDelta = delta; + closestSize = size; + } + } + return closestSize != null ? closestSize : TextSize.NORMAL; } /** - * Sets the default zoom density of the page. This should be called from UI - * thread. + * Sets the default zoom density of the page. This must be called from the UI + * thread. The default is {@link ZoomDensity#MEDIUM}. * - * @param zoom a ZoomDensity value - * @see WebSettings.ZoomDensity + * @param zoom the zoom density */ public void setDefaultZoom(ZoomDensity zoom) { throw new MustOverrideException(); } /** - * Gets the default zoom density of the page. This should be called from UI - * thread. - * @return a ZoomDensity value - * @see WebSettings.ZoomDensity + * Gets the default zoom density of the page. This should be called from + * the UI thread. + * + * @return the zoom density + * @see #setDefaultZoom */ public ZoomDensity getDefaultZoom() { throw new MustOverrideException(); @@ -438,6 +501,7 @@ public abstract class WebSettings { /** * Enables using light touches to make a selection and activate mouseovers. + * The default is false. */ public void setLightTouchEnabled(boolean enabled) { throw new MustOverrideException(); @@ -445,6 +509,9 @@ public abstract class WebSettings { /** * Gets whether light touches are enabled. + * + * @return whether light touches are enabled + * @see #setLightTouchEnabled */ public boolean getLightTouchEnabled() { throw new MustOverrideException(); @@ -455,6 +522,7 @@ public abstract class WebSettings { * it now has no effect. * * @deprecated This setting now has no effect. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public synchronized void setUseDoubleTree(boolean use) { @@ -466,6 +534,7 @@ public abstract class WebSettings { * it now has no effect. * * @deprecated This setting now has no effect. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public synchronized boolean getUseDoubleTree() { @@ -474,11 +543,17 @@ public abstract class WebSettings { } /** - * Tells the WebView about user-agent string. + * Sets the user-agent string using an integer code. + * <ul> + * <li>0 means the WebView should use an Android user-agent string</li> + * <li>1 means the WebView should use a desktop user-agent string</li> + * </ul> + * Other values are ignored. The default is an Android user-agent string, + * i.e. code value 0. * - * @param ua 0 if the WebView should use an Android user-agent string, - * 1 if the WebView should use a desktop user-agent string - * @deprecated Please use setUserAgentString instead. + * @param ua the integer code for the user-agent string + * @deprecated Please use {@link #setUserAgentString} instead. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public synchronized void setUserAgent(int ua) { @@ -486,12 +561,18 @@ public abstract class WebSettings { } /** - * Gets the user-agent as an int. + * Gets the user-agent as an integer code. + * <ul> + * <li>-1 means the WebView is using a custom user-agent string set with + * {@link #setUserAgentString}</li> + * <li>0 means the WebView should use an Android user-agent string</li> + * <li>1 means the WebView should use a desktop user-agent string</li> + * </ul> * - * @return 0 if the WebView is using an Android user-agent string, - * 1 if the WebView is using a desktop user-agent string, - * -1 if the WebView is using user defined user-agent string - * @deprecated Please use getUserAgentString instead. + * @return the integer code for the user-agent string + * @see #setUserAgent + * @deprecated Please use {@link #getUserAgentString} instead. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public synchronized int getUserAgent() { @@ -499,7 +580,9 @@ public abstract class WebSettings { } /** - * Tells the WebView to use the wide viewport. + * Tells the WebView to use a wide viewport. The default is false. + * + * @param use whether to use a wide viewport */ public synchronized void setUseWideViewPort(boolean use) { throw new MustOverrideException(); @@ -509,26 +592,28 @@ public abstract class WebSettings { * Gets whether the WebView is using a wide viewport. * * @return true if the WebView is using a wide viewport + * @see #setUseWideViewPort */ public synchronized boolean getUseWideViewPort() { throw new MustOverrideException(); } /** - * Tells the WebView whether it supports multiple windows. TRUE means - * that {@link WebChromeClient#onCreateWindow(WebView, boolean, - * boolean, Message)} is implemented by the host application. + * Sets whether the WebView whether supports multiple windows. If set to + * true, {@link WebChromeClient#onCreateWindow} must be implemented by the + * host application. The default is false. + * + * @param support whether to suport multiple windows */ public synchronized void setSupportMultipleWindows(boolean support) { throw new MustOverrideException(); } /** - * Gets whether the WebView is supporting multiple windows. + * Gets whether the WebView supports multiple windows. * - * @return true if the WebView is supporting multiple windows. This means - * that {@link WebChromeClient#onCreateWindow(WebView, boolean, - * boolean, Message)} is implemented by the host application. + * @return true if the WebView supports multiple windows + * @see #setSupportMultipleWindows */ public synchronized boolean supportMultipleWindows() { throw new MustOverrideException(); @@ -536,10 +621,9 @@ public abstract class WebSettings { /** * Sets the underlying layout algorithm. This will cause a relayout of the - * WebView. The default is NARROW_COLUMNS. + * WebView. The default is {@link LayoutAlgorithm#NARROW_COLUMNS}. * - * @param l a LayoutAlgorithm enum specifying the algorithm to use - * @see WebSettings.LayoutAlgorithm + * @param l the layout algorithm to use, as a {@link LayoutAlgorithm} value */ public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) { throw new MustOverrideException(); @@ -548,10 +632,8 @@ public abstract class WebSettings { /** * Gets the current layout algorithm. * - * @return a LayoutAlgorithm enum value describing the layout algorithm - * being used + * @return the layout algorithm in use, as a {@link LayoutAlgorithm} value * @see #setLayoutAlgorithm - * @see WebSettings.LayoutAlgorithm */ public synchronized LayoutAlgorithm getLayoutAlgorithm() { throw new MustOverrideException(); @@ -596,7 +678,7 @@ public abstract class WebSettings { } /** - * Sets the sans-serif font family name. + * Sets the sans-serif font family name. The default is "sans-serif". * * @param font a font family name */ @@ -608,6 +690,7 @@ public abstract class WebSettings { * Gets the sans-serif font family name. * * @return the sans-serif font family name as a string + * @see #setSansSerifFontFamily */ public synchronized String getSansSerifFontFamily() { throw new MustOverrideException(); @@ -883,9 +966,9 @@ public abstract class WebSettings { public abstract void setAllowFileAccessFromFileURLs(boolean flag); /** - * Tells the WebView to enable plugins. + * Sets whether the WebView should enable plugins. The default is false. * - * @param flag true if the WebView should load plugins + * @param flag true if plugins should be enabled * @deprecated This method has been deprecated in favor of * {@link #setPluginState} */ @@ -898,7 +981,8 @@ public abstract class WebSettings { * Tells the WebView to enable, disable, or have plugins on demand. On * demand mode means that if a plugin exists that can handle the embedded * content, a placeholder icon will be shown instead of the plugin. When - * the placeholder is clicked, the plugin will be enabled. + * the placeholder is clicked, the plugin will be enabled. The default is + * {@link PluginState#OFF}. * * @param state a PluginState value */ @@ -921,23 +1005,27 @@ public abstract class WebSettings { /** * Sets the path to where database storage API databases should be saved. - * Note that the WebCore Database Tracker only allows the path to be set once. + * In order for the database storage API to function correctly, this method + * must be called with a path to which the application can write. This + * method should only be called once: repeated calls are ignored. * - * @param databasePath a String path to the directory where databases should - * be saved. May be the empty string but should never - * be null. + * @param databasePath a path to the directory where databases should be + * saved. */ // This will update WebCore when the Sync runs in the C++ side. + // Note that the WebCore Database Tracker only allows the path to be set + // once. public synchronized void setDatabasePath(String databasePath) { throw new MustOverrideException(); } /** - * Sets the path where the Geolocation permissions database should be saved. + * Sets the path where the Geolocation databases should be saved. In order + * for Geolocation permissions and cached positions to be persisted, this + * method must be called with a path to which the application can write. * - * @param databasePath a String path to the directory where the Geolocation - * permissions database should be saved. May be the - * empty string but should never be null. + * @param databasePath a path to the directory where databases should be + * saved. */ // This will update WebCore when the Sync runs in the C++ side. public synchronized void setGeolocationDatabasePath(String databasePath) { @@ -945,7 +1033,10 @@ public abstract class WebSettings { } /** - * Tells the WebView to enable Application Caches API. + * Sets whether the Application Caches API should be enabled. The default + * is false. Note that in order for the Application Caches API to be + * enabled, a valid database path must also be supplied to + * {@link #setAppCachePath}. * * @param flag true if the WebView should enable Application Caches */ @@ -954,20 +1045,25 @@ public abstract class WebSettings { } /** - * Sets a custom path to the Application Caches files. The client - * must ensure it exists before this call. + * Sets the path to the Application Caches files. In order for the + * Application Caches API to be enabled, this method must be called with a + * path to which the application can write. This method should only be + * called once: repeated calls are ignored. * * @param appCachePath a String path to the directory containing - * Application Caches files. The appCache path can be - * the empty string but should not be null. Passing - * null for this parameter will result in a no-op. + * Application Caches files. + * @see setAppCacheEnabled */ public synchronized void setAppCachePath(String appCachePath) { throw new MustOverrideException(); } /** - * Sets the maximum size for the Application Caches content. + * Sets the maximum size for the Application Cache content. The passed size + * will be rounded to the nearest value that the database can support, so + * this should be viewed as a guide, not a hard limit. Setting the + * size to a value less than current database size does not cause the + * database to be trimmed. The default size is {@link Long#MAX_VALUE}. * * @param appCacheMaxSize the maximum size in bytes */ @@ -976,7 +1072,9 @@ public abstract class WebSettings { } /** - * Sets whether the database storage API is enabled. + * Sets whether the database storage API is enabled. The default value is + * false. See also {@link #setDatabasePath} for how to correctly set up the + * database storage API. * * @param flag true if the WebView should use the database storage API */ @@ -985,7 +1083,7 @@ public abstract class WebSettings { } /** - * Sets whether the DOM storage API is enabled. + * Sets whether the DOM storage API is enabled. The default value is false. * * @param flag true if the WebView should use the DOM storage API */ @@ -997,15 +1095,16 @@ public abstract class WebSettings { * Gets whether the DOM Storage APIs are enabled. * * @return true if the DOM Storage APIs are enabled + * @see #setDomStorageEnabled */ public synchronized boolean getDomStorageEnabled() { throw new MustOverrideException(); } /** - * Gets the path to where database storage API databases are saved for - * the current WebView. + * Gets the path to where database storage API databases are saved. * * @return the String path to the database storage API databases + * @see #setDatabasePath */ public synchronized String getDatabasePath() { throw new MustOverrideException(); @@ -1015,13 +1114,16 @@ public abstract class WebSettings { * Gets whether the database storage API is enabled. * * @return true if the database storage API is enabled + * @see #setDatabaseEnabled */ public synchronized boolean getDatabaseEnabled() { throw new MustOverrideException(); } /** - * Sets whether Geolocation is enabled. + * Sets whether Geolocation is enabled. The default is true. See also + * {@link #setGeolocationDatabasePath} for how to correctly set up + * Geolocation. * * @param flag whether Geolocation should be enabled */ @@ -1064,6 +1166,7 @@ public abstract class WebSettings { * Gets whether plugins are enabled. * * @return true if plugins are enabled + * @see #setPluginsEnabled * @deprecated This method has been replaced by {@link #getPluginState} */ @Deprecated @@ -1072,9 +1175,10 @@ public abstract class WebSettings { } /** - * Gets the current plugin state. + * Gets the current state regarding whether plugins are enabled. * - * @return a value corresponding to the enum PluginState + * @return the plugin state as a {@link PluginState} value + * @see #setPluginState */ public synchronized PluginState getPluginState() { throw new MustOverrideException(); @@ -1135,8 +1239,8 @@ public abstract class WebSettings { } /** - * Sets the WebView's user-agent string. If the string "ua" is null or empty, - * it will use the system default user-agent string. + * Sets the WebView's user-agent string. If the string is null or empty, + * the system default value will be used. */ public synchronized void setUserAgentString(String ua) { throw new MustOverrideException(); @@ -1144,14 +1248,29 @@ public abstract class WebSettings { /** * Gets the WebView's user-agent string. + * + * @return the WebView's user-agent string + * @see #setUserAgentString */ public synchronized String getUserAgentString() { throw new MustOverrideException(); } /** + * Returns the default User-Agent used by a WebView. + * An instance of WebView could use a different User-Agent if a call + * is made to {@link WebSettings#setUserAgentString(String)}. + * + * @param context a Context object used to access application assets + */ + public static String getDefaultUserAgent(Context context) { + return WebViewFactory.getProvider().getStatics().getDefaultUserAgent(context); + } + + /** * Tells the WebView whether it needs to set a node to have focus when - * {@link WebView#requestFocus(int, android.graphics.Rect)} is called. + * {@link WebView#requestFocus(int, android.graphics.Rect)} is called. The + * default value is true. * * @param flag whether the WebView needs to set a node */ @@ -1161,9 +1280,10 @@ public abstract class WebSettings { /** * Sets the priority of the Render thread. Unlike the other settings, this - * one only needs to be called once per process. The default is NORMAL. + * one only needs to be called once per process. The default value is + * {@link RenderPriority#NORMAL}. * - * @param priority a RenderPriority + * @param priority the priority */ public synchronized void setRenderPriority(RenderPriority priority) { throw new MustOverrideException(); @@ -1171,20 +1291,25 @@ public abstract class WebSettings { /** * Overrides the way the cache is used. The way the cache is used is based - * on the navigation option. For a normal page load, the cache is checked + * on the navigation type. For a normal page load, the cache is checked * and content is re-validated as needed. When navigating back, content is - * not revalidated, instead the content is just pulled from the cache. - * This function allows the client to override this behavior. + * not revalidated, instead the content is just retrieved from the cache. + * This method allows the client to override this behavior by specifying + * one of {@link #LOAD_DEFAULT}, {@link #LOAD_NORMAL}, + * {@link #LOAD_CACHE_ELSE_NETWORK}, {@link #LOAD_NO_CACHE} or + * {@link #LOAD_CACHE_ONLY}. The default value is {@link #LOAD_DEFAULT}. * - * @param mode one of the LOAD_ values + * @param mode the mode to use */ public void setCacheMode(int mode) { throw new MustOverrideException(); } /** - * Gets the current setting for overriding the cache mode. For a full - * description, see the {@link #setCacheMode(int)} function. + * Gets the current setting for overriding the cache mode. + * + * @return the current setting for overriding the cache mode + * @see #setCacheMode */ public int getCacheMode() { throw new MustOverrideException(); diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java index 1288613..1bbe7bb 100644 --- a/core/java/android/webkit/WebSettingsClassic.java +++ b/core/java/android/webkit/WebSettingsClassic.java @@ -34,7 +34,7 @@ import java.util.Locale; */ public class WebSettingsClassic extends WebSettings { // TODO: Keep this up to date - private static final String PREVIOUS_VERSION = "4.0.4"; + private static final String PREVIOUS_VERSION = "4.1.1"; // WebView associated with this WebSettings. private WebViewClassic mWebView; @@ -116,6 +116,7 @@ public class WebSettingsClassic extends WebSettings { private boolean mNeedInitialFocus = true; private boolean mNavDump = false; private boolean mSupportZoom = true; + private boolean mMediaPlaybackRequiresUserGesture = true; private boolean mBuiltInZoomControls = false; private boolean mDisplayZoomControls = true; private boolean mAllowFileAccess = true; @@ -373,6 +374,21 @@ public class WebSettingsClassic extends WebSettings { synchronized(sLockForLocaleSettings) { locale = sLocale; } + return getDefaultUserAgentForLocale(mContext, locale); + } + + /** + * Returns the default User-Agent used by a WebView. + * An instance of WebView could use a different User-Agent if a call + * is made to {@link WebSettings#setUserAgent(int)} or + * {@link WebSettings#setUserAgentString(String)}. + * + * @param context a Context object used to access application assets + * @param locale The Locale to use in the User-Agent string. + * @see WebViewFactoryProvider#getDefaultUserAgent(Context) + * @see WebView#getDefaultUserAgent(Context) + */ + public static String getDefaultUserAgentForLocale(Context context, Locale locale) { StringBuffer buffer = new StringBuffer(); // Add version final String version = Build.VERSION.RELEASE; @@ -416,9 +432,9 @@ public class WebSettingsClassic extends WebSettings { buffer.append(" Build/"); buffer.append(id); } - String mobile = mContext.getResources().getText( + String mobile = context.getResources().getText( com.android.internal.R.string.web_user_agent_target_content).toString(); - final String base = mContext.getResources().getText( + final String base = context.getResources().getText( com.android.internal.R.string.web_user_agent).toString(); return String.format(base, buffer, mobile); } @@ -459,6 +475,25 @@ public class WebSettingsClassic extends WebSettings { } /** + * @see android.webkit.WebSettings#setMediaPlaybackRequiresUserGesture(boolean) + */ + @Override + public void setMediaPlaybackRequiresUserGesture(boolean support) { + if (mMediaPlaybackRequiresUserGesture != support) { + mMediaPlaybackRequiresUserGesture = support; + postSync(); + } + } + + /** + * @see android.webkit.WebSettings#getMediaPlaybackRequiresUserGesture() + */ + @Override + public boolean getMediaPlaybackRequiresUserGesture() { + return mMediaPlaybackRequiresUserGesture; + } + + /** * @see android.webkit.WebSettings#setBuiltInZoomControls(boolean) */ @Override @@ -630,34 +665,6 @@ public class WebSettingsClassic extends WebSettings { } /** - * @see android.webkit.WebSettings#setTextSize(android.webkit.WebSettingsClassic.TextSize) - */ - @Override - public synchronized void setTextSize(TextSize t) { - setTextZoom(t.value); - } - - /** - * @see android.webkit.WebSettings#getTextSize() - */ - @Override - public synchronized TextSize getTextSize() { - TextSize closestSize = null; - int smallestDelta = Integer.MAX_VALUE; - for (TextSize size : TextSize.values()) { - int delta = Math.abs(mTextSize - size.value); - if (delta == 0) { - return size; - } - if (delta < smallestDelta) { - smallestDelta = delta; - closestSize = size; - } - } - return closestSize != null ? closestSize : TextSize.NORMAL; - } - - /** * Set the double-tap zoom of the page in percent. Default is 100. * @param doubleTapZoom A percent value for increasing or decreasing the double-tap zoom. */ @@ -1115,6 +1122,7 @@ public class WebSettingsClassic extends WebSettings { if (mJavaScriptEnabled != flag) { mJavaScriptEnabled = flag; postSync(); + mWebView.updateJavaScriptEnabled(flag); } } diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java index 76674f4..1e955bd 100644 --- a/core/java/android/webkit/WebStorage.java +++ b/core/java/android/webkit/WebStorage.java @@ -23,17 +23,22 @@ import java.util.Map; * {@link WebView}. It manages the Application Cache API, the Web SQL Database * API and the HTML5 Web Storage API. * - * The Web SQL Database API provides storage which is private to a given - * origin, where an origin comprises the host, scheme and port of a URI. - * Similarly, use of the Application Cache API can be attributed to an origin. - * This class provides access to the storage use and quotas for these APIs for - * a given origin. Origins are represented using {@link WebStorage.Origin}. + * The Application Cache API provides a mechanism to create and maintain an + * application cache to power offline Web applications. Use of the Application + * Cache API can be attributed to an origin {@link WebStorage.Origin}, however + * it is not possible to set per-origin quotas. Note that there can be only + * one application cache per application. + * + * The Web SQL Database API provides storage which is private to a given origin. + * Similar to the Application Cache, use of the Web SQL Database can be attributed + * to an origin. It is also possible to set per-origin quotas. */ public class WebStorage { /** * Encapsulates a callback function which is used to provide a new quota - * for a JavaScript storage API. See + * for a JavaScript storage API. + * See * {@link WebChromeClient#onExceededDatabaseQuota} and * {@link WebChromeClient#onReachedMaxAppCacheSize}. */ @@ -54,6 +59,7 @@ public class WebStorage { /** * This class encapsulates information about the amount of storage * currently used by an origin for the JavaScript storage APIs. + * An origin comprises the host, scheme and port of a URI. * See {@link WebStorage} for details. */ public static class Origin { diff --git a/core/java/android/webkit/WebSyncManager.java b/core/java/android/webkit/WebSyncManager.java index 38b5e5c..d3ec603 100644 --- a/core/java/android/webkit/WebSyncManager.java +++ b/core/java/android/webkit/WebSyncManager.java @@ -37,9 +37,6 @@ abstract class WebSyncManager implements Runnable { // handler of the sync thread protected Handler mHandler; // database for the persistent storage - // Note that this remains uninitialised as it is unused. We cannot remove - // the member as it leaked into the public API via CookieSyncManager. - // TODO: hide this member, ditto for mHandler. protected WebViewDatabase mDataBase; // Ref count for calls to start/stop sync private int mStartSyncRefCount; @@ -65,6 +62,7 @@ abstract class WebSyncManager implements Runnable { protected WebSyncManager(Context context, String name) { mThreadName = name; if (context != null) { + mDataBase = WebViewDatabase.getInstance(context); mSyncThread = new Thread(this); mSyncThread.setName(mThreadName); mSyncThread.start(); diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index e493653..4202a7f 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -35,8 +35,8 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewDebug; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -44,6 +44,7 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.widget.AbsoluteLayout; +import java.io.BufferedWriter; import java.io.File; import java.util.Map; @@ -163,10 +164,7 @@ import java.util.Map; * * <p>For obvious security reasons, your application has its own * cache, cookie store etc.—it does not share the Browser - * application's data. Cookies are managed on a separate thread, so - * operations like index building don't block the UI - * thread. Follow the instructions in {@link android.webkit.CookieSyncManager} - * if you want to use cookies in your application. + * application's data. * </p> * * <p>By default, requests by the HTML to open new windows are @@ -263,7 +261,7 @@ import java.util.Map; @Widget public class WebView extends AbsoluteLayout implements ViewTreeObserver.OnGlobalFocusChangeListener, - ViewGroup.OnHierarchyChangeListener { + ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler { private static final String LOGTAG = "webview_proxy"; @@ -312,15 +310,15 @@ public class WebView extends AbsoluteLayout /** * Notifies the listener about progress made by a find operation. * - * @param numberOfMatches how many matches have been found * @param activeMatchOrdinal the zero-based ordinal of the currently selected match + * @param numberOfMatches how many matches have been found * @param isDoneCounting whether the find operation has actually completed. The listener * may be notified multiple times while the * operation is underway, and the numberOfMatches * value should not be considered final unless * isDoneCounting is true. */ - public void onFindResultReceived(int numberOfMatches, int activeMatchOrdinal, + public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting); } @@ -332,16 +330,12 @@ public class WebView extends AbsoluteLayout @Deprecated public interface PictureListener { /** - * Notifies the listener that the picture has changed. + * Used to provide notification that the WebView's picture has changed. + * See {@link WebView#capturePicture} for details of the picture. * * @param view the WebView that owns the picture * @param picture the new picture - * @deprecated Due to internal changes, the picture does not include - * composited layers such as fixed position elements or - * scrollable divs. While the PictureListener API can still - * be used to detect changes in the WebView content, you - * are advised against its usage until a replacement is - * provided in a future Android release. + * @deprecated Deprecated due to internal changes. */ @Deprecated public void onNewPicture(WebView view, Picture picture); @@ -475,7 +469,13 @@ public class WebView extends AbsoluteLayout * @param defStyle the default style resource ID * @param privateBrowsing whether this WebView will be initialized in * private mode + * + * @deprecated Private browsing is no longer supported directly via + * WebView and will be removed in a future release. Prefer using + * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager} + * and {@link WebStorage} for fine-grained control of privacy data. */ + @Deprecated public WebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) { this(context, attrs, defStyle, null, privateBrowsing); @@ -555,6 +555,7 @@ public class WebView extends AbsoluteLayout * Gets the visible height (in pixels) of the embedded title bar (if any). * * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ public int getVisibleTitleHeight() { checkThread(); @@ -574,7 +575,11 @@ public class WebView extends AbsoluteLayout /** * Sets the SSL certificate for the main top-level page. + * + * @deprecated Calling this function has no useful effect, and will be + * ignored in future releases. */ + @Deprecated public void setCertificate(SslCertificate certificate) { checkThread(); mProvider.setCertificate(certificate); @@ -585,12 +590,16 @@ public class WebView extends AbsoluteLayout //------------------------------------------------------------------------- /** - * Saves the username and password for a particular host in this WebView's - * internal database. + * Sets a username and password pair for the specified host. This data is + * used by the Webview to autocomplete username and password fields in web + * forms. Note that this is unrelated to the credentials used for HTTP + * authentication. * * @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 WebViewDatabase#clearUsernamePassword + * @see WebViewDatabase#hasUsernamePassword */ public void savePassword(String host, String username, String password) { checkThread(); @@ -598,13 +607,17 @@ public class WebView extends AbsoluteLayout } /** - * Sets the HTTP authentication credentials for a given host and realm. + * Stores HTTP authentication credentials for a given host and realm. This + * method is intended to be used with + * {@link WebViewClient#onReceivedHttpAuthRequest}. * - * @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 host the host to which the credentials apply + * @param realm the realm to which the credentials apply + * @param username the username * @param password the password + * @see getHttpAuthUsernamePassword + * @see WebViewDatabase#hasHttpAuthUsernamePassword + * @see WebViewDatabase#clearHttpAuthUsernamePassword */ public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) { @@ -613,13 +626,18 @@ public class WebView extends AbsoluteLayout } /** - * Retrieves the HTTP authentication username and password for a given - * host and realm pair + * Retrieves HTTP authentication credentials for a given host and realm. + * This method is intended to be used with + * {@link WebViewClient#onReceivedHttpAuthRequest}. * - * @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. + * @param host the host to which the credentials apply + * @param realm the realm to which the credentials apply + * @return the credentials as a String array, if found. The first element + * is the username and the second element is the password. Null if + * no credentials are found. + * @see setHttpAuthUsernamePassword + * @see WebViewDatabase#hasHttpAuthUsernamePassword + * @see WebViewDatabase#clearHttpAuthUsernamePassword */ public String[] getHttpAuthUsernamePassword(String host, String realm) { checkThread(); @@ -641,6 +659,7 @@ public class WebView extends AbsoluteLayout * Notifications are enabled by default. * * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public static void enablePlatformNotifications() { @@ -653,6 +672,7 @@ public class WebView extends AbsoluteLayout * Notifications are enabled by default. * * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public static void disablePlatformNotifications() { @@ -677,14 +697,11 @@ public class WebView extends AbsoluteLayout * {@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. + * called. * * @param outState the Bundle to store this WebView's 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 */ public WebBackForwardList saveState(Bundle outState) { checkThread(); @@ -699,6 +716,7 @@ public class WebView extends AbsoluteLayout * overwritten with this WebView's picture data. * @return true if the picture was successfully saved * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public boolean savePicture(Bundle b, final File dest) { @@ -715,6 +733,7 @@ public class WebView extends AbsoluteLayout * @param src the file where the picture data was stored * @return true if the picture was successfully restored * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public boolean restorePicture(Bundle b, File src) { @@ -723,19 +742,16 @@ public class WebView extends AbsoluteLayout } /** - * Restores 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 this WebView before using the object. If + * Restores the state of this WebView from the given Bundle. This method is + * intended for use in {@link android.app.Activity#onRestoreInstanceState} + * and should be called to restore the state of this WebView. If * it is called after this 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. + * display data for this WebView. * * @param inState the incoming Bundle of state * @return the restored back/forward list or null if restoreState failed - * @see #savePicture - * @see #restorePicture */ public WebBackForwardList restoreState(Bundle inState) { checkThread(); @@ -791,11 +807,13 @@ public class WebView extends AbsoluteLayout * #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 + * The encoding parameter specifies whether the data is base64 or URL + * encoded. If the data is base64 encoded, the value of the encoding + * parameter must be 'base64'. For all other values of the parameter, + * including null, it is assumed that the data uses 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. + * 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 @@ -986,13 +1004,18 @@ public class WebView extends AbsoluteLayout } /** - * Gets a new picture that captures the current display of this WebView. - * This is a copy of the display, and will be unaffected if this WebView - * later loads a different URL. + * Gets a new picture that captures the current contents of this WebView. + * The picture is of the entire document being displayed, and is not + * limited to the area currently displayed by this WebView. Also, the + * picture is a static copy and is unaffected by later changes to the + * content being displayed. + * <p> + * Note that due to internal changes, for API levels between + * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and + * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the + * picture does not include fixed position elements or scrollable divs. * - * @return a picture containing the current contents of this WebView. Note - * this picture is of the entire document, and is not restricted to - * the bounds of the view. + * @return a picture that captures the current contents of this WebView */ public Picture capturePicture() { checkThread(); @@ -1003,7 +1026,13 @@ public class WebView extends AbsoluteLayout * Gets the current scale of this WebView. * * @return the current scale + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. */ + @Deprecated + @ViewDebug.ExportedProperty(category = "webview") public float getScale() { checkThread(); return mProvider.getScale(); @@ -1094,6 +1123,7 @@ public class WebView extends AbsoluteLayout * * @return the URL for the current page */ + @ViewDebug.ExportedProperty(category = "webview") public String getUrl() { checkThread(); return mProvider.getUrl(); @@ -1108,6 +1138,7 @@ public class WebView extends AbsoluteLayout * * @return the URL that was originally requested for the current page */ + @ViewDebug.ExportedProperty(category = "webview") public String getOriginalUrl() { checkThread(); return mProvider.getOriginalUrl(); @@ -1119,6 +1150,7 @@ public class WebView extends AbsoluteLayout * * @return the title for the current page */ + @ViewDebug.ExportedProperty(category = "webview") public String getTitle() { checkThread(); return mProvider.getTitle(); @@ -1161,6 +1193,7 @@ public class WebView extends AbsoluteLayout * * @return the height of the HTML content */ + @ViewDebug.ExportedProperty(category = "webview") public int getContentHeight() { checkThread(); return mProvider.getContentHeight(); @@ -1172,6 +1205,7 @@ public class WebView extends AbsoluteLayout * @return the width of the HTML content * @hide */ + @ViewDebug.ExportedProperty(category = "webview") public int getContentWidth() { return mProvider.getContentWidth(); } @@ -1246,8 +1280,10 @@ public class WebView extends AbsoluteLayout } /** - * Makes sure that clearing the form data removes the adapter from the - * currently focused textfield if there is one. + * Removes the autocomplete popup from the currently focused form field, if + * present. Note this only affects the display of the autocomplete popup, + * it does not remove any saved form data from this WebView's store. To do + * that, use {@link WebViewDatabase#clearFormData}. */ public void clearFormData() { checkThread(); @@ -1297,12 +1333,11 @@ public class WebView extends AbsoluteLayout } /** - * Highlights and scrolls to the next match found by {@link #findAll} or + * Highlights and scrolls to the next match found by * {@link #findAllAsync}, wrapping around page boundaries as necessary. - * Notifies any registered {@link FindListener}. If neither - * {@link #findAll} nor {@link #findAllAsync(String)} has been called yet, - * or if {@link #clearMatches} has been called since the last find - * operation, this function does nothing. + * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)} + * has not been called yet, or if {@link #clearMatches} has been called since the + * last find operation, this function does nothing. * * @param forward the direction to search * @see #setFindListener @@ -1331,8 +1366,7 @@ public class WebView extends AbsoluteLayout /** * Finds all instances of find on the page and highlights them, * asynchronously. Notifies any registered {@link FindListener}. - * Successive calls to this or {@link #findAll} will cancel any - * pending searches. + * Successive calls to this will cancel any pending searches. * * @param find the string to find. * @see #setFindListener @@ -1381,13 +1415,12 @@ public class WebView extends AbsoluteLayout * @return the address, or if no address is found, null */ public static String findAddress(String addr) { - checkThread(); return getFactory().getStatics().findAddress(addr); } /** * Clears the highlighting surrounding text matches created by - * {@link #findAll} or {@link #findAllAsync}. + * {@link #findAllAsync}. */ public void clearMatches() { checkThread(); @@ -1447,6 +1480,7 @@ public class WebView extends AbsoluteLayout * * @param listener an implementation of WebView.PictureListener * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public void setPictureListener(PictureListener listener) { @@ -1457,10 +1491,22 @@ public class WebView extends AbsoluteLayout /** * Injects the supplied Java object into this WebView. The object is * injected into the JavaScript context of the main frame, using the - * supplied name. This allows the Java object's public methods to be - * accessed from JavaScript. Note that that injected objects will not + * supplied name. This allows the Java object's methods to be + * accessed from JavaScript. For applications targeted to API + * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + * and above, only public methods that are annotated with + * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript. + * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below, + * all public methods (including the inherited ones) can be accessed, see the + * important security note below for implications. + * <p> Note that injected objects will not * appear in JavaScript until the page is next (re)loaded. For example: - * <pre> webView.addJavascriptInterface(new Object(), "injectedObject"); + * <pre> + * class JsObject { + * {@literal @}JavascriptInterface + * public String toString() { return "injectedObject"; } + * } + * webView.addJavascriptInterface(new JsObject(), "injectedObject"); * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null); * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre> * <p> @@ -1468,7 +1514,9 @@ public class WebView extends AbsoluteLayout * <ul> * <li> This method can be used to allow JavaScript to control the host * application. This is a powerful feature, but also presents a security - * risk, particularly as JavaScript could use reflection to access an + * risk for applications targeted to API level + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below, because + * JavaScript could use reflection to access an * injected object's public fields. 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 @@ -1477,6 +1525,7 @@ public class WebView extends AbsoluteLayout * <li> JavaScript interacts with Java object on a private, background * thread of this WebView. Care is therefore required to maintain thread * safety.</li> + * <li> The Java object's fields are not accessible.</li> * </ul> * * @param object the Java object to inject into this WebView's JavaScript @@ -1539,11 +1588,11 @@ public class WebView extends AbsoluteLayout * functionality; it will be deprecated in the future. * * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public void emulateShiftHeld() { checkThread(); - mProvider.emulateShiftHeld(); } /** @@ -1574,6 +1623,10 @@ public class WebView extends AbsoluteLayout public void onGlobalFocusChanged(View oldFocus, View newFocus) { } + /** + * @deprecated Only the default case, true, will be supported in a future version. + */ + @Deprecated public void setMapTrackballToArrowKeys(boolean setMap) { checkThread(); mProvider.setMapTrackballToArrowKeys(setMap); @@ -1607,7 +1660,12 @@ public class WebView extends AbsoluteLayout * Gets whether this WebView can be zoomed in. * * @return true if this WebView can be zoomed in + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. */ + @Deprecated public boolean canZoomIn() { checkThread(); return mProvider.canZoomIn(); @@ -1617,7 +1675,12 @@ public class WebView extends AbsoluteLayout * Gets whether this WebView can be zoomed out. * * @return true if this WebView can be zoomed out + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. */ + @Deprecated public boolean canZoomOut() { checkThread(); return mProvider.canZoomOut(); @@ -1645,11 +1708,29 @@ public class WebView extends AbsoluteLayout /** * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public void debugDump() { checkThread(); - mProvider.debugDump(); + } + + /** + * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)} + * @hide + */ + @Override + public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) { + mProvider.dumpViewHierarchyWithProperties(out, level); + } + + /** + * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)} + * @hide + */ + @Override + public View findHierarchyView(String className, int hashCode) { + return mProvider.findHierarchyView(className, hashCode); } //------------------------------------------------------------------------- diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index bcab1b1..d68511c 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -16,6 +16,7 @@ package android.webkit; +import android.accessibilityservice.AccessibilityServiceInfo; import android.animation.ObjectAnimator; import android.annotation.Widget; import android.app.ActivityManager; @@ -26,7 +27,6 @@ import android.content.ClipboardManager; import android.content.ComponentCallbacks2; import android.content.Context; import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; @@ -55,6 +55,7 @@ import android.net.ProxyProperties; import android.net.Uri; import android.net.http.SslCertificate; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -68,7 +69,6 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; -import android.view.Display; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.HardwareCanvas; @@ -86,7 +86,6 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewRootImpl; -import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -117,6 +116,8 @@ import android.widget.Toast; import junit.framework.Assert; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -129,11 +130,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.Vector; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Implements a backend provider for the {@link WebView} public API. @@ -275,7 +275,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc super.setComposingText(limitedText, newCursorPosition); updateSelection(); if (limitedText != text) { - restartInput(); int lastCaret = start + limitedText.length(); finishComposingText(); setSelection(lastCaret, lastCaret); @@ -376,28 +375,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; } } + int action = EditorInfo.IME_ACTION_GO; switch (type) { case WebTextView.NORMAL_TEXT_FIELD: - imeOptions |= EditorInfo.IME_ACTION_GO; break; case WebTextView.TEXT_AREA: inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; - imeOptions |= EditorInfo.IME_ACTION_NONE; + action = EditorInfo.IME_ACTION_NONE; break; case WebTextView.PASSWORD: inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD; - imeOptions |= EditorInfo.IME_ACTION_GO; break; case WebTextView.SEARCH: - imeOptions |= EditorInfo.IME_ACTION_SEARCH; + action = EditorInfo.IME_ACTION_SEARCH; break; case WebTextView.EMAIL: // inputType needs to be overwritten because of the different text variation. inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; - imeOptions |= EditorInfo.IME_ACTION_GO; break; case WebTextView.NUMBER: // inputType needs to be overwritten because of the different class. @@ -405,23 +402,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL; // Number and telephone do not have both a Tab key and an // action, so set the action to NEXT - imeOptions |= EditorInfo.IME_ACTION_NEXT; break; case WebTextView.TELEPHONE: // inputType needs to be overwritten because of the different class. inputType = InputType.TYPE_CLASS_PHONE; - imeOptions |= EditorInfo.IME_ACTION_NEXT; break; case WebTextView.URL: // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so // exclude it for now. - imeOptions |= EditorInfo.IME_ACTION_GO; inputType |= InputType.TYPE_TEXT_VARIATION_URI; break; default: - imeOptions |= EditorInfo.IME_ACTION_GO; break; } + imeOptions |= action; mHint = initData.mLabel; mInputType = inputType; mImeOptions = imeOptions; @@ -679,6 +673,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // after resize. static private final int EDIT_RECT_BUFFER = 10; + static private final long SELECTION_HANDLE_ANIMATION_MS = 150; + // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK private boolean mAutoRedraw; @@ -742,12 +738,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // the existing GL resources for the html5 video will be destroyed // at native side. // Here we just need to clean up the Surface Texture which is static. - if (level >= TRIM_MEMORY_UI_HIDDEN) { + if (level > TRIM_MEMORY_UI_HIDDEN) { HTML5VideoInline.cleanupSurfaceTexture(); + HTML5VideoView.release(); } WebViewClassic.nativeOnTrimMemory(level); } - } // A final CallbackProxy shared by WebViewCore and BrowserFrame. @@ -949,21 +945,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private Drawable mSelectHandleLeft; private Drawable mSelectHandleRight; private Drawable mSelectHandleCenter; - private Point mSelectHandleLeftOffset; - private Point mSelectHandleRightOffset; - private Point mSelectHandleCenterOffset; - private Point mSelectCursorLeft = new Point(); - private int mSelectCursorLeftLayerId; - private QuadF mSelectCursorLeftTextQuad = new QuadF(); - private Point mSelectCursorRight = new Point(); - private int mSelectCursorRightLayerId; - private QuadF mSelectCursorRightTextQuad = new QuadF(); + private Point mSelectOffset; + private Point mSelectCursorBase = new Point(); + private Rect mSelectHandleBaseBounds = new Rect(); + private int mSelectCursorBaseLayerId; + private QuadF mSelectCursorBaseTextQuad = new QuadF(); + private Point mSelectCursorExtent = new Point(); + private Rect mSelectHandleExtentBounds = new Rect(); + private int mSelectCursorExtentLayerId; + private QuadF mSelectCursorExtentTextQuad = new QuadF(); private Point mSelectDraggingCursor; - private Point mSelectDraggingOffset; private QuadF mSelectDraggingTextQuad; private boolean mIsCaretSelection; - static final int HANDLE_ID_LEFT = 0; - static final int HANDLE_ID_RIGHT = 1; + static final int HANDLE_ID_BASE = 0; + static final int HANDLE_ID_EXTENT = 1; // the color used to highlight the touch rectangles static final int HIGHLIGHT_COLOR = 0x6633b5e5; @@ -1037,7 +1032,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc static final int AUTOFILL_COMPLETE = 134; static final int SCREEN_ON = 136; - static final int ENTER_FULLSCREEN_VIDEO = 137; static final int UPDATE_ZOOM_DENSITY = 139; static final int EXIT_FULLSCREEN_VIDEO = 140; @@ -1053,6 +1047,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc static final int EDIT_TEXT_SIZE_CHANGED = 150; static final int SHOW_CARET_HANDLE = 151; static final int UPDATE_CONTENT_BOUNDS = 152; + static final int SCROLL_HANDLE_INTO_VIEW = 153; private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID; private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT; @@ -1313,6 +1308,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc public WebViewDatabase getWebViewDatabase(Context context) { return WebViewDatabaseClassic.getInstance(context); } + + @Override + public String getDefaultUserAgent(Context context) { + return WebSettingsClassic.getDefaultUserAgentForLocale(context, + Locale.getDefault()); + } } private void onHandleUiEvent(MotionEvent event, int eventType, int flags) { @@ -1646,6 +1647,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mZoomManager.updateMultiTouchSupport(context); } + void updateJavaScriptEnabled(boolean enabled) { + if (isAccessibilityInjectionEnabled()) { + getAccessibilityInjector().updateJavaScriptEnabled(enabled); + } + } + private void init() { OnTrimMemoryListener.init(mContext); mWebView.setWillNotDraw(false); @@ -1657,7 +1664,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mTouchSlopSquare = slop * slop; slop = configuration.getScaledDoubleTapSlop(); mDoubleTapSlopSquare = slop * slop; - final float density = mContext.getResources().getDisplayMetrics().density; + final float density = WebViewCore.getFixedDisplayDensity(mContext); // use one line height, 16 based on our current default font, for how // far we allow a touch be away from the edge of a link mNavSlop = (int) (16 * density); @@ -1759,8 +1766,21 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0)); } - private boolean isAccessibilityEnabled() { - return AccessibilityManager.getInstance(mContext).isEnabled(); + private boolean isAccessibilityInjectionEnabled() { + final AccessibilityManager manager = AccessibilityManager.getInstance(mContext); + if (!manager.isEnabled()) { + return false; + } + + // Accessibility scripts should be injected only when a speaking service + // is enabled. This may need to change later to accommodate Braille. + final List<AccessibilityServiceInfo> services = manager.getEnabledAccessibilityServiceList( + AccessibilityServiceInfo.FEEDBACK_SPOKEN); + if (services.isEmpty()) { + return false; + } + + return true; } private AccessibilityInjector getAccessibilityInjector() { @@ -1789,7 +1809,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /* package */ void adjustDefaultZoomDensity(int zoomDensity) { - final float density = mContext.getResources().getDisplayMetrics().density + final float density = WebViewCore.getFixedDisplayDensity(mContext) * 100 / zoomDensity; updateDefaultZoomDensity(density); } @@ -1866,9 +1886,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mSavePasswordDialog = null; } }) - .setOnCancelListener(new OnCancelListener() { + .setOnDismissListener(new DialogInterface.OnDismissListener() { @Override - public void onCancel(DialogInterface dialog) { + public void onDismiss(DialogInterface dialog) { if (mResumeMsg != null) { resumeMsg.sendToTarget(); mResumeMsg = null; @@ -2073,14 +2093,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc hideSoftKeyboard(); clearActionModes(); dismissFullScreenMode(); - cancelSelectDialog(); + cancelDialogs(); } - private void cancelSelectDialog() { + private void cancelDialogs() { if (mListBoxDialog != null) { mListBoxDialog.cancel(); mListBoxDialog = null; } + if (mSavePasswordDialog != null) { + mSavePasswordDialog.dismiss(); + mSavePasswordDialog = null; + } } /** @@ -2108,15 +2132,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private void destroyJava() { mCallbackProxy.blockMessages(); - clearHelpers(); - if (mListBoxDialog != null) { - mListBoxDialog.dismiss(); - mListBoxDialog = null; - } - if (mSavePasswordDialog != null) { - mSavePasswordDialog.dismiss(); - mSavePasswordDialog = null; - } if (mWebViewCore != null) { // Tell WebViewCore to destroy itself synchronized (this) { @@ -2223,7 +2238,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } // We grab a copy of the back/forward list because a client of WebView // may have invalidated the history list by calling clearHistory. - WebBackForwardList list = copyBackForwardList(); + WebBackForwardListClassic list = copyBackForwardList(); final int currentIndex = list.getCurrentIndex(); final int size = list.getSize(); // We should fail saving the state if the list is empty or the index is @@ -2237,7 +2252,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // arrays. ArrayList<byte[]> history = new ArrayList<byte[]>(size); for (int i = 0; i < size; i++) { - WebHistoryItem item = list.getItemAtIndex(i); + WebHistoryItemClassic item = list.getItemAtIndex(i); if (null == item) { // FIXME: this shouldn't happen // need to determine how item got set to null @@ -2436,7 +2451,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public WebBackForwardList restoreState(Bundle inState) { - WebBackForwardList returnList = null; + WebBackForwardListClassic returnList = null; if (inState == null) { return returnList; } @@ -2444,7 +2459,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mCertificate = SslCertificate.restoreState( inState.getBundle("certificate")); - final WebBackForwardList list = mCallbackProxy.getBackForwardList(); + final WebBackForwardListClassic list = mCallbackProxy.getBackForwardList(); final int index = inState.getInt("index"); // We can't use a clone of the list because we need to modify the // shared copy, so synchronize instead to prevent concurrent @@ -2465,7 +2480,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // the item and thus our history list cannot be rebuilt. return null; } - WebHistoryItem item = new WebHistoryItem(data); + WebHistoryItem item = new WebHistoryItemClassic(data); list.addHistoryItem(item); } // Grab the most recent copy to return to the caller. @@ -2638,7 +2653,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean canGoBack() { - WebBackForwardList l = mCallbackProxy.getBackForwardList(); + WebBackForwardListClassic l = mCallbackProxy.getBackForwardList(); synchronized (l) { if (l.getClearPending()) { return false; @@ -2661,7 +2676,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean canGoForward() { - WebBackForwardList l = mCallbackProxy.getBackForwardList(); + WebBackForwardListClassic l = mCallbackProxy.getBackForwardList(); synchronized (l) { if (l.getClearPending()) { return false; @@ -2684,7 +2699,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean canGoBackOrForward(int steps) { - WebBackForwardList l = mCallbackProxy.getBackForwardList(); + WebBackForwardListClassic l = mCallbackProxy.getBackForwardList(); synchronized (l) { if (l.getClearPending()) { return false; @@ -3332,6 +3347,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } if (mTouchMode == TOUCH_DRAG_LAYER_MODE) { scrollLayerTo(scrollX, scrollY); + animateHandles(); return; } mInOverScrollMode = false; @@ -3352,6 +3368,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mWebViewPrivate.super_scrollTo(scrollX, scrollY); + animateHandles(); + if (mOverScrollGlow != null) { mOverScrollGlow.pullGlow(getScrollX(), getScrollY(), oldX, oldY, maxX, maxY); } @@ -3398,7 +3416,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public String getTouchIconUrl() { - WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); + WebHistoryItemClassic h = mCallbackProxy.getBackForwardList().getCurrentItem(); return h != null ? h.getTouchIconUrl() : null; } @@ -3464,7 +3482,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc nativeSetPauseDrawing(mNativeClass, true); } - cancelSelectDialog(); + cancelDialogs(); WebCoreThreadWatchdog.pause(); } } @@ -3562,7 +3580,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * See {@link WebView#copyBackForwardList()} */ @Override - public WebBackForwardList copyBackForwardList() { + public WebBackForwardListClassic copyBackForwardList() { return mCallbackProxy.getBackForwardList().clone(); } @@ -3570,7 +3588,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * See {@link WebView#setFindListener(WebView.FindListener)}. * @hide */ - public void setFindListener(WebView.FindListener listener) { + @Override + public void setFindListener(WebView.FindListener listener) { mFindListener = listener; } @@ -3593,6 +3612,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return findAllBody(find, false); } + @Override public void findAllAsync(String find) { findAllBody(find, true); } @@ -3631,6 +3651,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * If false and text is non-null, perform a find all. * @return boolean True if the find dialog is shown, false otherwise. */ + @Override public boolean showFindDialog(String text, boolean showIme) { FindActionModeCallback callback = new FindActionModeCallback(mContext); if (mWebView.getParent() == null || mWebView.startActionMode(callback) == null) { @@ -3846,17 +3867,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return; } if (mSelectingText) { - if (mSelectCursorLeftLayerId == mCurrentScrollingLayerId) { - mSelectCursorLeft.offset(dx, dy); - mSelectCursorLeftTextQuad.offset(dx, dy); + if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) { + mSelectCursorBase.offset(dx, dy); + mSelectCursorBaseTextQuad.offset(dx, dy); } - if (mSelectCursorRightLayerId == mCurrentScrollingLayerId) { - mSelectCursorRight.offset(dx, dy); - mSelectCursorRightTextQuad.offset(dx, dy); + if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) { + mSelectCursorExtent.offset(dx, dy); + mSelectCursorExtentTextQuad.offset(dx, dy); } - } else if (mHandleAlpha.getAlpha() > 0) { - // stop fading as we're not going to move with the layer. - mHandleAlphaAnimator.end(); } if (mAutoCompletePopup != null && mCurrentScrollingLayerId == mEditTextLayerId) { @@ -3951,7 +3969,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // reset the flag since we set to true in if need after // loading is see onPageFinished(Url) - if (isAccessibilityEnabled()) { + if (isAccessibilityInjectionEnabled()) { getAccessibilityInjector().onPageStarted(url); } @@ -3966,7 +3984,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /* package */ void onPageFinished(String url) { mZoomManager.onPageFinished(url); - if (isAccessibilityEnabled()) { + if (isAccessibilityInjectionEnabled()) { getAccessibilityInjector().onPageFinished(url); } } @@ -4097,12 +4115,22 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void addJavascriptInterface(Object object, String name) { + if (object == null) { return; } WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData(); + arg.mObject = object; arg.mInterfaceName = name; + + // starting with JELLY_BEAN_MR1, annotations are mandatory for enabling access to + // methods that are accessible from JS. + if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + arg.mRequireAnnotation = true; + } else { + arg.mRequireAnnotation = false; + } mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); } @@ -4472,9 +4500,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } private void onZoomAnimationStart() { - if (!mSelectingText && mHandleAlpha.getAlpha() > 0) { - mHandleAlphaAnimator.end(); - } } private void onZoomAnimationEnd() { @@ -4507,34 +4532,63 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private class SelectionHandleAlpha { private int mAlpha = 0; + private int mTargetAlpha = 0; + public void setAlpha(int alpha) { mAlpha = alpha; - if (mSelectHandleCenter != null) { - mSelectHandleCenter.setAlpha(alpha); - mSelectHandleLeft.setAlpha(alpha); - mSelectHandleRight.setAlpha(alpha); - // TODO: Use partial invalidate - invalidate(); - } + // TODO: Use partial invalidate + invalidate(); } public int getAlpha() { return mAlpha; } + public void setTargetAlpha(int alpha) { + mTargetAlpha = alpha; + } + + public int getTargetAlpha() { + return mTargetAlpha; + } + } private void startSelectingText() { mSelectingText = true; mShowTextSelectionExtra = true; - mHandleAlphaAnimator.setIntValues(255); - mHandleAlphaAnimator.start(); + animateHandles(); + } + + private void animateHandle(boolean canShow, ObjectAnimator animator, + Point selectionPoint, int selectionLayerId, + SelectionHandleAlpha alpha) { + boolean isVisible = canShow && mSelectingText + && ((mSelectionStarted && mSelectDraggingCursor == selectionPoint) + || isHandleVisible(selectionPoint, selectionLayerId)); + int targetValue = isVisible ? 255 : 0; + if (targetValue != alpha.getTargetAlpha()) { + alpha.setTargetAlpha(targetValue); + animator.setIntValues(targetValue); + animator.setDuration(SELECTION_HANDLE_ANIMATION_MS); + animator.start(); + } } + + private void animateHandles() { + boolean canShowBase = mSelectingText; + boolean canShowExtent = mSelectingText && !mIsCaretSelection; + animateHandle(canShowBase, mBaseHandleAlphaAnimator, mSelectCursorBase, + mSelectCursorBaseLayerId, mBaseAlpha); + animateHandle(canShowExtent, mExtentHandleAlphaAnimator, + mSelectCursorExtent, mSelectCursorExtentLayerId, + mExtentAlpha); + } + private void endSelectingText() { mSelectingText = false; mShowTextSelectionExtra = false; - mHandleAlphaAnimator.setIntValues(0); - mHandleAlphaAnimator.start(); + animateHandles(); } private void ensureSelectionHandles() { @@ -4545,55 +4599,76 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc com.android.internal.R.drawable.text_select_handle_left).mutate(); mSelectHandleRight = mContext.getResources().getDrawable( com.android.internal.R.drawable.text_select_handle_right).mutate(); - mHandleAlpha.setAlpha(mHandleAlpha.getAlpha()); - mSelectHandleCenterOffset = new Point(0, - -mSelectHandleCenter.getIntrinsicHeight()); - mSelectHandleLeftOffset = new Point(0, + // All handles have the same height, so we can save effort with + // this assumption. + mSelectOffset = new Point(0, -mSelectHandleLeft.getIntrinsicHeight()); - mSelectHandleRightOffset = new Point( - -mSelectHandleLeft.getIntrinsicWidth() / 2, - -mSelectHandleRight.getIntrinsicHeight()); } } + private void drawHandle(Point point, int handleId, Rect bounds, + int alpha, Canvas canvas) { + int offset; + int width; + int height; + Drawable drawable; + boolean isLeft = nativeIsHandleLeft(mNativeClass, handleId); + if (isLeft) { + drawable = mSelectHandleLeft; + width = mSelectHandleLeft.getIntrinsicWidth(); + height = mSelectHandleLeft.getIntrinsicHeight(); + // Magic formula copied from TextView + offset = (width * 3) / 4; + } else { + drawable = mSelectHandleRight; + width = mSelectHandleRight.getIntrinsicWidth(); + height = mSelectHandleRight.getIntrinsicHeight(); + // Magic formula copied from TextView + offset = width / 4; + } + int x = contentToViewDimension(point.x); + int y = contentToViewDimension(point.y); + bounds.set(x - offset, y, x - offset + width, y + height); + drawable.setBounds(bounds); + drawable.setAlpha(alpha); + drawable.draw(canvas); + } + private void drawTextSelectionHandles(Canvas canvas) { - if (mHandleAlpha.getAlpha() == 0) { + if (mBaseAlpha.getAlpha() == 0 && mExtentAlpha.getAlpha() == 0) { return; } ensureSelectionHandles(); - if (mSelectingText) { - int[] handles = new int[4]; - getSelectionHandles(handles); - int start_x = contentToViewDimension(handles[0]); - int start_y = contentToViewDimension(handles[1]); - int end_x = contentToViewDimension(handles[2]); - int end_y = contentToViewDimension(handles[3]); - - if (mIsCaretSelection) { - // Caret handle is centered - start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2); - mSelectHandleCenter.setBounds(start_x, start_y, - start_x + mSelectHandleCenter.getIntrinsicWidth(), - start_y + mSelectHandleCenter.getIntrinsicHeight()); - } else { - // Magic formula copied from TextView - start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4; - mSelectHandleLeft.setBounds(start_x, start_y, - start_x + mSelectHandleLeft.getIntrinsicWidth(), - start_y + mSelectHandleLeft.getIntrinsicHeight()); - end_x -= mSelectHandleRight.getIntrinsicWidth() / 4; - mSelectHandleRight.setBounds(end_x, end_y, - end_x + mSelectHandleRight.getIntrinsicWidth(), - end_y + mSelectHandleRight.getIntrinsicHeight()); - } - } - if (mIsCaretSelection) { + // Caret handle is centered + int x = contentToViewDimension(mSelectCursorBase.x) - + (mSelectHandleCenter.getIntrinsicWidth() / 2); + int y = contentToViewDimension(mSelectCursorBase.y); + mSelectHandleBaseBounds.set(x, y, + x + mSelectHandleCenter.getIntrinsicWidth(), + y + mSelectHandleCenter.getIntrinsicHeight()); + mSelectHandleCenter.setBounds(mSelectHandleBaseBounds); + mSelectHandleCenter.setAlpha(mBaseAlpha.getAlpha()); mSelectHandleCenter.draw(canvas); } else { - mSelectHandleLeft.draw(canvas); - mSelectHandleRight.draw(canvas); + drawHandle(mSelectCursorBase, HANDLE_ID_BASE, + mSelectHandleBaseBounds, mBaseAlpha.getAlpha(), canvas); + drawHandle(mSelectCursorExtent, HANDLE_ID_EXTENT, + mSelectHandleExtentBounds, mExtentAlpha.getAlpha(), canvas); + } + } + + private boolean isHandleVisible(Point selectionPoint, int layerId) { + boolean isVisible = true; + if (mIsEditingText) { + isVisible = mEditTextContentBounds.contains(selectionPoint.x, + selectionPoint.y); } + if (isVisible) { + isVisible = nativeIsPointVisible(mNativeClass, layerId, + selectionPoint.x, selectionPoint.y); + } + return isVisible; } /** @@ -4601,10 +4676,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * startX, startY, endX, endY */ private void getSelectionHandles(int[] handles) { - handles[0] = mSelectCursorLeft.x; - handles[1] = mSelectCursorLeft.y; - handles[2] = mSelectCursorRight.x; - handles[3] = mSelectCursorRight.y; + handles[0] = mSelectCursorBase.x; + handles[1] = mSelectCursorBase.y; + handles[2] = mSelectCursorExtent.x; + handles[3] = mSelectCursorExtent.y; } // draw history @@ -4839,6 +4914,43 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** + * Sets use of the Geolocation mock client. Also resets that client. Called + * by DRT on UI thread, need to proxy to WebCore thread. + * + * debug only + */ + public void setUseMockGeolocation() { + mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_GEOLOCATION); + } + + /** + * Called by DRT on WebCore thread. + * + * debug only + */ + public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) { + mWebViewCore.setMockGeolocationPosition(latitude, longitude, accuracy); + } + + /** + * Called by DRT on WebCore thread. + * + * debug only + */ + public void setMockGeolocationError(int code, String message) { + mWebViewCore.setMockGeolocationError(code, message); + } + + /** + * Called by DRT on WebCore thread. + * + * debug only + */ + public void setMockGeolocationPermission(boolean allow) { + mWebViewCore.setMockGeolocationPermission(allow); + } + + /** * Called by DRT on WebCore thread. * * debug only @@ -4923,7 +5035,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } // See if the accessibility injector needs to handle this event. - if (isAccessibilityEnabled() + if (isAccessibilityInjectionEnabled() && getAccessibilityInjector().handleKeyEventIfNecessary(event)) { return true; } @@ -5030,7 +5142,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } // See if the accessibility injector needs to handle this event. - if (isAccessibilityEnabled() + if (isAccessibilityInjectionEnabled() && getAccessibilityInjector().handleKeyEventIfNecessary(event)) { return true; } @@ -5071,8 +5183,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc ClipboardManager cm = (ClipboardManager)(mContext .getSystemService(Context.CLIPBOARD_SERVICE)); if (cm.hasPrimaryClip()) { - Point cursorPoint = new Point(contentToViewX(mSelectCursorLeft.x), - contentToViewY(mSelectCursorLeft.y)); + Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x), + contentToViewY(mSelectCursorBase.y)); Point cursorTop = calculateCaretTop(); cursorTop.set(contentToViewX(cursorTop.x), contentToViewY(cursorTop.y)); @@ -5122,12 +5234,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * calculates the top of a caret. */ private Point calculateCaretTop() { - float scale = scaleAlongSegment(mSelectCursorLeft.x, mSelectCursorLeft.y, - mSelectCursorLeftTextQuad.p4, mSelectCursorLeftTextQuad.p3); + float scale = scaleAlongSegment(mSelectCursorBase.x, mSelectCursorBase.y, + mSelectCursorBaseTextQuad.p4, mSelectCursorBaseTextQuad.p3); int x = Math.round(scaleCoordinate(scale, - mSelectCursorLeftTextQuad.p1.x, mSelectCursorLeftTextQuad.p2.x)); + mSelectCursorBaseTextQuad.p1.x, mSelectCursorBaseTextQuad.p2.x)); int y = Math.round(scaleCoordinate(scale, - mSelectCursorLeftTextQuad.p1.y, mSelectCursorLeftTextQuad.p2.y)); + mSelectCursorBaseTextQuad.p1.y, mSelectCursorBaseTextQuad.p2.y)); return new Point(x, y); } @@ -5138,50 +5250,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } private void syncSelectionCursors() { - mSelectCursorLeftLayerId = - nativeGetHandleLayerId(mNativeClass, HANDLE_ID_LEFT, - mSelectCursorLeft, mSelectCursorLeftTextQuad); - mSelectCursorRightLayerId = - nativeGetHandleLayerId(mNativeClass, HANDLE_ID_RIGHT, - mSelectCursorRight, mSelectCursorRightTextQuad); - } - - private void adjustSelectionCursors() { - if (mIsCaretSelection) { - syncSelectionCursors(); - return; // no need to swap left and right handles. - } - - boolean wasDraggingLeft = (mSelectDraggingCursor == mSelectCursorLeft); - int oldX = mSelectDraggingCursor.x; - int oldY = mSelectDraggingCursor.y; - int oldLeftX = mSelectCursorLeft.x; - int oldLeftY = mSelectCursorLeft.y; - int oldRightX = mSelectCursorRight.x; - int oldRightY = mSelectCursorRight.y; - syncSelectionCursors(); - - boolean rightChanged = (oldRightX != mSelectCursorRight.x - || oldRightY != mSelectCursorRight.y); - boolean leftChanged = (oldLeftX != mSelectCursorLeft.x - || oldLeftY != mSelectCursorLeft.y); - if (leftChanged && rightChanged) { - // Left and right switched places, so swap dragging cursor - boolean draggingLeft = !wasDraggingLeft; - mSelectDraggingCursor = (draggingLeft - ? mSelectCursorLeft : mSelectCursorRight); - mSelectDraggingTextQuad = (draggingLeft - ? mSelectCursorLeftTextQuad : mSelectCursorRightTextQuad); - mSelectDraggingOffset = (draggingLeft - ? mSelectHandleLeftOffset : mSelectHandleRightOffset); - } - mSelectDraggingCursor.set(oldX, oldY); - } - - private float distanceSquared(int x, int y, Point p) { - float dx = p.x - x; - float dy = p.y - y; - return (dx * dx) + (dy * dy); + mSelectCursorBaseLayerId = + nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE, + mSelectCursorBase, mSelectCursorBaseTextQuad); + mSelectCursorExtentLayerId = + nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT, + mSelectCursorExtent, mSelectCursorExtentTextQuad); } private boolean setupWebkitSelect() { @@ -5196,18 +5270,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } private void updateWebkitSelection() { - int[] handles = null; - if (mIsCaretSelection) { - mSelectCursorRight.set(mSelectCursorLeft.x, mSelectCursorLeft.y); - } - if (mSelectingText) { - handles = new int[4]; - getSelectionHandles(handles); - } else { - nativeSetTextSelection(mNativeClass, 0); - } + int handleId = (mSelectDraggingCursor == mSelectCursorBase) + ? HANDLE_ID_BASE : HANDLE_ID_EXTENT; mWebViewCore.removeMessages(EventHub.SELECT_TEXT); - mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles); + mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, + mSelectDraggingCursor.x, mSelectDraggingCursor.y, (Integer)handleId); } private void resetCaretTimer() { @@ -5219,14 +5286,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * See {@link WebView#emulateShiftHeld()} - */ - @Override - @Deprecated - public void emulateShiftHeld() { - } - - /** * Select all of the text in this WebView. * * This is an implementation detail. @@ -5313,16 +5372,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * This is an implementation detail. - */ - public SearchBox getSearchBox() { - if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) { - return null; - } - return mWebViewCore.getBrowserFrame().getSearchBox(); - } - - /** * Returns the currently highlighted text as a string. */ String getSelection() { @@ -5334,7 +5383,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc public void onAttachedToWindow() { if (mWebView.hasWindowFocus()) setActive(true); - if (isAccessibilityEnabled()) { + if (isAccessibilityInjectionEnabled()) { getAccessibilityInjector().addAccessibilityApisIfNecessary(); } @@ -5347,7 +5396,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mZoomManager.dismissZoomPicker(); if (mWebView.hasWindowFocus()) setActive(false); - if (isAccessibilityEnabled()) { + if (isAccessibilityInjectionEnabled()) { getAccessibilityInjector().removeAccessibilityApisIfNecessary(); } else { // Ensure the injector is cleared if we're detaching from the window @@ -5570,21 +5619,21 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc Point caretTop = calculateCaretTop(); if (visibleRect.width() < mEditTextContentBounds.width()) { // The whole edit won't fit in the width, so use the caret rect - if (mSelectCursorLeft.x < caretTop.x) { - showRect.left = Math.max(0, mSelectCursorLeft.x - buffer); + if (mSelectCursorBase.x < caretTop.x) { + showRect.left = Math.max(0, mSelectCursorBase.x - buffer); showRect.right = caretTop.x + buffer; } else { showRect.left = Math.max(0, caretTop.x - buffer); - showRect.right = mSelectCursorLeft.x + buffer; + showRect.right = mSelectCursorBase.x + buffer; } } if (visibleRect.height() < mEditTextContentBounds.height()) { // The whole edit won't fit in the height, so use the caret rect - if (mSelectCursorLeft.y > caretTop.y) { + if (mSelectCursorBase.y > caretTop.y) { showRect.top = Math.max(0, caretTop.y - buffer); - showRect.bottom = mSelectCursorLeft.y + buffer; + showRect.bottom = mSelectCursorBase.y + buffer; } else { - showRect.top = Math.max(0, mSelectCursorLeft.y - buffer); + showRect.top = Math.max(0, mSelectCursorBase.y - buffer); showRect.bottom = caretTop.y + buffer; } } @@ -5828,28 +5877,19 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc ensureSelectionHandles(); int shiftedY = y - getTitleHeight() + getScrollY(); int shiftedX = x + getScrollX(); - if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds() - .contains(shiftedX, shiftedY)) { - mSelectionStarted = true; - mSelectDraggingCursor = mSelectCursorLeft; - mSelectDraggingOffset = mSelectHandleCenterOffset; - mSelectDraggingTextQuad = mSelectCursorLeftTextQuad; - mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE); - hidePasteButton(); - } else if (mSelectHandleLeft != null - && mSelectHandleLeft.getBounds() - .contains(shiftedX, shiftedY)) { + if (mSelectHandleBaseBounds.contains(shiftedX, shiftedY)) { mSelectionStarted = true; - mSelectDraggingOffset = mSelectHandleLeftOffset; - mSelectDraggingCursor = mSelectCursorLeft; - mSelectDraggingTextQuad = mSelectCursorLeftTextQuad; - } else if (mSelectHandleRight != null - && mSelectHandleRight.getBounds() + mSelectDraggingCursor = mSelectCursorBase; + mSelectDraggingTextQuad = mSelectCursorBaseTextQuad; + if (mIsCaretSelection) { + mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE); + hidePasteButton(); + } + } else if (mSelectHandleExtentBounds .contains(shiftedX, shiftedY)) { mSelectionStarted = true; - mSelectDraggingOffset = mSelectHandleRightOffset; - mSelectDraggingCursor = mSelectCursorRight; - mSelectDraggingTextQuad = mSelectCursorRightTextQuad; + mSelectDraggingCursor = mSelectCursorExtent; + mSelectDraggingTextQuad = mSelectCursorExtentTextQuad; } else if (mIsCaretSelection) { selectionDone(); } @@ -5894,9 +5934,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } if (deltaX != 0 || deltaY != 0) { int handleX = contentX + - viewToContentDimension(mSelectDraggingOffset.x); + viewToContentDimension(mSelectOffset.x); int handleY = contentY + - viewToContentDimension(mSelectDraggingOffset.y); + viewToContentDimension(mSelectOffset.y); mSelectDraggingCursor.set(handleX, handleY); boolean inCursorText = mSelectDraggingTextQuad.containsPoint(handleX, handleY); @@ -6033,12 +6073,15 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc break; } case MotionEvent.ACTION_UP: { - endScrollEdit(); - if (!mConfirmMove && mIsEditingText && mSelectionStarted && - mIsCaretSelection) { - showPasteWindow(); - stopTouch(); - break; + if (mIsEditingText && mSelectionStarted) { + endScrollEdit(); + mPrivateHandler.sendEmptyMessageDelayed(SCROLL_HANDLE_INTO_VIEW, + TEXT_SCROLL_FIRST_SCROLL_MS); + if (!mConfirmMove && mIsCaretSelection) { + showPasteWindow(); + stopTouch(); + break; + } } mLastTouchUpTime = eventTime; if (mSentAutoScrollMessage) { @@ -6145,6 +6188,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } + private static int getSelectionCoordinate(int coordinate, int min, int max) { + return Math.max(Math.min(coordinate, max), min); + } + private void beginScrollEdit() { if (mLastEditScroll == 0) { mLastEditScroll = SystemClock.uptimeMillis() - @@ -6153,10 +6200,37 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } + private void scrollDraggedSelectionHandleIntoView() { + if (mSelectDraggingCursor == null) { + return; + } + int x = mSelectDraggingCursor.x; + int y = mSelectDraggingCursor.y; + if (!mEditTextContentBounds.contains(x,y)) { + int left = Math.min(0, x - mEditTextContentBounds.left - EDIT_RECT_BUFFER); + int right = Math.max(0, x - mEditTextContentBounds.right + EDIT_RECT_BUFFER); + int deltaX = left + right; + int above = Math.min(0, y - mEditTextContentBounds.top - EDIT_RECT_BUFFER); + int below = Math.max(0, y - mEditTextContentBounds.bottom + EDIT_RECT_BUFFER); + int deltaY = above + below; + if (deltaX != 0 || deltaY != 0) { + int scrollX = getTextScrollX() + deltaX; + int scrollY = getTextScrollY() + deltaY; + scrollX = clampBetween(scrollX, 0, getMaxTextScrollX()); + scrollY = clampBetween(scrollY, 0, getMaxTextScrollY()); + scrollEditText(scrollX, scrollY); + } + } + } + private void endScrollEdit() { mLastEditScroll = 0; } + private static int clampBetween(int value, int min, int max) { + return Math.max(min, Math.min(value, max)); + } + private static int getTextScrollDelta(float speed, long deltaT) { float distance = speed * deltaT; int intDistance = (int)Math.floor(distance); @@ -6172,10 +6246,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ private void scrollEditWithCursor() { if (mLastEditScroll != 0) { - int x = viewToContentX(mLastTouchX + getScrollX() + mSelectDraggingOffset.x); + int x = viewToContentX(mLastTouchX + getScrollX() + mSelectOffset.x); float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left, mEditTextContentBounds.right); - int y = viewToContentY(mLastTouchY + getScrollY() + mSelectDraggingOffset.y); + int y = viewToContentY(mLastTouchY + getScrollY() + mSelectOffset.y); float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top, mEditTextContentBounds.bottom); if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) { @@ -6185,24 +6259,27 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc long timeSinceLastUpdate = currentTime - mLastEditScroll; int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate); int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate); + int scrollX = getTextScrollX() + deltaX; + scrollX = clampBetween(scrollX, 0, getMaxTextScrollX()); + int scrollY = getTextScrollY() + deltaY; + scrollY = clampBetween(scrollY, 0, getMaxTextScrollY()); + mLastEditScroll = currentTime; - if (deltaX == 0 && deltaY == 0) { + if (scrollX == getTextScrollX() && scrollY == getTextScrollY()) { // By probability no text scroll this time. Try again later. mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT, TEXT_SCROLL_FIRST_SCROLL_MS); } else { - int scrollX = getTextScrollX() + deltaX; - scrollX = Math.min(getMaxTextScrollX(), scrollX); - scrollX = Math.max(0, scrollX); - int scrollY = getTextScrollY() + deltaY; - scrollY = Math.min(getMaxTextScrollY(), scrollY); - scrollY = Math.max(0, scrollY); - scrollEditText(scrollX, scrollY); - int cursorX = mSelectDraggingCursor.x; - int cursorY = mSelectDraggingCursor.y; - mSelectDraggingCursor.set(x - deltaX, y - deltaY); + int selectionX = getSelectionCoordinate(x, + mEditTextContentBounds.left, mEditTextContentBounds.right); + int selectionY = getSelectionCoordinate(y, + mEditTextContentBounds.top, mEditTextContentBounds.bottom); + int oldX = mSelectDraggingCursor.x; + int oldY = mSelectDraggingCursor.y; + mSelectDraggingCursor.set(selectionX, selectionY); updateWebkitSelection(); - mSelectDraggingCursor.set(cursorX, cursorY); + scrollEditText(scrollX, scrollY); + mSelectDraggingCursor.set(oldX, oldY); } } } @@ -6258,10 +6335,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // scrolling. The rectangle is in document coordinates. final int maxX = mScrollingLayerRect.right; final int maxY = mScrollingLayerRect.bottom; - final int resultX = Math.max(0, - Math.min(mScrollingLayerRect.left + contentX, maxX)); - final int resultY = Math.max(0, - Math.min(mScrollingLayerRect.top + contentY, maxY)); + final int resultX = clampBetween(maxX, 0, + mScrollingLayerRect.left + contentX); + final int resultY = clampBetween(maxY, 0, + mScrollingLayerRect.top + contentY); if (resultX != mScrollingLayerRect.left || resultY != mScrollingLayerRect.top @@ -6362,10 +6439,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc int x = Math.round(newX); int y = Math.round(newY); if (mIsEditingText) { - x = Math.max(mEditTextContentBounds.left, - Math.min(mEditTextContentBounds.right, x)); - y = Math.max(mEditTextContentBounds.top, - Math.min(mEditTextContentBounds.bottom, y)); + x = clampBetween(x, mEditTextContentBounds.left, + mEditTextContentBounds.right); + y = clampBetween(y, mEditTextContentBounds.top, + mEditTextContentBounds.bottom); } mSelectDraggingCursor.set(x, y); } @@ -6394,9 +6471,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mWebViewPrivate.getVerticalScrollFactor()); final int hdelta = (int) (hscroll * mWebViewPrivate.getHorizontalScrollFactor()); - if (pinScrollBy(hdelta, vdelta, false, 0)) { - return true; - } + + abortAnimation(); + int oldTouchMode = mTouchMode; + startScrollingLayer(event.getX(), event.getY()); + doDrag(hdelta, vdelta); + mTouchMode = oldTouchMode; + return true; } } } @@ -6428,9 +6509,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private long mTrackballUpTime = 0; private long mLastCursorTime = 0; private Rect mLastCursorBounds; - private SelectionHandleAlpha mHandleAlpha = new SelectionHandleAlpha(); - private ObjectAnimator mHandleAlphaAnimator = - ObjectAnimator.ofInt(mHandleAlpha, "alpha", 0); + private SelectionHandleAlpha mBaseAlpha = new SelectionHandleAlpha(); + private SelectionHandleAlpha mExtentAlpha = new SelectionHandleAlpha(); + private ObjectAnimator mBaseHandleAlphaAnimator = + ObjectAnimator.ofInt(mBaseAlpha, "alpha", 0); + private ObjectAnimator mExtentHandleAlphaAnimator = + ObjectAnimator.ofInt(mExtentAlpha, "alpha", 0); // Set by default; BrowserActivity clears to interpret trackball data // directly for movement. Currently, the framework only passes @@ -6440,6 +6524,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private DrawData mDelaySetPicture; private DrawData mLoadedPicture; + @Override public void setMapTrackballToArrowKeys(boolean setMap) { mMapTrackballToArrowKeys = setMap; } @@ -6666,6 +6751,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } + @Override public void flingScroll(int vx, int vy) { mScroller.fling(getScrollX(), getScrollY(), vx, vy, 0, computeMaxScrollX(), 0, computeMaxScrollY(), mOverflingDistance, mOverflingDistance); @@ -6918,6 +7004,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override public boolean requestFocus(int direction, Rect previouslyFocusedRect) { + // Check if we are destroyed + if (mWebViewCore == null) return false; // FIXME: If a subwindow is showing find, and the user touches the // background window, it can steal focus. if (mFindIsUp) return false; @@ -7227,11 +7315,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // nativeCreate sets mNativeClass to a non-zero value String drawableDir = BrowserFrame.getRawResFilename( BrowserFrame.DRAWABLEDIR, mContext); - WindowManager windowManager = - (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - Display display = windowManager.getDefaultDisplay(); - nativeCreate(msg.arg1, drawableDir, - ActivityManager.isHighEndGfx(display)); + nativeCreate(msg.arg1, drawableDir, ActivityManager.isHighEndGfx()); if (mDelaySetPicture != null) { setNewPicture(mDelaySetPicture, true); mDelaySetPicture = null; @@ -7317,15 +7401,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mWebView.setKeepScreenOn(msg.arg1 == 1); break; - case ENTER_FULLSCREEN_VIDEO: - int layerId = msg.arg1; - - String url = (String) msg.obj; - if (mHTML5VideoViewProxy != null) { - mHTML5VideoViewProxy.enterFullScreenVideo(layerId, url); - } - break; - case EXIT_FULLSCREEN_VIDEO: if (mHTML5VideoViewProxy != null) { mHTML5VideoViewProxy.exitFullScreenVideo(); @@ -7405,7 +7480,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc break; case SELECTION_STRING_CHANGED: - if (isAccessibilityEnabled()) { + if (isAccessibilityInjectionEnabled()) { getAccessibilityInjector() .handleSelectionChangedIfNecessary((String) msg.obj); } @@ -7464,7 +7539,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mEditTextLayerId = initData.mNodeLayerId; nativeMapLayerRect(mNativeClass, mEditTextLayerId, mEditTextContentBounds); - mEditTextContent.set(initData.mContentRect); + mEditTextContent.set(initData.mClientRect); relocateAutoCompletePopup(); } break; @@ -7545,6 +7620,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc scrollEditWithCursor(); break; + case SCROLL_HANDLE_INTO_VIEW: + scrollDraggedSelectionHandleIntoView(); + break; + default: super.handleMessage(msg); break; @@ -7580,8 +7659,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc .contains(x, y); } else { isPressingHandle = - mSelectHandleLeft.getBounds().contains(x, y) - || mSelectHandleRight.getBounds().contains(x, y); + mSelectHandleBaseBounds.contains(x, y) + || mSelectHandleExtentBounds.contains(x, y); } return isPressingHandle; } @@ -7869,7 +7948,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc int functor = 0; boolean forceInval = isPictureAfterFirstLayout; ViewRootImpl viewRoot = mWebView.getViewRootImpl(); - if (mWebView.isHardwareAccelerated() && viewRoot != null) { + if (mWebView.isHardwareAccelerated() + && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE + && viewRoot != null) { functor = nativeGetDrawGLFunction(mNativeClass); if (functor != 0) { // force an invalidate if functor attach not successful @@ -7928,8 +8009,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (data.mSelectTextPtr != 0 && (data.mStart != data.mEnd || - (mFieldPointer == nodePointer && mFieldPointer != 0))) { - mIsCaretSelection = (data.mStart == data.mEnd); + (mFieldPointer == nodePointer && mFieldPointer != 0) || + (nodePointer == 0 && data.mStart == 0 && data.mEnd == 0))) { + mIsEditingText = (mFieldPointer == nodePointer) && nodePointer != 0; + mIsCaretSelection = (data.mStart == data.mEnd && nodePointer != 0); if (mIsCaretSelection && (mInputConnection == null || mInputConnection.getEditable().length() == 0)) { @@ -7938,11 +8021,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } else { if (!mSelectingText) { setupWebkitSelect(); - } else if (!mSelectionStarted) { - syncSelectionCursors(); } else { - adjustSelectionCursors(); + syncSelectionCursors(); } + animateHandles(); if (mIsCaretSelection) { resetCaretTimer(); } @@ -7958,8 +8040,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc float maxScrollX = getMaxTextScrollX(); float scrollPercentX = ((float)scrollX)/maxScrollX; mEditTextContent.offsetTo(-scrollX, -scrollY); - mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0, + mWebViewCore.removeMessages(EventHub.SCROLL_TEXT_INPUT); + mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 0, scrollY, (Float)scrollPercentX); + animateHandles(); } private void beginTextBatch() { @@ -8404,14 +8488,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * See {@link WebView#debugDump()} - */ - @Override - @Deprecated - public void debugDump() { - } - - /** * Enable the communication b/t the webView and VideoViewProxy * * only used by the Browser @@ -8545,6 +8621,54 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc WebViewCore.setShouldMonitorWebCoreThread(); } + @Override + public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) { + int layer = getBaseLayer(); + if (layer != 0) { + try { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + ViewStateSerializer.dumpLayerHierarchy(layer, stream, level); + stream.close(); + byte[] buf = stream.toByteArray(); + out.write(new String(buf, "ascii")); + } catch (IOException e) {} + } + } + + @Override + public View findHierarchyView(String className, int hashCode) { + if (mNativeClass == 0) return null; + Picture pic = new Picture(); + if (!nativeDumpLayerContentToPicture(mNativeClass, className, hashCode, pic)) { + return null; + } + return new PictureWrapperView(getContext(), pic, mWebView); + } + + private static class PictureWrapperView extends View { + Picture mPicture; + WebView mWebView; + + public PictureWrapperView(Context context, Picture picture, WebView parent) { + super(context); + mPicture = picture; + mWebView = parent; + setWillNotDraw(false); + setRight(mPicture.getWidth()); + setBottom(mPicture.getHeight()); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawPicture(mPicture); + } + + @Override + public boolean post(Runnable action) { + return mWebView.post(action); + } + } + private native void nativeCreate(int ptr, String drawableDir, boolean isHighEndGfx); private native void nativeDebugDump(); private static native void nativeDestroy(int ptr); @@ -8565,6 +8689,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc int scrollingLayer); private native int nativeGetBaseLayer(int nativeInstance); private native void nativeCopyBaseContentToPicture(Picture pict); + private native boolean nativeDumpLayerContentToPicture(int nativeInstance, + String className, int layerId, Picture pict); private native boolean nativeHasContent(); private native void nativeStopGL(int ptr); private native void nativeDiscardAllTextures(); @@ -8608,4 +8734,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private static native int nativeSetHwAccelerated(int instance, boolean hwAccelerated); private static native void nativeFindMaxVisibleRect(int instance, int layerId, Rect visibleContentRect); + private static native boolean nativeIsHandleLeft(int instance, int handleId); + private static native boolean nativeIsPointVisible(int instance, + int layerId, int contentX, int contentY); } diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 6aff10a..08a046a 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -204,43 +204,16 @@ public class WebViewClient { } /** - * Notify the host application that an SSL error occurred while loading a - * resource, but the WebView chose to proceed anyway based on a - * decision retained from a previous response to onReceivedSslError(). - * @hide - */ - public void onProceededAfterSslError(WebView view, SslError error) { - } - - /** - * Notify the host application to handle a SSL client certificate - * request (display the request to the user and ask whether to - * proceed with a client certificate or not). The host application - * has to call either handler.cancel() or handler.proceed() as the - * connection is suspended and waiting for the response. The - * default behavior is to cancel, returning no client certificate. - * - * @param view The WebView that is initiating the callback. - * @param handler A ClientCertRequestHandler object that will - * handle the user's response. - * @param host_and_port The host and port of the requesting server. + * Notifies the host application that the WebView received an HTTP + * authentication request. The host application can use the supplied + * {@link HttpAuthHandler} to set the WebView's response to the request. + * The default behavior is to cancel the request. * - * @hide - */ - public void onReceivedClientCertRequest(WebView view, - ClientCertRequestHandler handler, String host_and_port) { - handler.cancel(); - } - - /** - * Notify the host application to handle an authentication request. The - * default behavior is to cancel the request. - * - * @param view The WebView that is initiating the callback. - * @param handler The HttpAuthHandler that will handle the user's response. - * @param host The host requiring authentication. - * @param realm A description to help store user credentials for future - * visits. + * @param view the WebView that is initiating the callback + * @param handler the HttpAuthHandler used to set the WebView's response + * @param host the host requiring authentication + * @param realm the realm for which authentication is required + * @see Webview#getHttpAuthUsernamePassword */ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { diff --git a/core/java/android/webkit/WebViewClientClassicExt.java b/core/java/android/webkit/WebViewClientClassicExt.java new file mode 100644 index 0000000..a873585 --- /dev/null +++ b/core/java/android/webkit/WebViewClientClassicExt.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +import android.net.http.SslError; + +/** + * Adds WebViewClassic specific extension methods to the WebViewClient callback class. + * These are not part of the public WebView API, so the class is hidden. + * @hide + */ +public class WebViewClientClassicExt extends WebViewClient { + + /** + * Notify the host application that an SSL error occurred while loading a + * resource, but the WebView chose to proceed anyway based on a + * decision retained from a previous response to onReceivedSslError(). + */ + public void onProceededAfterSslError(WebView view, SslError error) { + } + + /** + * Notify the host application to handle a SSL client certificate + * request (display the request to the user and ask whether to + * proceed with a client certificate or not). The host application + * has to call either handler.cancel() or handler.proceed() as the + * connection is suspended and waiting for the response. The + * default behavior is to cancel, returning no client certificate. + * + * @param view The WebView that is initiating the callback. + * @param handler A ClientCertRequestHandler object that will + * handle the user's response. + * @param host_and_port The host and port of the requesting server. + */ + public void onReceivedClientCertRequest(WebView view, + ClientCertRequestHandler handler, String host_and_port) { + handler.cancel(); + } +} diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 728ddbf..3fb3ec6 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -132,6 +132,8 @@ public final class WebViewCore { private int mRestoredX = 0; private int mRestoredY = 0; + private MockGeolocation mMockGeolocation = new MockGeolocation(this); + private DeviceMotionAndOrientationManager mDeviceMotionAndOrientationManager = new DeviceMotionAndOrientationManager(this); private DeviceMotionService mDeviceMotionService; @@ -441,7 +443,7 @@ public final class WebViewCore { } /** - * Notify the browser that the origin has exceeded it's database quota. + * Notify the embedding application that the origin has exceeded it's database quota. * @param url The URL that caused the overflow. * @param databaseIdentifier The identifier of the database. * @param quota The current quota for the origin. @@ -466,12 +468,15 @@ public final class WebViewCore { } /** - * Notify the browser that the appcache has exceeded its max size. + * Notify the embedding application that the appcache has reached or exceeded its maximum + * allowed storage size. + * * @param requiredStorage is the amount of storage, in bytes, that would be * needed in order for the last appcache operation to succeed. + * @param maxSize maximum allowed Application Cache database size, in bytes. */ - protected void reachedMaxAppCacheSize(long requiredStorage) { - mCallbackProxy.onReachedMaxAppCacheSize(requiredStorage, getUsedQuota(), + protected void reachedMaxAppCacheSize(long requiredStorage, long maxSize) { + mCallbackProxy.onReachedMaxAppCacheSize(requiredStorage, maxSize, new WebStorage.QuotaUpdater() { @Override public void updateQuota(long newQuota) { @@ -560,24 +565,6 @@ public final class WebViewCore { } /** - * Notify the webview that this is an installable web app. - */ - protected void setInstallableWebApp() { - mCallbackProxy.setInstallableWebApp(); - } - - /** - * Notify the webview that we want to display the video layer fullscreen. - */ - protected void enterFullscreenForVideoLayer(int layerId, String url) { - if (mWebViewClassic == null) return; - Message message = Message.obtain(mWebViewClassic.mPrivateHandler, - WebViewClassic.ENTER_FULLSCREEN_VIDEO, layerId, 0); - message.obj = url; - message.sendToTarget(); - } - - /** * Notify the webview that we want to exit the video fullscreen. * This is called through JNI by webcore. */ @@ -619,8 +606,6 @@ public final class WebViewCore { */ private native void nativeNotifyAnimationStarted(int nativeClass); - private native boolean nativeFocusBoundsChanged(int nativeClass); - private native boolean nativeKey(int nativeClass, int keyCode, int unichar, int repeatCount, boolean isShift, boolean isAlt, boolean isSym, boolean isDown); @@ -839,6 +824,7 @@ public final class WebViewCore { static class JSInterfaceData { Object mObject; String mInterfaceName; + boolean mRequireAnnotation; } static class JSKeyData { @@ -960,7 +946,7 @@ public final class WebViewCore { public int mMaxLength; public Rect mContentBounds; public int mNodeLayerId; - public Rect mContentRect; + public Rect mClientRect; } // mAction of TouchEventData can be MotionEvent.getAction() which uses the @@ -1199,6 +1185,7 @@ public final class WebViewCore { static final int SET_INITIAL_FOCUS = 224; static final int SAVE_VIEW_STATE = 225; + static final int SET_USE_MOCK_GEOLOCATION = 226; // Private handler for WebCore messages. private Handler mHandler; @@ -1306,13 +1293,8 @@ public final class WebViewCore { } else { xPercent = ((Float) msg.obj).floatValue(); } - Rect contentBounds = new Rect(); nativeScrollFocusedTextInput(mNativeClass, xPercent, - msg.arg2, contentBounds); - Message.obtain( - mWebViewClassic.mPrivateHandler, - WebViewClassic.UPDATE_CONTENT_BOUNDS, - contentBounds).sendToTarget(); + msg.arg2); break; case LOAD_URL: { @@ -1508,7 +1490,7 @@ public final class WebViewCore { case ADD_JS_INTERFACE: JSInterfaceData jsData = (JSInterfaceData) msg.obj; mBrowserFrame.addJavascriptInterface(jsData.mObject, - jsData.mInterfaceName); + jsData.mInterfaceName, jsData.mRequireAnnotation); break; case REMOVE_JS_INTERFACE: @@ -1660,6 +1642,10 @@ public final class WebViewCore { (Set<String>) msg.obj); break; + case SET_USE_MOCK_GEOLOCATION: + setUseMockGeolocation(); + break; + case SET_USE_MOCK_DEVICE_ORIENTATION: setUseMockDeviceOrientation(); break; @@ -1708,13 +1694,9 @@ public final class WebViewCore { nativeInsertText(mNativeClass, (String) msg.obj); break; case SELECT_TEXT: { - int[] args = (int[]) msg.obj; - if (args == null) { - nativeClearTextSelection(mNativeClass); - } else { - nativeSelectText(mNativeClass, args[0], - args[1], args[2], args[3]); - } + int handleId = (Integer) msg.obj; + nativeSelectText(mNativeClass, handleId, + msg.arg1, msg.arg2); break; } case SELECT_WORD_AT: { @@ -2139,8 +2121,8 @@ public final class WebViewCore { return width; } - // Utility method for exceededDatabaseQuota and reachedMaxAppCacheSize - // callbacks. Computes the sum of database quota for all origins. + // Utility method for exceededDatabaseQuota callback. Computes the sum + // of WebSQL database quota for all origins. private long getUsedQuota() { WebStorageClassic webStorage = WebStorageClassic.getInstance(); Collection<WebStorage.Origin> origins = webStorage.getOriginsSync(); @@ -2192,7 +2174,6 @@ public final class WebViewCore { // only non-null if it is for the first picture set after the first layout ViewState mViewState; boolean mFirstLayoutForNonStandardLoad; - boolean mFocusSizeChanged; } DrawData mLastDrawData = null; @@ -2247,7 +2228,6 @@ public final class WebViewCore { private void webkitDraw(DrawData draw) { if (mWebViewClassic != null) { - draw.mFocusSizeChanged = nativeFocusBoundsChanged(mNativeClass); draw.mViewSize = new Point(mCurrentViewWidth, mCurrentViewHeight); if (mSettings.getUseWideViewPort()) { draw.mMinPrefWidth = Math.max( @@ -2330,7 +2310,6 @@ public final class WebViewCore { Log.w(LOGTAG, "Cannot pauseUpdatePicture, core destroyed or not initialized!"); return; } - core.nativeSetIsPaused(core.mNativeClass, true); core.mDrawIsPaused = true; } } @@ -2348,7 +2327,6 @@ public final class WebViewCore { Log.w(LOGTAG, "Cannot resumeUpdatePicture, core destroyed!"); return; } - core.nativeSetIsPaused(core.mNativeClass, false); core.mDrawIsPaused = false; // always redraw on resume to reenable gif animations core.mDrawIsScheduled = false; @@ -2363,13 +2341,13 @@ public final class WebViewCore { ////////////////////////////////////////////////////////////////////////// private void restoreState(int index) { - WebBackForwardList list = mCallbackProxy.getBackForwardList(); + WebBackForwardListClassic list = mCallbackProxy.getBackForwardList(); int size = list.getSize(); for (int i = 0; i < size; i++) { list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame); } mBrowserFrame.mLoadInitFromJava = true; - list.restoreIndex(mBrowserFrame.mNativeFrame, index); + WebBackForwardListClassic.restoreIndex(mBrowserFrame.mNativeFrame, index); mBrowserFrame.mLoadInitFromJava = false; } @@ -2501,6 +2479,13 @@ public final class WebViewCore { setupViewport(true); } + static float getFixedDisplayDensity(Context context) { + // We make bad assumptions about multiplying and dividing density by 100, + // force them to be true with this hack + float density = context.getResources().getDisplayMetrics().density; + return ((int) (density * 100)) / 100.0f; + } + private void setupViewport(boolean updateViewState) { if (mWebViewClassic == null || mSettings == null) { // We've been destroyed or are being destroyed, return early @@ -2545,11 +2530,13 @@ public final class WebViewCore { // adjust the default scale to match the densityDpi float adjust = 1.0f; if (mViewportDensityDpi == -1) { - adjust = mContext.getResources().getDisplayMetrics().density; + adjust = getFixedDisplayDensity(mContext); } else if (mViewportDensityDpi > 0) { adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi / mViewportDensityDpi; + adjust = ((int) (adjust * 100)) / 100.0f; } + // Remove any update density messages in flight. // If the density is indeed different from WebView's default scale, // a new message will be queued. @@ -2787,14 +2774,11 @@ public final class WebViewCore { } // called by JNI - private void updateTextfield(int ptr, boolean changeToPassword, - String text, int textGeneration) { + private void updateTextfield(int ptr, String text, int textGeneration) { if (mWebViewClassic != null) { - Message msg = Message.obtain(mWebViewClassic.mPrivateHandler, + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr, - textGeneration, text); - msg.getData().putBoolean("password", changeToPassword); - msg.sendToTarget(); + textGeneration, text).sendToTarget(); } } @@ -2855,7 +2839,7 @@ public final class WebViewCore { * Scroll the focused textfield to (xPercent, y) in document space */ private native void nativeScrollFocusedTextInput(int nativeClass, - float xPercent, int y, Rect contentBounds); + float xPercent, int y); // these must be in document space (i.e. not scaled/zoomed). private native void nativeSetScrollOffset(int nativeClass, @@ -3063,6 +3047,22 @@ public final class WebViewCore { mDeviceMotionAndOrientationManager.setUseMock(); } + private void setUseMockGeolocation() { + mMockGeolocation.setUseMock(); + } + + public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) { + mMockGeolocation.setPosition(latitude, longitude, accuracy); + } + + public void setMockGeolocationError(int code, String message) { + mMockGeolocation.setError(code, message); + } + + public void setMockGeolocationPermission(boolean allow) { + mMockGeolocation.setPermission(allow); + } + public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { mDeviceMotionAndOrientationManager.setMockOrientation(canProvideAlpha, alpha, @@ -3089,7 +3089,6 @@ public final class WebViewCore { sShouldMonitorWebCoreThread = true; } - private native void nativeSetIsPaused(int nativeClass, boolean isPaused); private native void nativePause(int nativeClass); private native void nativeResume(int nativeClass); private native void nativeFreeMemory(int nativeClass); @@ -3135,7 +3134,7 @@ public final class WebViewCore { private native String nativeGetText(int nativeClass, int startX, int startY, int endX, int endY); private native void nativeSelectText(int nativeClass, - int startX, int startY, int endX, int endY); + int handleId, int x, int y); private native void nativeClearTextSelection(int nativeClass); private native boolean nativeSelectWordAt(int nativeClass, int x, int y); private native void nativeSelectAll(int nativeClass); diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 9d10d67..5597259 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -23,13 +23,15 @@ import android.content.Context; * application has stored any of the following types of browsing data and * to clear any such stored data for all WebViews in the application. * <ul> - * <li>Username/password pairs entered into web forms</li> + * <li>Username/password pairs for web forms</li> * <li>HTTP authentication username/password pairs</li> * <li>Data entered into text fields (e.g. for autocomplete suggestions)</li> * </ul> */ public class WebViewDatabase { - // TODO: deprecate/hide this. + /** + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ protected static final String LOGTAG = "webviewdatabase"; /** @@ -38,55 +40,70 @@ public class WebViewDatabase { protected WebViewDatabase() { } - public static synchronized WebViewDatabase getInstance(Context context) { + public static WebViewDatabase getInstance(Context context) { return WebViewFactory.getProvider().getWebViewDatabase(context); } /** - * Gets whether there are any username/password combinations - * from web pages saved. + * Gets whether there are any saved username/password pairs for web forms. + * Note that these are unrelated to HTTP authentication credentials. * - * @return true if there are any username/passwords used in web - * forms saved + * @return true if there are any saved username/password pairs + * @see WebView#savePassword + * @see clearUsernamePassword */ public boolean hasUsernamePassword() { throw new MustOverrideException(); } /** - * Clears any username/password combinations saved from web forms. + * Clears any saved username/password pairs for web forms. + * Note that these are unrelated to HTTP authentication credentials. + * + * @see WebView#savePassword + * @see hasUsernamePassword */ public void clearUsernamePassword() { throw new MustOverrideException(); } /** - * Gets whether there are any HTTP authentication username/password combinations saved. + * Gets whether there are any saved credentials for HTTP authentication. * - * @return true if there are any HTTP authentication username/passwords saved + * @return whether there are any saved credentials + * @see Webview#getHttpAuthUsernamePassword + * @see Webview#setHttpAuthUsernamePassword + * @see clearHttpAuthUsernamePassword */ public boolean hasHttpAuthUsernamePassword() { throw new MustOverrideException(); } /** - * Clears any HTTP authentication username/passwords that are saved. + * Clears any saved credentials for HTTP authentication. + * + * @see Webview#getHttpAuthUsernamePassword + * @see Webview#setHttpAuthUsernamePassword + * @see hasHttpAuthUsernamePassword */ public void clearHttpAuthUsernamePassword() { throw new MustOverrideException(); } /** - * Gets whether there is any previously-entered form data saved. + * Gets whether there is any saved data for web forms. * - * @return true if there is form data saved + * @return whether there is any saved data for web forms + * @see clearFormData */ public boolean hasFormData() { throw new MustOverrideException(); } /** - * Clears any stored previously-entered form data. + * Clears any saved data for web forms. + * + * @see hasFormData */ public void clearFormData() { throw new MustOverrideException(); diff --git a/core/java/android/webkit/WebViewDatabaseClassic.java b/core/java/android/webkit/WebViewDatabaseClassic.java index 9b1d4cb..be01028 100644 --- a/core/java/android/webkit/WebViewDatabaseClassic.java +++ b/core/java/android/webkit/WebViewDatabaseClassic.java @@ -52,6 +52,7 @@ final class WebViewDatabaseClassic extends WebViewDatabase { // implemented for b/5265606. private static WebViewDatabaseClassic sInstance = null; + private static final Object sInstanceLock = new Object(); private static SQLiteDatabase sDatabase = null; @@ -99,7 +100,8 @@ final class WebViewDatabaseClassic extends WebViewDatabase { // Initially true until the background thread completes. private boolean mInitialized = false; - WebViewDatabaseClassic(final Context context) { + private WebViewDatabaseClassic(final Context context) { + JniUtil.setContext(context); new Thread() { @Override public void run() { @@ -110,11 +112,13 @@ final class WebViewDatabaseClassic extends WebViewDatabase { // Singleton only, use getInstance() } - public static synchronized WebViewDatabaseClassic getInstance(Context context) { - if (sInstance == null) { - sInstance = new WebViewDatabaseClassic(context); + public static WebViewDatabaseClassic getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + sInstance = new WebViewDatabaseClassic(context); + } + return sInstance; } - return sInstance; } private synchronized void init(Context context) { diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 73ae910..b833a01 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -16,14 +16,23 @@ package android.webkit; +import android.os.Build; +import android.os.StrictMode; +import android.os.SystemProperties; import android.util.Log; +import dalvik.system.PathClassLoader; + /** * Top level factory, used creating all the main WebView implementation classes. */ class WebViewFactory { // Default Provider factory class name. - private static final String DEFAULT_WEB_VIEW_FACTORY = "android.webkit.WebViewClassic$Factory"; + // TODO: When the Chromium powered WebView is ready, it should be the default factory class. + private static final String DEFAULT_WEBVIEW_FACTORY = "android.webkit.WebViewClassic$Factory"; + private static final String CHROMIUM_WEBVIEW_FACTORY = + "com.android.webviewchromium.WebViewChromiumFactoryProvider"; + private static final String CHROMIUM_WEBVIEW_JAR = "/system/framework/webviewchromium.jar"; private static final String LOGTAG = "WebViewFactory"; @@ -32,24 +41,54 @@ class WebViewFactory { // Cache the factory both for efficiency, and ensure any one process gets all webviews from the // same provider. private static WebViewFactoryProvider sProviderInstance; + private static final Object sProviderLock = new Object(); + + static WebViewFactoryProvider getProvider() { + synchronized (sProviderLock) { + // For now the main purpose of this function (and the factory abstraction) is to keep + // us honest and minimize usage of WebViewClassic internals when binding the proxy. + if (sProviderInstance != null) return sProviderInstance; - static synchronized WebViewFactoryProvider getProvider() { - // For now the main purpose of this function (and the factory abstraction) is to keep - // us honest and minimize usage of WebViewClassic internals when binding the proxy. - if (sProviderInstance != null) return sProviderInstance; + // For debug builds, we allow a system property to specify that we should use the + // Chromium powered WebView. This enables us to switch between implementations + // at runtime. For user (release) builds, don't allow this. + if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("webview.use_chromium", false)) { + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + sProviderInstance = loadChromiumProvider(); + if (DEBUG) Log.v(LOGTAG, "Loaded Chromium provider: " + sProviderInstance); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } - sProviderInstance = getFactoryByName(DEFAULT_WEB_VIEW_FACTORY); - if (sProviderInstance == null) { - if (DEBUG) Log.v(LOGTAG, "Falling back to explicit linkage"); - sProviderInstance = new WebViewClassic.Factory(); + if (sProviderInstance == null) { + if (DEBUG) Log.v(LOGTAG, "Falling back to default provider: " + + DEFAULT_WEBVIEW_FACTORY); + sProviderInstance = getFactoryByName(DEFAULT_WEBVIEW_FACTORY, + WebViewFactory.class.getClassLoader()); + if (sProviderInstance == null) { + if (DEBUG) Log.v(LOGTAG, "Falling back to explicit linkage"); + sProviderInstance = new WebViewClassic.Factory(); + } + } + return sProviderInstance; } - return sProviderInstance; } - private static WebViewFactoryProvider getFactoryByName(String providerName) { + // TODO: This allows us to have the legacy and Chromium WebView coexist for development + // and side-by-side testing. After transition, remove this when no longer required. + private static WebViewFactoryProvider loadChromiumProvider() { + ClassLoader clazzLoader = new PathClassLoader(CHROMIUM_WEBVIEW_JAR, null, + WebViewFactory.class.getClassLoader()); + return getFactoryByName(CHROMIUM_WEBVIEW_FACTORY, clazzLoader); + } + + private static WebViewFactoryProvider getFactoryByName(String providerName, + ClassLoader loader) { try { if (DEBUG) Log.v(LOGTAG, "attempt to load class " + providerName); - Class<?> c = Class.forName(providerName); + Class<?> c = Class.forName(providerName, true, loader); if (DEBUG) Log.v(LOGTAG, "instantiating factory"); return (WebViewFactoryProvider) c.newInstance(); } catch (ClassNotFoundException e) { diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index 1d302f1..934ef83 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -42,6 +42,12 @@ public interface WebViewFactoryProvider { * {@link android.webkit.WebView#disablePlatformNotifications()} */ void setPlatformNotificationsEnabled(boolean enable); + + /** + * Implements the API method: + * {@link android.webkit.WebSettings#getDefaultUserAgent(Context) } + */ + String getDefaultUserAgent(Context context); } Statics getStatics(); diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index 867ee54..c9f9fbd 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -37,6 +37,7 @@ import android.view.inputmethod.InputConnection; import android.webkit.WebView.HitTestResult; import android.webkit.WebView.PictureListener; +import java.io.BufferedWriter; import java.io.File; import java.util.Map; @@ -220,8 +221,6 @@ public interface WebViewProvider { public WebSettings getSettings(); - public void emulateShiftHeld(); - public void setMapTrackballToArrowKeys(boolean setMap); public void flingScroll(int vx, int vy); @@ -236,7 +235,9 @@ public interface WebViewProvider { public boolean zoomOut(); - public void debugDump(); + public void dumpViewHierarchyWithProperties(BufferedWriter out, int level); + + public View findHierarchyView(String className, int hashCode); //------------------------------------------------------------------------- // Provider glue methods diff --git a/core/java/android/webkit/ZoomControlEmbedded.java b/core/java/android/webkit/ZoomControlEmbedded.java index d2a0561..ae19832 100644 --- a/core/java/android/webkit/ZoomControlEmbedded.java +++ b/core/java/android/webkit/ZoomControlEmbedded.java @@ -90,7 +90,7 @@ class ZoomControlEmbedded implements ZoomControlBase { View controls = mZoomButtonsController.getZoomControls(); ViewGroup.LayoutParams params = controls.getLayoutParams(); if (params instanceof FrameLayout.LayoutParams) { - ((FrameLayout.LayoutParams) params).gravity = Gravity.RIGHT; + ((FrameLayout.LayoutParams) params).gravity = Gravity.END; } } return mZoomButtonsController; diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 80a6782..1d864e5 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -287,6 +287,7 @@ class ZoomManager { if (!exceedsMinScaleIncrement(mMinZoomScale, mMaxZoomScale)) { mMaxZoomScale = mMinZoomScale; } + sanitizeMinMaxScales(); } public final float getScale() { @@ -909,6 +910,14 @@ class ZoomManager { } } + private void sanitizeMinMaxScales() { + if (mMinZoomScale > mMaxZoomScale) { + Log.w(LOGTAG, "mMinZoom > mMaxZoom!!! " + mMinZoomScale + " > " + mMaxZoomScale, + new Exception()); + mMaxZoomScale = mMinZoomScale; + } + } + public void onSizeChanged(int w, int h, int ow, int oh) { // reset zoom and anchor to the top left corner of the screen // unless we are already zooming @@ -933,6 +942,7 @@ class ZoomManager { if (mInitialScale > 0 && mInitialScale < mMinZoomScale) { mMinZoomScale = mInitialScale; } + sanitizeMinMaxScales(); } dismissZoomPicker(); @@ -1004,6 +1014,7 @@ class ZoomManager { } else { mMaxZoomScale = viewState.mMaxScale; } + sanitizeMinMaxScales(); } /** @@ -1033,6 +1044,7 @@ class ZoomManager { if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) { mMinZoomScale = newZoomOverviewScale; mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale); + sanitizeMinMaxScales(); } // fit the content width to the current view for the first new picture // after first layout. @@ -1113,6 +1125,7 @@ class ZoomManager { mMinZoomScale = (mInitialScale > 0) ? Math.min(mInitialScale, overviewScale) : overviewScale; mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale); + sanitizeMinMaxScales(); } if (!mWebView.drawHistory()) { diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 437da59..7f0af09 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -40,7 +40,6 @@ import android.util.SparseBooleanArray; import android.util.StateSet; import android.view.ActionMode; import android.view.ContextMenu.ContextMenuInfo; -import android.view.FocusFinder; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.InputDevice; @@ -66,6 +65,7 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionWrapper; import android.view.inputmethod.InputMethodManager; +import android.widget.RemoteViews.OnClickHandler; import com.android.internal.R; @@ -676,6 +676,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te static final Interpolator sLinearInterpolator = new LinearInterpolator(); /** + * The saved state that we will be restoring from when we next sync. + * Kept here so that if we happen to be asked to save our state before + * the sync happens, we can return this existing data rather than losing + * it. + */ + private SavedState mPendingSync; + + /** * Interface definition for a callback to be invoked when the list or grid * has been scrolled. */ @@ -974,6 +982,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // Start selection mode if needed. We don't need to if we're unchecking something. if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) { + if (mMultiChoiceModeCallback == null || + !mMultiChoiceModeCallback.hasWrappedCallback()) { + throw new IllegalStateException("AbsListView: attempted to start selection mode " + + "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " + + "supplied. Call setMultiChoiceModeListener to set a callback."); + } mChoiceActionMode = startActionMode(mMultiChoiceModeCallback); } @@ -1329,150 +1343,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override - public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { - if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) { - switch(direction) { - case ACCESSIBILITY_FOCUS_BACKWARD: { - View focusable = (getChildCount() > 0) ? getChildAt(getChildCount() - 1) : this; - if (focusable.isAccessibilityFocusable()) { - views.add(focusable); - } - } return; - case ACCESSIBILITY_FOCUS_FORWARD: { - if (isAccessibilityFocusable()) { - views.add(this); - } - } return; - } - } - super.addFocusables(views, direction, focusableMode); - } - - @Override - public View focusSearch(int direction) { - return focusSearch(this, direction); - } - - @Override - public View focusSearch(View focused, int direction) { - switch (direction) { - case ACCESSIBILITY_FOCUS_FORWARD: { - // If we are the focused view try giving it to the first child. - if (focused == this) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child.getVisibility() == View.VISIBLE) { - return child; - } - } - return super.focusSearch(this, direction); - } - // Find the item that has the focused view. - final int currentPosition = getPositionForView(focused); - if (currentPosition < 0 || currentPosition >= getCount()) { - return super.focusSearch(this, direction); - } - // Try to advance focus in the current item. - View currentItem = getChildAt(currentPosition - getFirstVisiblePosition()); - if (currentItem.getVisibility() == View.VISIBLE) { - if (currentItem instanceof ViewGroup) { - ViewGroup currentItemGroup = (ViewGroup) currentItem; - View nextFocus = FocusFinder.getInstance().findNextFocus(currentItemGroup, - focused, direction); - if (nextFocus != null && nextFocus != currentItemGroup - && nextFocus != focused) { - return nextFocus; - } - } - } - // Try to move focus to the next item. - final int nextPosition = currentPosition - getFirstVisiblePosition() + 1; - for (int i = nextPosition; i < getChildCount(); i++) { - View child = getChildAt(i); - if (child.getVisibility() == View.VISIBLE) { - return child; - } - } - // No next item start searching from the list. - return super.focusSearch(this, direction); - } - case ACCESSIBILITY_FOCUS_BACKWARD: { - // If we are the focused search from the view that is - // as closer to the bottom as possible. - if (focused == this) { - final int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - View child = getChildAt(i); - if (child.getVisibility() == View.VISIBLE) { - return super.focusSearch(child, direction); - } - } - return super.focusSearch(this, direction); - } - // Find the item that has the focused view. - final int currentPosition = getPositionForView(focused); - if (currentPosition < 0 || currentPosition >= getCount()) { - return super.focusSearch(this, direction); - } - - View currentItem = getChildAt(currentPosition - getFirstVisiblePosition()); - - // If a list item is the focused view we try to find a view - // in the previous item since in reverse the item contents - // get accessibility focus before the item itself. - if (currentItem == focused) { - currentItem = null; - focused = null; - // This list gets accessibility focus after the last item. - final int previousPosition = currentPosition - getFirstVisiblePosition() - 1; - for (int i = previousPosition; i >= 0; i--) { - View child = getChildAt(i); - if (child.getVisibility() == View.VISIBLE) { - currentItem = child; - break; - } - } - if (currentItem == null) { - return this; - } - } - - if (currentItem.getVisibility() == View.VISIBLE) { - // Search into the item. - if (currentItem instanceof ViewGroup) { - ViewGroup currentItemGroup = (ViewGroup) currentItem; - View nextFocus = FocusFinder.getInstance().findNextFocus(currentItemGroup, - focused, direction); - if (nextFocus != null && nextFocus != currentItemGroup - && nextFocus != focused) { - return nextFocus; - } - } - - // If not item content wants focus we give it to the item. - return currentItem; - } - - return super.focusSearch(this, direction); - } - } - return super.focusSearch(focused, direction); - } - - /** - * @hide - */ - @Override - public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) { - final int position = getPositionForView(child); - if (position != INVALID_POSITION) { - return getChildAt(position - mFirstPosition); - } - return super.findViewToTakeAccessibilityFocusFromHover(child, descendant); - } - - @Override public void sendAccessibilityEvent(int eventType) { // Since this class calls onScrollChanged even if the mFirstPosition and the // child count have not changed we will avoid sending duplicate accessibility @@ -1751,6 +1621,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te SavedState ss = new SavedState(superState); + if (mPendingSync != null) { + // Just keep what we last restored. + ss.selectedId = mPendingSync.selectedId; + ss.firstId = mPendingSync.firstId; + ss.viewTop = mPendingSync.viewTop; + ss.position = mPendingSync.position; + ss.height = mPendingSync.height; + ss.filter = mPendingSync.filter; + ss.inActionMode = mPendingSync.inActionMode; + ss.checkedItemCount = mPendingSync.checkedItemCount; + ss.checkState = mPendingSync.checkState; + ss.checkIdState = mPendingSync.checkIdState; + return ss; + } + boolean haveChildren = getChildCount() > 0 && mItemCount > 0; long selectedId = getSelectedItemId(); ss.selectedId = selectedId; @@ -1831,6 +1716,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (ss.selectedId >= 0) { mNeedSync = true; + mPendingSync = ss; mSyncRowId = ss.selectedId; mSyncPosition = ss.position; mSpecificTop = ss.viewTop; @@ -1841,6 +1727,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te setNextSelectedPositionInt(INVALID_POSITION); mSelectorPosition = INVALID_POSITION; mNeedSync = true; + mPendingSync = ss; mSyncRowId = ss.firstId; mSyncPosition = ss.position; mSpecificTop = ss.viewTop; @@ -1942,6 +1829,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mDataChanged = false; mPositionScrollAfterLayout = null; mNeedSync = false; + mPendingSync = null; mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; setSelectedPositionInt(INVALID_POSITION); @@ -2297,7 +2185,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mAccessibilityDelegate == null) { mAccessibilityDelegate = new ListItemAccessibilityDelegate(); } - child.setAccessibilityDelegate(mAccessibilityDelegate); + if (child.getAccessibilityDelegate() == null) { + child.setAccessibilityDelegate(mAccessibilityDelegate); + } } return child; @@ -5346,6 +5236,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mNeedSync) { // Update this first, since setNextSelectedPositionInt inspects it mNeedSync = false; + mPendingSync = null; if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) { mLayoutMode = LAYOUT_FORCE_BOTTOM; @@ -5461,6 +5352,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mNextSelectedPosition = INVALID_POSITION; mNextSelectedRowId = INVALID_ROW_ID; mNeedSync = false; + mPendingSync = null; mSelectorPosition = INVALID_POSITION; checkSelectionChanged(); } @@ -5984,6 +5876,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews + * + * @param handler The OnClickHandler to use when inflating RemoteViews. + * + * @hide + */ + public void setRemoteViewsOnClickHandler(OnClickHandler handler) { + // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing + // service handling the specified intent. + if (mRemoteAdapter != null) { + mRemoteAdapter.setRemoteViewsOnClickHandler(handler); + } + } + + /** * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not * connected yet. */ @@ -6090,6 +5997,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mWrapped = wrapped; } + public boolean hasWrappedCallback() { + return mWrapped != null; + } + public boolean onCreateActionMode(ActionMode mode, Menu menu) { if (mWrapped.onCreateActionMode(mode, menu)) { // Initialize checked graphic state? diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index e217e4f..646fe7e 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -107,6 +107,9 @@ public abstract class AbsSeekBar extends ProgressBar { } if (thumb != null) { thumb.setCallback(this); + if (canResolveLayoutDirection()) { + thumb.setLayoutDirection(getLayoutDirection()); + } // Assuming the thumb drawable is symmetric, set the thumb offset // such that the thumb will hang halfway off either edge of the @@ -301,9 +304,22 @@ public abstract class AbsSeekBar extends ProgressBar { } // Canvas will be translated, so 0,0 is where we start drawing - thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound); + final int left = isLayoutRtl() ? available - thumbPos : thumbPos; + thumb.setBounds(left, topBound, left + thumbWidth, bottomBound); } - + + /** + * @hide + */ + @Override + public void onResolveDrawables(int layoutDirection) { + super.onResolveDrawables(layoutDirection); + + if (mThumb != null) { + mThumb.setLayoutDirection(layoutDirection); + } + } + @Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -409,15 +425,25 @@ public abstract class AbsSeekBar extends ProgressBar { int x = (int)event.getX(); float scale; float progress = 0; - if (x < mPaddingLeft) { - scale = 0.0f; - } else if (x > width - mPaddingRight) { - scale = 1.0f; + if (isLayoutRtl()) { + if (x > width - mPaddingRight) { + scale = 0.0f; + } else if (x < mPaddingLeft) { + scale = 1.0f; + } else { + scale = (float)(available - x + mPaddingLeft) / (float)available; + progress = mTouchProgressOffset; + } } else { - scale = (float)(x - mPaddingLeft) / (float)available; - progress = mTouchProgressOffset; + if (x < mPaddingLeft) { + scale = 0.0f; + } else if (x > width - mPaddingRight) { + scale = 1.0f; + } else { + scale = (float)(x - mPaddingLeft) / (float)available; + progress = mTouchProgressOffset; + } } - final int max = getMax(); progress += scale * max; diff --git a/core/java/android/widget/ActivityChooserModel.java b/core/java/android/widget/ActivityChooserModel.java index fe6c4f5..736566e 100644 --- a/core/java/android/widget/ActivityChooserModel.java +++ b/core/java/android/widget/ActivityChooserModel.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.database.DataSetObservable; -import android.database.DataSetObserver; import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; @@ -458,13 +457,18 @@ public class ActivityChooserModel extends DataSetObservable { * </p> * * @return An {@link Intent} for launching the activity or null if the - * policy has consumed the intent. + * policy has consumed the intent or there is not current intent + * set via {@link #setIntent(Intent)}. * * @see HistoricalRecord * @see OnChooseActivityListener */ public Intent chooseActivity(int index) { synchronized (mInstanceLock) { + if (mIntent == null) { + return null; + } + ensureConsistentState(); ActivityResolveInfo chosenActivity = mActivities.get(index); diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java index 4eb169b..2037c3a 100644 --- a/core/java/android/widget/ActivityChooserView.java +++ b/core/java/android/widget/ActivityChooserView.java @@ -497,7 +497,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod // Default activity button. final int activityCount = mAdapter.getActivityCount(); final int historySize = mAdapter.getHistorySize(); - if (activityCount > 0 && historySize > 0) { + if (activityCount==1 || activityCount > 1 && historySize > 0) { mDefaultActivityButton.setVisibility(VISIBLE); ResolveInfo activity = mAdapter.getDefaultActivity(); PackageManager packageManager = mContext.getPackageManager(); diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index 2266cea..90e949a 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -31,6 +31,7 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.RemoteViews.OnClickHandler; import java.util.ArrayList; import java.util.HashMap; @@ -992,6 +993,21 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> } } + /** + * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews + * + * @param handler The OnClickHandler to use when inflating RemoteViews. + * + * @hide + */ + public void setRemoteViewsOnClickHandler(OnClickHandler handler) { + // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing + // service handling the specified intent. + if (mRemoteViewsAdapter != null) { + mRemoteViewsAdapter.setRemoteViewsOnClickHandler(handler); + } + } + @Override public void setSelection(int position) { setDisplayedChild(position); diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java index 988760d..06dadb0 100755 --- a/core/java/android/widget/AppSecurityPermissions.java +++ b/core/java/android/widget/AppSecurityPermissions.java @@ -18,17 +18,23 @@ package android.widget; import com.android.internal.R; +import android.app.AlertDialog; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PackageParser; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import java.text.Collator; import java.util.ArrayList; @@ -36,7 +42,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -52,102 +57,270 @@ import java.util.Set; * * {@hide} */ -public class AppSecurityPermissions implements View.OnClickListener { +public class AppSecurityPermissions { - private enum State { - NO_PERMS, - DANGEROUS_ONLY, - NORMAL_ONLY, - BOTH - } + public static final int WHICH_PERSONAL = 1<<0; + public static final int WHICH_DEVICE = 1<<1; + public static final int WHICH_NEW = 1<<2; + public static final int WHICH_ALL = 0xffff; private final static String TAG = "AppSecurityPermissions"; - private boolean localLOGV = false; + private final static boolean localLOGV = false; private Context mContext; private LayoutInflater mInflater; private PackageManager mPm; - private LinearLayout mPermsView; - private Map<String, String> mDangerousMap; - private Map<String, String> mNormalMap; - private List<PermissionInfo> mPermsList; - private String mDefaultGrpLabel; - private String mDefaultGrpName="DefaultGrp"; - private String mPermFormat; + private PackageInfo mInstalledPackageInfo; + private final Map<String, MyPermissionGroupInfo> mPermGroups + = new HashMap<String, MyPermissionGroupInfo>(); + private final List<MyPermissionGroupInfo> mPermGroupsList + = new ArrayList<MyPermissionGroupInfo>(); + private final PermissionGroupInfoComparator mPermGroupComparator; + private final PermissionInfoComparator mPermComparator; + private List<MyPermissionInfo> mPermsList; + private CharSequence mNewPermPrefix; private Drawable mNormalIcon; private Drawable mDangerousIcon; - private boolean mExpanded; - private Drawable mShowMaxIcon; - private Drawable mShowMinIcon; - private View mShowMore; - private TextView mShowMoreText; - private ImageView mShowMoreIcon; - private State mCurrentState; - private LinearLayout mNonDangerousList; - private LinearLayout mDangerousList; - private HashMap<String, CharSequence> mGroupLabelCache; - private View mNoPermsView; - + + static class MyPermissionGroupInfo extends PermissionGroupInfo { + CharSequence mLabel; + + final ArrayList<MyPermissionInfo> mNewPermissions = new ArrayList<MyPermissionInfo>(); + final ArrayList<MyPermissionInfo> mPersonalPermissions = new ArrayList<MyPermissionInfo>(); + final ArrayList<MyPermissionInfo> mDevicePermissions = new ArrayList<MyPermissionInfo>(); + final ArrayList<MyPermissionInfo> mAllPermissions = new ArrayList<MyPermissionInfo>(); + + MyPermissionGroupInfo(PermissionInfo perm) { + name = perm.packageName; + packageName = perm.packageName; + } + + MyPermissionGroupInfo(PermissionGroupInfo info) { + super(info); + } + + public Drawable loadGroupIcon(PackageManager pm) { + if (icon != 0) { + return loadIcon(pm); + } else { + ApplicationInfo appInfo; + try { + appInfo = pm.getApplicationInfo(packageName, 0); + return appInfo.loadIcon(pm); + } catch (NameNotFoundException e) { + } + } + return null; + } + } + + static class MyPermissionInfo extends PermissionInfo { + CharSequence mLabel; + + /** + * PackageInfo.requestedPermissionsFlags for the new package being installed. + */ + int mNewReqFlags; + + /** + * PackageInfo.requestedPermissionsFlags for the currently installed + * package, if it is installed. + */ + int mExistingReqFlags; + + /** + * True if this should be considered a new permission. + */ + boolean mNew; + + MyPermissionInfo() { + } + + MyPermissionInfo(PermissionInfo info) { + super(info); + } + + MyPermissionInfo(MyPermissionInfo info) { + super(info); + mNewReqFlags = info.mNewReqFlags; + mExistingReqFlags = info.mExistingReqFlags; + mNew = info.mNew; + } + } + + public static class PermissionItemView extends LinearLayout implements View.OnClickListener { + MyPermissionGroupInfo mGroup; + MyPermissionInfo mPerm; + AlertDialog mDialog; + + public PermissionItemView(Context context, AttributeSet attrs) { + super(context, attrs); + setClickable(true); + } + + public void setPermission(MyPermissionGroupInfo grp, MyPermissionInfo perm, + boolean first, CharSequence newPermPrefix) { + mGroup = grp; + mPerm = perm; + + ImageView permGrpIcon = (ImageView) findViewById(R.id.perm_icon); + TextView permNameView = (TextView) findViewById(R.id.perm_name); + + PackageManager pm = getContext().getPackageManager(); + Drawable icon = null; + if (first) { + icon = grp.loadGroupIcon(pm); + } + CharSequence label = perm.mLabel; + if (perm.mNew && newPermPrefix != null) { + // If this is a new permission, format it appropriately. + SpannableStringBuilder builder = new SpannableStringBuilder(); + Parcel parcel = Parcel.obtain(); + TextUtils.writeToParcel(newPermPrefix, parcel, 0); + parcel.setDataPosition(0); + CharSequence newStr = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + parcel.recycle(); + builder.append(newStr); + builder.append(label); + label = builder; + } + + permGrpIcon.setImageDrawable(icon); + permNameView.setText(label); + setOnClickListener(this); + if (localLOGV) Log.i(TAG, "Made perm item " + perm.name + + ": " + label + " in group " + grp.name); + } + + @Override + public void onClick(View v) { + if (mGroup != null && mPerm != null) { + if (mDialog != null) { + mDialog.dismiss(); + } + PackageManager pm = getContext().getPackageManager(); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(mGroup.mLabel); + if (mPerm.descriptionRes != 0) { + builder.setMessage(mPerm.loadDescription(pm)); + } else { + CharSequence appName; + try { + ApplicationInfo app = pm.getApplicationInfo(mPerm.packageName, 0); + appName = app.loadLabel(pm); + } catch (NameNotFoundException e) { + appName = mPerm.packageName; + } + StringBuilder sbuilder = new StringBuilder(128); + sbuilder.append(getContext().getString( + R.string.perms_description_app, appName)); + sbuilder.append("\n\n"); + sbuilder.append(mPerm.name); + builder.setMessage(sbuilder.toString()); + } + builder.setCancelable(true); + builder.setIcon(mGroup.loadGroupIcon(pm)); + mDialog = builder.show(); + mDialog.setCanceledOnTouchOutside(true); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mDialog != null) { + mDialog.dismiss(); + } + } + } + public AppSecurityPermissions(Context context, List<PermissionInfo> permList) { mContext = context; mPm = mContext.getPackageManager(); - mPermsList = permList; + loadResources(); + mPermComparator = new PermissionInfoComparator(); + mPermGroupComparator = new PermissionGroupInfoComparator(); + for (PermissionInfo pi : permList) { + mPermsList.add(new MyPermissionInfo(pi)); + } + setPermissions(mPermsList); } public AppSecurityPermissions(Context context, String packageName) { mContext = context; mPm = mContext.getPackageManager(); - mPermsList = new ArrayList<PermissionInfo>(); - Set<PermissionInfo> permSet = new HashSet<PermissionInfo>(); + loadResources(); + mPermComparator = new PermissionInfoComparator(); + mPermGroupComparator = new PermissionGroupInfoComparator(); + mPermsList = new ArrayList<MyPermissionInfo>(); + Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>(); PackageInfo pkgInfo; try { pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); } catch (NameNotFoundException e) { - Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName); + Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName); return; } // Extract all user permissions if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) { getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet); } - for(PermissionInfo tmpInfo : permSet) { + for(MyPermissionInfo tmpInfo : permSet) { mPermsList.add(tmpInfo); } + setPermissions(mPermsList); } - - public AppSecurityPermissions(Context context, PackageParser.Package pkg) { + + public AppSecurityPermissions(Context context, PackageInfo info) { mContext = context; mPm = mContext.getPackageManager(); - mPermsList = new ArrayList<PermissionInfo>(); - Set<PermissionInfo> permSet = new HashSet<PermissionInfo>(); - if(pkg == null) { + loadResources(); + mPermComparator = new PermissionInfoComparator(); + mPermGroupComparator = new PermissionGroupInfoComparator(); + mPermsList = new ArrayList<MyPermissionInfo>(); + Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>(); + if(info == null) { return; } + + // Convert to a PackageInfo + PackageInfo installedPkgInfo = null; // Get requested permissions - if (pkg.requestedPermissions != null) { - ArrayList<String> strList = pkg.requestedPermissions; - int size = strList.size(); - if (size > 0) { - extractPerms(strList.toArray(new String[size]), permSet); + if (info.requestedPermissions != null) { + try { + installedPkgInfo = mPm.getPackageInfo(info.packageName, + PackageManager.GET_PERMISSIONS); + } catch (NameNotFoundException e) { } + extractPerms(info, permSet, installedPkgInfo); } // Get permissions related to shared user if any - if(pkg.mSharedUserId != null) { + if (info.sharedUserId != null) { int sharedUid; try { - sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId); + sharedUid = mPm.getUidForSharedUser(info.sharedUserId); getAllUsedPermissions(sharedUid, permSet); } catch (NameNotFoundException e) { - Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName); + Log.w(TAG, "Could'nt retrieve shared user id for:"+info.packageName); } } // Retrieve list of permissions - for(PermissionInfo tmpInfo : permSet) { + for (MyPermissionInfo tmpInfo : permSet) { mPermsList.add(tmpInfo); } + setPermissions(mPermsList); } - + + private void loadResources() { + // Pick up from framework resources instead. + mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix); + mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot); + mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission); + } + /** - * Utility to retrieve a view displaying a single permission. + * Utility to retrieve a view displaying a single permission. This provides + * the old UI layout for permissions; it is only here for the device admin + * settings to continue to use. */ public static View getPermissionItemView(Context context, CharSequence grpName, CharSequence description, boolean dangerous) { @@ -155,11 +328,15 @@ public class AppSecurityPermissions implements View.OnClickListener { Context.LAYOUT_INFLATER_SERVICE); Drawable icon = context.getResources().getDrawable(dangerous ? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot); - return getPermissionItemView(context, inflater, grpName, + return getPermissionItemViewOld(context, inflater, grpName, description, dangerous, icon); } - private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) { + public PackageInfo getInstalledPackageInfo() { + return mInstalledPackageInfo; + } + + private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) { String sharedPkgList[] = mPm.getPackagesForUid(sharedUid); if(sharedPkgList == null || (sharedPkgList.length == 0)) { return; @@ -170,29 +347,95 @@ public class AppSecurityPermissions implements View.OnClickListener { } private void getPermissionsForPackage(String packageName, - Set<PermissionInfo> permSet) { + Set<MyPermissionInfo> permSet) { PackageInfo pkgInfo; try { pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); } catch (NameNotFoundException e) { - Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName); + Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName); return; } if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) { - extractPerms(pkgInfo.requestedPermissions, permSet); + extractPerms(pkgInfo, permSet, pkgInfo); } } - - private void extractPerms(String strList[], Set<PermissionInfo> permSet) { - if((strList == null) || (strList.length == 0)) { + + private void extractPerms(PackageInfo info, Set<MyPermissionInfo> permSet, + PackageInfo installedPkgInfo) { + String[] strList = info.requestedPermissions; + int[] flagsList = info.requestedPermissionsFlags; + if ((strList == null) || (strList.length == 0)) { return; } - for(String permName:strList) { + mInstalledPackageInfo = installedPkgInfo; + for (int i=0; i<strList.length; i++) { + String permName = strList[i]; + // If we are only looking at an existing app, then we only + // care about permissions that have actually been granted to it. + if (installedPkgInfo != null && info == installedPkgInfo) { + if ((flagsList[i]&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { + continue; + } + } try { PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0); - if(tmpPermInfo != null) { - permSet.add(tmpPermInfo); + if (tmpPermInfo == null) { + continue; + } + int existingIndex = -1; + if (installedPkgInfo != null + && installedPkgInfo.requestedPermissions != null) { + for (int j=0; j<installedPkgInfo.requestedPermissions.length; j++) { + if (permName.equals(installedPkgInfo.requestedPermissions[j])) { + existingIndex = j; + break; + } + } + } + final int existingFlags = existingIndex >= 0 ? + installedPkgInfo.requestedPermissionsFlags[existingIndex] : 0; + if (!isDisplayablePermission(tmpPermInfo, flagsList[i], existingFlags)) { + // This is not a permission that is interesting for the user + // to see, so skip it. + continue; + } + final String origGroupName = tmpPermInfo.group; + String groupName = origGroupName; + if (groupName == null) { + groupName = tmpPermInfo.packageName; + tmpPermInfo.group = groupName; + } + MyPermissionGroupInfo group = mPermGroups.get(groupName); + if (group == null) { + PermissionGroupInfo grp = null; + if (origGroupName != null) { + grp = mPm.getPermissionGroupInfo(origGroupName, 0); + } + if (grp != null) { + group = new MyPermissionGroupInfo(grp); + } else { + // We could be here either because the permission + // didn't originally specify a group or the group it + // gave couldn't be found. In either case, we consider + // its group to be the permission's package name. + tmpPermInfo.group = tmpPermInfo.packageName; + group = mPermGroups.get(tmpPermInfo.group); + if (group == null) { + group = new MyPermissionGroupInfo(tmpPermInfo); + } + group = new MyPermissionGroupInfo(tmpPermInfo); + } + mPermGroups.put(tmpPermInfo.group, group); } + final boolean newPerm = installedPkgInfo != null + && (existingFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0; + MyPermissionInfo myPerm = new MyPermissionInfo(tmpPermInfo); + myPerm.mNewReqFlags = flagsList[i]; + myPerm.mExistingReqFlags = existingFlags; + // This is a new permission if the app is already installed and + // doesn't currently hold this permission. + myPerm.mNew = newPerm; + permSet.add(myPerm); } catch (NameNotFoundException e) { Log.i(TAG, "Ignoring unknown permission:"+permName); } @@ -200,131 +443,101 @@ public class AppSecurityPermissions implements View.OnClickListener { } public int getPermissionCount() { - return mPermsList.size(); + return getPermissionCount(WHICH_ALL); } - public View getPermissionsView() { - - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null); - mShowMore = mPermsView.findViewById(R.id.show_more); - mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon); - mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text); - mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list); - mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list); - mNoPermsView = mPermsView.findViewById(R.id.no_permissions); - - // Set up the LinearLayout that acts like a list item. - mShowMore.setClickable(true); - mShowMore.setOnClickListener(this); - mShowMore.setFocusable(true); - - // Pick up from framework resources instead. - mDefaultGrpLabel = mContext.getString(R.string.default_permission_group); - mPermFormat = mContext.getString(R.string.permissions_format); - mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot); - mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission); - mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_close_holo_dark); - mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_open_holo_dark); - - // Set permissions view - setPermissions(mPermsList); - return mPermsView; + private List<MyPermissionInfo> getPermissionList(MyPermissionGroupInfo grp, int which) { + if (which == WHICH_NEW) { + return grp.mNewPermissions; + } else if (which == WHICH_PERSONAL) { + return grp.mPersonalPermissions; + } else if (which == WHICH_DEVICE) { + return grp.mDevicePermissions; + } else { + return grp.mAllPermissions; + } } - /** - * Canonicalizes the group description before it is displayed to the user. - * - * TODO check for internationalization issues remove trailing '.' in str1 - */ - private String canonicalizeGroupDesc(String groupDesc) { - if ((groupDesc == null) || (groupDesc.length() == 0)) { - return null; + public int getPermissionCount(int which) { + int N = 0; + for (int i=0; i<mPermGroupsList.size(); i++) { + N += getPermissionList(mPermGroupsList.get(i), which).size(); } - // Both str1 and str2 are non-null and are non-zero in size. - int len = groupDesc.length(); - if(groupDesc.charAt(len-1) == '.') { - groupDesc = groupDesc.substring(0, len-1); - } - return groupDesc; + return N; } - /** - * Utility method that concatenates two strings defined by mPermFormat. - * a null value is returned if both str1 and str2 are null, if one of the strings - * is null the other non null value is returned without formatting - * this is to placate initial error checks - */ - private String formatPermissions(String groupDesc, CharSequence permDesc) { - if(groupDesc == null) { - if(permDesc == null) { - return null; - } - return permDesc.toString(); - } - groupDesc = canonicalizeGroupDesc(groupDesc); - if(permDesc == null) { - return groupDesc; - } - // groupDesc and permDesc are non null - return String.format(mPermFormat, groupDesc, permDesc.toString()); + public View getPermissionsView() { + return getPermissionsView(WHICH_ALL); } - private CharSequence getGroupLabel(String grpName) { - if (grpName == null) { - //return default label - return mDefaultGrpLabel; - } - CharSequence cachedLabel = mGroupLabelCache.get(grpName); - if (cachedLabel != null) { - return cachedLabel; - } - PermissionGroupInfo pgi; - try { - pgi = mPm.getPermissionGroupInfo(grpName, 0); - } catch (NameNotFoundException e) { - Log.i(TAG, "Invalid group name:" + grpName); - return null; + public View getPermissionsView(int which) { + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + LinearLayout permsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null); + LinearLayout displayList = (LinearLayout) permsView.findViewById(R.id.perms_list); + View noPermsView = permsView.findViewById(R.id.no_permissions); + + displayPermissions(mPermGroupsList, displayList, which); + if (displayList.getChildCount() <= 0) { + noPermsView.setVisibility(View.VISIBLE); } - CharSequence label = pgi.loadLabel(mPm).toString(); - mGroupLabelCache.put(grpName, label); - return label; + + return permsView; } /** * Utility method that displays permissions from a map containing group name and * list of permission descriptions. */ - private void displayPermissions(boolean dangerous) { - Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap; - LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList; + private void displayPermissions(List<MyPermissionGroupInfo> groups, + LinearLayout permListView, int which) { permListView.removeAllViews(); - Set<String> permInfoStrSet = permInfoMap.keySet(); - for (String loopPermGrpInfoStr : permInfoStrSet) { - CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr); - //guaranteed that grpLabel wont be null since permissions without groups - //will belong to the default group - if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:" - + permInfoMap.get(loopPermGrpInfoStr)); - permListView.addView(getPermissionItemView(grpLabel, - permInfoMap.get(loopPermGrpInfoStr), dangerous)); + int spacing = (int)(8*mContext.getResources().getDisplayMetrics().density); + + for (int i=0; i<groups.size(); i++) { + MyPermissionGroupInfo grp = groups.get(i); + final List<MyPermissionInfo> perms = getPermissionList(grp, which); + for (int j=0; j<perms.size(); j++) { + MyPermissionInfo perm = perms.get(j); + View view = getPermissionItemView(grp, perm, j == 0, + which != WHICH_NEW ? mNewPermPrefix : null); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + if (j == 0) { + lp.topMargin = spacing; + } + if (j == grp.mAllPermissions.size()-1) { + lp.bottomMargin = spacing; + } + if (permListView.getChildCount() == 0) { + lp.topMargin *= 2; + } + permListView.addView(view, lp); + } } } - private void displayNoPermissions() { - mNoPermsView.setVisibility(View.VISIBLE); + private PermissionItemView getPermissionItemView(MyPermissionGroupInfo grp, + MyPermissionInfo perm, boolean first, CharSequence newPermPrefix) { + return getPermissionItemView(mContext, mInflater, grp, perm, first, newPermPrefix); } - private View getPermissionItemView(CharSequence grpName, CharSequence permList, - boolean dangerous) { - return getPermissionItemView(mContext, mInflater, grpName, permList, - dangerous, dangerous ? mDangerousIcon : mNormalIcon); + private static PermissionItemView getPermissionItemView(Context context, LayoutInflater inflater, + MyPermissionGroupInfo grp, MyPermissionInfo perm, boolean first, + CharSequence newPermPrefix) { + PermissionItemView permView = (PermissionItemView)inflater.inflate( + (perm.flags & PermissionInfo.FLAG_COSTS_MONEY) != 0 + ? R.layout.app_permission_item_money : R.layout.app_permission_item, + null); + permView.setPermission(grp, perm, first, newPermPrefix); + return permView; } - private static View getPermissionItemView(Context context, LayoutInflater inflater, + private static View getPermissionItemViewOld(Context context, LayoutInflater inflater, CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) { - View permView = inflater.inflate(R.layout.app_permission_item, null); + View permView = inflater.inflate(R.layout.app_permission_item_old, null); TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group); TextView permDescView = (TextView) permView.findViewById(R.id.permission_list); @@ -341,159 +554,109 @@ public class AppSecurityPermissions implements View.OnClickListener { return permView; } - private void showPermissions() { - - switch(mCurrentState) { - case NO_PERMS: - displayNoPermissions(); - break; - - case DANGEROUS_ONLY: - displayPermissions(true); - break; - - case NORMAL_ONLY: - displayPermissions(false); - break; - - case BOTH: - displayPermissions(true); - if (mExpanded) { - displayPermissions(false); - mShowMoreIcon.setImageDrawable(mShowMaxIcon); - mShowMoreText.setText(R.string.perms_hide); - mNonDangerousList.setVisibility(View.VISIBLE); - } else { - mShowMoreIcon.setImageDrawable(mShowMinIcon); - mShowMoreText.setText(R.string.perms_show_all); - mNonDangerousList.setVisibility(View.GONE); - } - mShowMore.setVisibility(View.VISIBLE); - break; + private boolean isDisplayablePermission(PermissionInfo pInfo, int newReqFlags, + int existingReqFlags) { + final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; + // Dangerous and normal permissions are always shown to the user. + if (base == PermissionInfo.PROTECTION_DANGEROUS || + base == PermissionInfo.PROTECTION_NORMAL) { + return true; } - } - - private boolean isDisplayablePermission(PermissionInfo pInfo) { - if(pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS || - pInfo.protectionLevel == PermissionInfo.PROTECTION_NORMAL) { + // Development permissions are only shown to the user if they are already + // granted to the app -- if we are installing an app and they are not + // already granted, they will not be granted as part of the install. + if ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0 + && (pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { + if (localLOGV) Log.i(TAG, "Special perm " + pInfo.name + + ": protlevel=0x" + Integer.toHexString(pInfo.protectionLevel)); return true; } return false; } - /* - * Utility method that aggregates all permission descriptions categorized by group - * Say group1 has perm11, perm12, perm13, the group description will be - * perm11_Desc, perm12_Desc, perm13_Desc - */ - private void aggregateGroupDescs( - Map<String, List<PermissionInfo> > map, Map<String, String> retMap) { - if(map == null) { - return; + private static class PermissionGroupInfoComparator implements Comparator<MyPermissionGroupInfo> { + private final Collator sCollator = Collator.getInstance(); + PermissionGroupInfoComparator() { } - if(retMap == null) { - return; - } - Set<String> grpNames = map.keySet(); - Iterator<String> grpNamesIter = grpNames.iterator(); - while(grpNamesIter.hasNext()) { - String grpDesc = null; - String grpNameKey = grpNamesIter.next(); - List<PermissionInfo> grpPermsList = map.get(grpNameKey); - if(grpPermsList == null) { - continue; - } - for(PermissionInfo permInfo: grpPermsList) { - CharSequence permDesc = permInfo.loadLabel(mPm); - grpDesc = formatPermissions(grpDesc, permDesc); + public final int compare(MyPermissionGroupInfo a, MyPermissionGroupInfo b) { + if (((a.flags^b.flags)&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) { + return ((a.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) ? -1 : 1; } - // Insert grpDesc into map - if(grpDesc != null) { - if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString()); - retMap.put(grpNameKey, grpDesc.toString()); + if (a.priority != b.priority) { + return a.priority > b.priority ? -1 : 1; } + return sCollator.compare(a.mLabel, b.mLabel); } } - private static class PermissionInfoComparator implements Comparator<PermissionInfo> { - private PackageManager mPm; + private static class PermissionInfoComparator implements Comparator<MyPermissionInfo> { private final Collator sCollator = Collator.getInstance(); - PermissionInfoComparator(PackageManager pm) { - mPm = pm; + PermissionInfoComparator() { } - public final int compare(PermissionInfo a, PermissionInfo b) { - CharSequence sa = a.loadLabel(mPm); - CharSequence sb = b.loadLabel(mPm); - return sCollator.compare(sa, sb); + public final int compare(MyPermissionInfo a, MyPermissionInfo b) { + return sCollator.compare(a.mLabel, b.mLabel); } } - - private void setPermissions(List<PermissionInfo> permList) { - mGroupLabelCache = new HashMap<String, CharSequence>(); - //add the default label so that uncategorized permissions can go here - mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel); - - // Map containing group names and a list of permissions under that group - // categorized as dangerous - mDangerousMap = new HashMap<String, String>(); - // Map containing group names and a list of permissions under that group - // categorized as normal - mNormalMap = new HashMap<String, String>(); - - // Additional structures needed to ensure that permissions are unique under - // each group - Map<String, List<PermissionInfo>> dangerousMap = - new HashMap<String, List<PermissionInfo>>(); - Map<String, List<PermissionInfo> > normalMap = - new HashMap<String, List<PermissionInfo>>(); - PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm); - + + private void addPermToList(List<MyPermissionInfo> permList, + MyPermissionInfo pInfo) { + if (pInfo.mLabel == null) { + pInfo.mLabel = pInfo.loadLabel(mPm); + } + int idx = Collections.binarySearch(permList, pInfo, mPermComparator); + if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+permList.size()); + if (idx < 0) { + idx = -idx-1; + permList.add(idx, pInfo); + } + } + + private void setPermissions(List<MyPermissionInfo> permList) { if (permList != null) { // First pass to group permissions - for (PermissionInfo pInfo : permList) { + for (MyPermissionInfo pInfo : permList) { if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name); - if(!isDisplayablePermission(pInfo)) { + if(!isDisplayablePermission(pInfo, pInfo.mNewReqFlags, pInfo.mExistingReqFlags)) { if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable"); continue; } - Map<String, List<PermissionInfo> > permInfoMap = - (pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ? - dangerousMap : normalMap; - String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group; - if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName); - List<PermissionInfo> grpPermsList = permInfoMap.get(grpName); - if(grpPermsList == null) { - grpPermsList = new ArrayList<PermissionInfo>(); - permInfoMap.put(grpName, grpPermsList); - grpPermsList.add(pInfo); - } else { - int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator); - if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size()); - if (idx < 0) { - idx = -idx-1; - grpPermsList.add(idx, pInfo); + MyPermissionGroupInfo group = mPermGroups.get(pInfo.group); + if (group != null) { + pInfo.mLabel = pInfo.loadLabel(mPm); + addPermToList(group.mAllPermissions, pInfo); + if (pInfo.mNew) { + addPermToList(group.mNewPermissions, pInfo); + } + if ((group.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) { + addPermToList(group.mPersonalPermissions, pInfo); + } else { + addPermToList(group.mDevicePermissions, pInfo); } } } - // Second pass to actually form the descriptions - // Look at dangerous permissions first - aggregateGroupDescs(dangerousMap, mDangerousMap); - aggregateGroupDescs(normalMap, mNormalMap); } - mCurrentState = State.NO_PERMS; - if(mDangerousMap.size() > 0) { - mCurrentState = (mNormalMap.size() > 0) ? State.BOTH : State.DANGEROUS_ONLY; - } else if(mNormalMap.size() > 0) { - mCurrentState = State.NORMAL_ONLY; + for (MyPermissionGroupInfo pgrp : mPermGroups.values()) { + if (pgrp.labelRes != 0 || pgrp.nonLocalizedLabel != null) { + pgrp.mLabel = pgrp.loadLabel(mPm); + } else { + ApplicationInfo app; + try { + app = mPm.getApplicationInfo(pgrp.packageName, 0); + pgrp.mLabel = app.loadLabel(mPm); + } catch (NameNotFoundException e) { + pgrp.mLabel = pgrp.loadLabel(mPm); + } + } + mPermGroupsList.add(pgrp); + } + Collections.sort(mPermGroupsList, mPermGroupComparator); + if (localLOGV) { + for (MyPermissionGroupInfo grp : mPermGroupsList) { + Log.i(TAG, "Group " + grp.name + " personal=" + + ((grp.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) + + " priority=" + grp.priority); + } } - if(localLOGV) Log.i(TAG, "mCurrentState=" + mCurrentState); - showPermissions(); - } - - public void onClick(View v) { - if(localLOGV) Log.i(TAG, "mExpanded="+mExpanded); - mExpanded = !mExpanded; - showPermissions(); } } diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 30dd17d..75d1471 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -579,6 +579,23 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } /** + * Set a listener that will be invoked whenever the AutoCompleteTextView's + * list of completions is dismissed. + * @param dismissListener Listener to invoke when completions are dismissed + */ + public void setOnDismissListener(final OnDismissListener dismissListener) { + PopupWindow.OnDismissListener wrappedListener = null; + if (dismissListener != null) { + wrappedListener = new PopupWindow.OnDismissListener() { + @Override public void onDismiss() { + dismissListener.onDismiss(); + } + }; + } + mPopup.setOnDismissListener(wrappedListener); + } + + /** * <p>Returns a filterable list adapter used for auto completion.</p> * * @return a data adapter used for auto completion @@ -904,8 +921,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * * @param filter If <code>false</code>, no filtering will be performed * as a result of this call. - * - * @hide Pending API council approval. */ public void setText(CharSequence text, boolean filter) { if (filter) { @@ -1078,6 +1093,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED); mPopup.setListItemExpandMax(EXPAND_MAX); } + mPopup.setLayoutDirection(getLayoutDirection()); mPopup.show(); mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS); } @@ -1208,6 +1224,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } /** + * Listener to respond to the AutoCompleteTextView's completion list being dismissed. + * @see AutoCompleteTextView#setOnDismissListener(OnDismissListener) + */ + public interface OnDismissListener { + /** + * This method will be invoked whenever the AutoCompleteTextView's list + * of completion options has been dismissed and is no longer available + * for user interaction. + */ + void onDismiss(); + } + + /** * Allows us a private hook into the on click event without preventing users from setting * their own click listener. */ diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index b06da06..361eca4 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -1573,7 +1573,8 @@ public class CalendarView extends FrameLayout { // If we're showing the week number calculate it based on Monday int i = 0; if (mShowWeekNumber) { - mDayNumbers[0] = Integer.toString(mTempDate.get(Calendar.WEEK_OF_YEAR)); + mDayNumbers[0] = String.format(Locale.getDefault(), "%d", + mTempDate.get(Calendar.WEEK_OF_YEAR)); i++; } @@ -1594,7 +1595,8 @@ public class CalendarView extends FrameLayout { if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { mDayNumbers[i] = ""; } else { - mDayNumbers[i] = Integer.toString(mTempDate.get(Calendar.DAY_OF_MONTH)); + mDayNumbers[i] = String.format(Locale.getDefault(), "%d", + mTempDate.get(Calendar.DAY_OF_MONTH)); } mTempDate.add(Calendar.DAY_OF_MONTH, 1); } @@ -1609,7 +1611,7 @@ public class CalendarView extends FrameLayout { } /** - * Initialize the paint isntances. + * Initialize the paint instances. */ private void initilaizePaints() { mDrawPaint.setFakeBoldText(false); @@ -1620,6 +1622,7 @@ public class CalendarView extends FrameLayout { mMonthNumDrawPaint.setAntiAlias(true); mMonthNumDrawPaint.setStyle(Style.FILL); mMonthNumDrawPaint.setTextAlign(Align.CENTER); + mMonthNumDrawPaint.setTextSize(mDateTextSize); } /** @@ -1657,16 +1660,34 @@ public class CalendarView extends FrameLayout { * @return True if a day was found for the given location. */ public boolean getDayFromLocation(float x, Calendar outCalendar) { - int dayStart = mShowWeekNumber ? mWidth / mNumCells : 0; - if (x < dayStart || x > mWidth) { + final boolean isLayoutRtl = isLayoutRtl(); + + int start; + int end; + + if (isLayoutRtl) { + start = 0; + end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + start = mShowWeekNumber ? mWidth / mNumCells : 0; + end = mWidth; + } + + if (x < start || x > end) { outCalendar.clear(); return false; } - // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels - int dayPosition = (int) ((x - dayStart) * mDaysPerWeek - / (mWidth - dayStart)); + + // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels + int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start)); + + if (isLayoutRtl) { + dayPosition = mDaysPerWeek - 1 - dayPosition; + } + outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); + return true; } @@ -1691,12 +1712,25 @@ public class CalendarView extends FrameLayout { mTempRect.top = mWeekSeperatorLineWidth; mTempRect.bottom = mHeight; - mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; - mTempRect.right = mSelectedLeft - 2; + + final boolean isLayoutRtl = isLayoutRtl(); + + if (isLayoutRtl) { + mTempRect.left = 0; + mTempRect.right = mSelectedLeft - 2; + } else { + mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; + mTempRect.right = mSelectedLeft - 2; + } canvas.drawRect(mTempRect, mDrawPaint); - mTempRect.left = mSelectedRight + 3; - mTempRect.right = mWidth; + if (isLayoutRtl) { + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mWidth; + } canvas.drawRect(mTempRect, mDrawPaint); } @@ -1706,25 +1740,41 @@ public class CalendarView extends FrameLayout { * @param canvas The canvas to draw on */ private void drawWeekNumbersAndDates(Canvas canvas) { - float textHeight = mDrawPaint.getTextSize(); - int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; - int nDays = mNumCells; + final float textHeight = mDrawPaint.getTextSize(); + final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; + final int nDays = mNumCells; + final int divisor = 2 * nDays; mDrawPaint.setTextAlign(Align.CENTER); mDrawPaint.setTextSize(mDateTextSize); + int i = 0; - int divisor = 2 * nDays; - if (mShowWeekNumber) { - mDrawPaint.setColor(mWeekNumberColor); - int x = mWidth / divisor; - canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); - i++; - } - for (; i < nDays; i++) { - mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor - : mUnfocusedMonthDateColor); - int x = (2 * i + 1) * mWidth / divisor; - canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); + + if (isLayoutRtl()) { + for (; i < nDays - 1; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint); + } + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth - mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + } + } else { + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + i++; + } + for (; i < nDays; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); + } } } @@ -1744,8 +1794,16 @@ public class CalendarView extends FrameLayout { } mDrawPaint.setColor(mWeekSeparatorLineColor); mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); - float x = mShowWeekNumber ? mWidth / mNumCells : 0; - canvas.drawLine(x, 0, mWidth, 0, mDrawPaint); + float startX; + float stopX; + if (isLayoutRtl()) { + startX = 0; + stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth; + } else { + startX = mShowWeekNumber ? mWidth / mNumCells : 0; + stopX = mWidth; + } + canvas.drawLine(startX, 0, stopX, 0, mDrawPaint); } /** @@ -1778,15 +1836,21 @@ public class CalendarView extends FrameLayout { */ private void updateSelectionPositions() { if (mHasSelectedDay) { + final boolean isLayoutRtl = isLayoutRtl(); int selectedPosition = mSelectedDay - mFirstDayOfWeek; if (selectedPosition < 0) { selectedPosition += 7; } - if (mShowWeekNumber) { + if (mShowWeekNumber && !isLayoutRtl) { selectedPosition++; } - mSelectedLeft = selectedPosition * mWidth / mNumCells; - mSelectedRight = (selectedPosition + 1) * mWidth / mNumCells; + if (isLayoutRtl) { + mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells; + + } else { + mSelectedLeft = selectedPosition * mWidth / mNumCells; + } + mSelectedRight = mSelectedLeft + mWidth / mNumCells; } } diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 61935c2..e74e37c 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -55,7 +55,7 @@ public class CheckedTextView extends TextView implements Checkable { } public CheckedTextView(Context context, AttributeSet attrs) { - this(context, attrs, 0); + this(context, attrs, R.attr.checkedTextViewStyle); } public CheckedTextView(Context context, AttributeSet attrs, int defStyle) { @@ -101,7 +101,7 @@ public class CheckedTextView extends TextView implements Checkable { /** * Set the checkmark to a given Drawable, identified by its resourece id. This will be drawn * when {@link #isChecked()} is true. - * + * * @param resid The Drawable to use for the checkmark. * * @see #setCheckMarkDrawable(Drawable) @@ -144,14 +144,14 @@ public class CheckedTextView extends TextView implements Checkable { d.setVisible(getVisibility() == VISIBLE, false); d.setState(CHECKED_STATE_SET); setMinHeight(d.getIntrinsicHeight()); - + mCheckMarkWidth = d.getIntrinsicWidth(); d.setState(getDrawableState()); } else { mCheckMarkWidth = 0; } mCheckMarkDrawable = d; - // Do padding resolution. This will call setPadding() and do a requestLayout() if needed. + // Do padding resolution. This will call internalSetPadding() and do a requestLayout() if needed. resolvePadding(); } @@ -169,28 +169,55 @@ public class CheckedTextView extends TextView implements Checkable { return mCheckMarkDrawable; } + /** + * @hide + */ + @Override + protected void internalSetPadding(int left, int top, int right, int bottom) { + super.internalSetPadding(left, top, right, bottom); + setBasePadding(isLayoutRtl()); + } + @Override - public void onPaddingChanged(int layoutDirection) { + public void onRtlPropertiesChanged(int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + updatePadding(); + } + + private void updatePadding() { + resetPaddingToInitialValues(); int newPadding = (mCheckMarkDrawable != null) ? mCheckMarkWidth + mBasePadding : mBasePadding; mNeedRequestlayout |= (mPaddingRight != newPadding); - mPaddingRight = newPadding; + if (isLayoutRtl()) { + mPaddingLeft = newPadding; + } else { + mPaddingRight = newPadding; + } if (mNeedRequestlayout) { requestLayout(); mNeedRequestlayout = false; } } - + @Override public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, bottom); - mBasePadding = mPaddingRight; + setBasePadding(isLayoutRtl()); } @Override public void setPaddingRelative(int start, int top, int end, int bottom) { super.setPaddingRelative(start, top, end, bottom); - mBasePadding = getPaddingEnd(); + setBasePadding(isLayoutRtl()); + } + + private void setBasePadding(boolean isLayoutRtl) { + if (isLayoutRtl) { + mBasePadding = mPaddingLeft; + } else { + mBasePadding = mPaddingRight; + } } @Override @@ -213,12 +240,20 @@ public class CheckedTextView extends TextView implements Checkable { break; } - int right = getWidth(); - checkMarkDrawable.setBounds( - right - mPaddingRight, - y, - right - mPaddingRight + mCheckMarkWidth, - y + height); + final boolean isLayoutRtl = isLayoutRtl(); + final int width = getWidth(); + final int top = y; + final int bottom = top + height; + final int left; + final int right; + if (isLayoutRtl) { + left = mBasePadding; + right = left + mCheckMarkWidth; + } else { + right = width - mBasePadding; + left = right - mCheckMarkWidth; + } + checkMarkDrawable.setBounds( left, top, right, bottom); checkMarkDrawable.draw(canvas); } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 0a71c5a..421a324 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -225,26 +225,53 @@ public abstract class CompoundButton extends Button implements Checkable { } @Override + public int getCompoundPaddingLeft() { + int padding = super.getCompoundPaddingLeft(); + if (!isLayoutRtl()) { + final Drawable buttonDrawable = mButtonDrawable; + if (buttonDrawable != null) { + padding += buttonDrawable.getIntrinsicWidth(); + } + } + return padding; + } + + @Override + public int getCompoundPaddingRight() { + int padding = super.getCompoundPaddingRight(); + if (isLayoutRtl()) { + final Drawable buttonDrawable = mButtonDrawable; + if (buttonDrawable != null) { + padding += buttonDrawable.getIntrinsicWidth(); + } + } + return padding; + } + + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final Drawable buttonDrawable = mButtonDrawable; if (buttonDrawable != null) { final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK; - final int height = buttonDrawable.getIntrinsicHeight(); - - int y = 0; + final int drawableHeight = buttonDrawable.getIntrinsicHeight(); + final int drawableWidth = buttonDrawable.getIntrinsicWidth(); + int top = 0; switch (verticalGravity) { case Gravity.BOTTOM: - y = getHeight() - height; + top = getHeight() - drawableHeight; break; case Gravity.CENTER_VERTICAL: - y = (getHeight() - height) / 2; + top = (getHeight() - drawableHeight) / 2; break; } + int bottom = top + drawableHeight; + int left = isLayoutRtl() ? getWidth() - drawableWidth : 0; + int right = isLayoutRtl() ? getWidth() : drawableWidth; - buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height); + buttonDrawable.setBounds(left, top, right, bottom); buttonDrawable.draw(canvas); } } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index ac3bedb..07d3a7a 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -220,7 +220,7 @@ public class DatePicker extends FrameLayout { // day mDaySpinner = (NumberPicker) findViewById(R.id.day); - mDaySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); + mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); mDaySpinner.setOnLongPressUpdateInterval(100); mDaySpinner.setOnValueChangedListener(onChangeListener); mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input); diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java index add9d9b..3e9107f 100644 --- a/core/java/android/widget/DigitalClock.java +++ b/core/java/android/widget/DigitalClock.java @@ -34,13 +34,17 @@ import java.util.Calendar; * * FIXME: implement separate views for hours/minutes/seconds, so * proportional fonts don't shake rendering + * + * @deprecated It is recommended you use a {@link TextView} and {@link DateFormat} + * to implement the same behavior. */ - +@Deprecated public class DigitalClock extends TextView { Calendar mCalendar; private final static String m12 = "h:mm:ss aa"; private final static String m24 = "k:mm:ss"; + @SuppressWarnings("FieldCanBeLocal") // We must keep a reference to this observer private FormatChangeObserver mFormatChangeObserver; private Runnable mTicker; @@ -52,17 +56,15 @@ public class DigitalClock extends TextView { public DigitalClock(Context context) { super(context); - initClock(context); + initClock(); } public DigitalClock(Context context, AttributeSet attrs) { super(context, attrs); - initClock(context); + initClock(); } - private void initClock(Context context) { - Resources r = mContext.getResources(); - + private void initClock() { if (mCalendar == null) { mCalendar = Calendar.getInstance(); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index c29dd58..c67cae6 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -88,9 +88,6 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView.OnItemClickListener; -import android.widget.Editor.InputContentType; -import android.widget.Editor.InputMethodState; -import android.widget.Editor.SelectionModifierCursorController; import android.widget.TextView.Drawables; import android.widget.TextView.OnEditorActionListener; @@ -292,7 +289,7 @@ public class Editor { mErrorWasChanged = true; final Drawables dr = mTextView.mDrawables; if (dr != null) { - switch (mTextView.getResolvedLayoutDirection()) { + switch (mTextView.getLayoutDirection()) { default: case View.LAYOUT_DIRECTION_LTR: mTextView.setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon, @@ -978,8 +975,8 @@ public class Editor { mSuggestionsPopupWindow.onParentLostFocus(); } - // Don't leave us in the middle of a batch edit. - mTextView.onEndBatchEdit(); + // Don't leave us in the middle of a batch edit. Same as in onFocusChanged + ensureEndedBatchEdit(); } } @@ -1801,13 +1798,13 @@ public class Editor { mTextView.deleteText_internal(dragSourceStart, dragSourceEnd); // Make sure we do not leave two adjacent spaces. - CharSequence t = mTextView.getTransformedText(dragSourceStart - 1, dragSourceStart + 1); - if ( (dragSourceStart == 0 || Character.isSpaceChar(t.charAt(0))) && - (dragSourceStart == mTextView.getText().length() || - Character.isSpaceChar(t.charAt(1))) ) { - final int pos = dragSourceStart == mTextView.getText().length() ? - dragSourceStart - 1 : dragSourceStart; - mTextView.deleteText_internal(pos, pos + 1); + final int prevCharIdx = Math.max(0, dragSourceStart - 1); + final int nextCharIdx = Math.min(mTextView.getText().length(), dragSourceStart + 1); + if (nextCharIdx > prevCharIdx + 1) { + CharSequence t = mTextView.getTransformedText(prevCharIdx, nextCharIdx); + if (Character.isSpaceChar(t.charAt(0)) && Character.isSpaceChar(t.charAt(1))) { + mTextView.deleteText_internal(prevCharIdx, prevCharIdx + 1); + } } } } @@ -2282,14 +2279,11 @@ public class Editor { final SuggestionInfo suggestionInfo = mSuggestionInfos[position]; textView.setText(suggestionInfo.text); - if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) { - textView.setCompoundDrawablesWithIntrinsicBounds( - com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0); - } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) { - textView.setCompoundDrawablesWithIntrinsicBounds( - com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0); + if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY || + suggestionInfo.suggestionIndex == DELETE_TEXT) { + textView.setBackgroundColor(Color.TRANSPARENT); } else { - textView.setCompoundDrawables(null, null, null, null); + textView.setBackgroundColor(Color.WHITE); } return textView; diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 083a952..d2139af 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -181,10 +181,13 @@ class FastScroller { } public void setScrollbarPosition(int position) { + if (position == View.SCROLLBAR_POSITION_DEFAULT) { + position = mList.isLayoutRtl() ? + View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT; + } mPosition = position; switch (position) { default: - case View.SCROLLBAR_POSITION_DEFAULT: case View.SCROLLBAR_POSITION_RIGHT: mOverlayDrawable = mOverlayDrawableRight; break; @@ -229,7 +232,6 @@ class FastScroller { final int viewWidth = mList.getWidth(); // Bounds are always top right. Y coordinate get's translated during draw switch (mPosition) { - case View.SCROLLBAR_POSITION_DEFAULT: case View.SCROLLBAR_POSITION_RIGHT: mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH); break; @@ -327,7 +329,6 @@ class FastScroller { } int left = 0; switch (mPosition) { - case View.SCROLLBAR_POSITION_DEFAULT: case View.SCROLLBAR_POSITION_RIGHT: left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX; break; @@ -360,7 +361,6 @@ class FastScroller { int left = 0; switch (mPosition) { default: - case View.SCROLLBAR_POSITION_DEFAULT: case View.SCROLLBAR_POSITION_RIGHT: left = Math.max(0, mThumbDrawable.getBounds().left - mThumbW - mOverlaySize); @@ -410,7 +410,6 @@ class FastScroller { if (mThumbDrawable != null) { switch (mPosition) { default: - case View.SCROLLBAR_POSITION_DEFAULT: case View.SCROLLBAR_POSITION_RIGHT: mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH); break; @@ -820,7 +819,6 @@ class FastScroller { boolean inTrack = false; switch (mPosition) { default: - case View.SCROLLBAR_POSITION_DEFAULT: case View.SCROLLBAR_POSITION_RIGHT: inTrack = x > mList.getWidth() - mThumbW; break; diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index d019d8c..00cd604 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -55,7 +55,7 @@ import android.widget.RemoteViews.RemoteView; */ @RemoteView public class FrameLayout extends ViewGroup { - private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.LEFT; + private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START; @ViewDebug.ExportedProperty(category = "measurement") boolean mMeasureAllChildren = false; @@ -411,7 +411,7 @@ public class FrameLayout extends ViewGroup { gravity = DEFAULT_CHILD_GRAVITY; } - final int layoutDirection = getResolvedLayoutDirection(); + final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; @@ -483,7 +483,7 @@ public class FrameLayout extends ViewGroup { selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom); } - final int layoutDirection = getResolvedLayoutDirection(); + final int layoutDirection = getLayoutDirection(); Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds, layoutDirection); @@ -603,6 +603,12 @@ public class FrameLayout extends ViewGroup { */ public int gravity = -1; + @Override + protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { + width = a.getLayoutDimension(widthAttr, MATCH_PARENT); + height = a.getLayoutDimension(heightAttr, MATCH_PARENT); + } + /** * {@inheritDoc} */ diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index b72b8cb..e0c5bbd 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -182,6 +182,12 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList */ private boolean mIsRtl = true; + /** + * Offset between the center of the selected child view and the center of the Gallery. + * Used to reset position correctly during layout. + */ + private int mSelectedCenterOffset; + public Gallery(Context context) { this(context, null); } @@ -395,6 +401,14 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList setSelectionToCenterChild(); + final View selChild = mSelectedChild; + if (selChild != null) { + final int childLeft = selChild.getLeft(); + final int childCenter = selChild.getWidth() / 2; + final int galleryCenter = getWidth() / 2; + mSelectedCenterOffset = childLeft + childCenter - galleryCenter; + } + onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. invalidate(); @@ -537,6 +551,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList // We haven't been callbacking during the fling, so do it now super.selectionChanged(); } + mSelectedCenterOffset = 0; invalidate(); } @@ -650,7 +665,8 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList View sel = makeAndAddView(mSelectedPosition, 0, 0, true); // Put the selected child in the center - int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2); + int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2) + + mSelectedCenterOffset; sel.offsetLeftAndRight(selectedOffset); fillToGalleryRight(); diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 37e0b90..63147dd 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -94,7 +94,7 @@ public class GridView extends AbsListView { private View mReferenceView = null; private View mReferenceViewInSelectedRow = null; - private int mGravity = Gravity.LEFT; + private int mGravity = Gravity.START; private final Rect mTempRect = new Rect(); @@ -300,9 +300,18 @@ public class GridView extends AbsListView { final int columnWidth = mColumnWidth; final int horizontalSpacing = mHorizontalSpacing; + final boolean isLayoutRtl = isLayoutRtl(); + int last; - int nextLeft = mListPadding.left + - ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0); + int nextLeft; + + if (isLayoutRtl) { + nextLeft = getWidth() - mListPadding.right - columnWidth - + ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0); + } else { + nextLeft = mListPadding.left + + ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0); + } if (!mStackFromBottom) { last = Math.min(startPos + mNumColumns, mItemCount); @@ -311,7 +320,8 @@ public class GridView extends AbsListView { startPos = Math.max(0, startPos - mNumColumns + 1); if (last - startPos < mNumColumns) { - nextLeft += (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing); + final int deltaLeft = (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing); + nextLeft += (isLayoutRtl ? -1 : +1) * deltaLeft; } } @@ -330,7 +340,7 @@ public class GridView extends AbsListView { final int where = flow ? -1 : pos - startPos; child = makeAndAddView(pos, y, flow, nextLeft, selected, where); - nextLeft += columnWidth; + nextLeft += (isLayoutRtl ? -1 : +1) * columnWidth; if (pos < last - 1) { nextLeft += horizontalSpacing; } @@ -1415,7 +1425,7 @@ public class GridView extends AbsListView { int childLeft; final int childTop = flow ? y : y - h; - final int layoutDirection = getResolvedLayoutDirection(); + final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index cf28da4..87396fb 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -193,15 +193,6 @@ public class ImageView extends View { } } - /** - * @hide - */ - @Override - public int getResolvedLayoutDirection(Drawable dr) { - return (dr == mDrawable) ? - getResolvedLayoutDirection() : super.getResolvedLayoutDirection(dr); - } - @Override public boolean hasOverlappingRendering() { return (getBackground() != null); @@ -351,8 +342,15 @@ public class ImageView extends View { updateDrawable(null); mResource = resId; mUri = null; + + final int oldWidth = mDrawableWidth; + final int oldHeight = mDrawableHeight; + resolveUri(); - requestLayout(); + + if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) { + requestLayout(); + } invalidate(); } } @@ -376,8 +374,15 @@ public class ImageView extends View { updateDrawable(null); mResource = 0; mUri = uri; + + final int oldWidth = mDrawableWidth; + final int oldHeight = mDrawableHeight; + resolveUri(); - requestLayout(); + + if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) { + requestLayout(); + } invalidate(); } } @@ -392,8 +397,8 @@ public class ImageView extends View { mResource = 0; mUri = null; - int oldWidth = mDrawableWidth; - int oldHeight = mDrawableHeight; + final int oldWidth = mDrawableWidth; + final int oldHeight = mDrawableHeight; updateDrawable(drawable); @@ -675,6 +680,7 @@ public class ImageView extends View { d.setState(getDrawableState()); } d.setLevel(mLevel); + d.setLayoutDirection(getLayoutDirection()); mDrawableWidth = d.getIntrinsicWidth(); mDrawableHeight = d.getIntrinsicHeight(); applyColorMod(); diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 09c0129..b6f0862 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -345,28 +345,42 @@ public class LinearLayout extends ViewGroup { void drawDividersHorizontal(Canvas canvas) { final int count = getVirtualChildCount(); + final boolean isLayoutRtl = isLayoutRtl(); for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child != null && child.getVisibility() != GONE) { if (hasDividerBeforeChildAt(i)) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - final int left = child.getLeft() - lp.leftMargin - mDividerWidth; - drawVerticalDivider(canvas, left); + final int position; + if (isLayoutRtl) { + position = child.getRight() + lp.rightMargin; + } else { + position = child.getLeft() - lp.leftMargin - mDividerWidth; + } + drawVerticalDivider(canvas, position); } } } if (hasDividerBeforeChildAt(count)) { final View child = getVirtualChildAt(count - 1); - int right = 0; + int position; if (child == null) { - right = getWidth() - getPaddingRight() - mDividerWidth; + if (isLayoutRtl) { + position = getPaddingLeft(); + } else { + position = getWidth() - getPaddingRight() - mDividerWidth; + } } else { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - right = child.getRight() + lp.rightMargin; + if (isLayoutRtl) { + position = child.getLeft() - lp.leftMargin - mDividerWidth; + } else { + position = child.getRight() + lp.rightMargin; + } } - drawVerticalDivider(canvas, right); + drawVerticalDivider(canvas, position); } } @@ -1481,7 +1495,7 @@ public class LinearLayout extends ViewGroup { if (gravity < 0) { gravity = minorGravity; } - final int layoutDirection = getResolvedLayoutDirection(); + final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: @@ -1545,7 +1559,7 @@ public class LinearLayout extends ViewGroup { final int[] maxAscent = mMaxAscent; final int[] maxDescent = mMaxDescent; - final int layoutDirection = getResolvedLayoutDirection(); + final int layoutDirection = getLayoutDirection(); switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) { case Gravity.RIGHT: // mTotalLength contains the padding already diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 1d966b3..1c81d11 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -21,6 +21,7 @@ import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; @@ -31,6 +32,8 @@ import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.ViewParent; +import java.util.Locale; + /** * A ListPopupWindow anchors itself to a host view and displays a * list of choices. @@ -92,6 +95,8 @@ public class ListPopupWindow { private boolean mModal; + private int mLayoutDirection; + /** * The provided prompt view should appear above list content. * @@ -193,6 +198,9 @@ public class ListPopupWindow { mContext = context; mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes); mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + // Set the default layout direction to match the default locale one + final Locale locale = mContext.getResources().getConfiguration().locale; + mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(locale); } /** @@ -1013,6 +1021,8 @@ public class ListPopupWindow { mDropDownList.setOnItemSelectedListener(mItemSelectedListener); } + mDropDownList.setLayoutDirection(mLayoutDirection); + dropDownView = mDropDownList; View hintView = mPromptView; @@ -1122,6 +1132,21 @@ public class ListPopupWindow { } /** + * Set the layout direction for this popup. Should be a resolved direction as the + * popup as no capacity to do the resolution on his own. + * + * @param layoutDirection One of {@link View#LAYOUT_DIRECTION_LTR}, + * {@link View#LAYOUT_DIRECTION_RTL}, + * + */ + public void setLayoutDirection(int layoutDirection) { + mLayoutDirection = layoutDirection; + if (mDropDownList != null) { + mDropDownList.setLayoutDirection(mLayoutDirection); + } + } + + /** * <p>Wrapper class for a ListView. This wrapper can hijack the focus to * make sure the list uses the appropriate drawables and states when * displayed on screen within a drop down. The focus is never actually diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index e011c13..03507b5 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -29,6 +29,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.MathUtils; import android.util.SparseBooleanArray; import android.view.FocusFinder; import android.view.KeyEvent; @@ -37,6 +38,7 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; @@ -1490,6 +1492,10 @@ public class ListView extends AbsListView { View focusLayoutRestoreView = null; + AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null; + View accessibilityFocusLayoutRestoreView = null; + int accessibilityFocusPosition = INVALID_POSITION; + // Remember stuff we will need down below switch (mLayoutMode) { case LAYOUT_SET_SELECTION: @@ -1584,6 +1590,30 @@ public class ListView extends AbsListView { requestFocus(); } + // Remember which child, if any, had accessibility focus. + final ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + final View accessFocusedView = viewRootImpl.getAccessibilityFocusedHost(); + if (accessFocusedView != null) { + final View accessFocusedChild = findAccessibilityFocusedChild( + accessFocusedView); + if (accessFocusedChild != null) { + if (!dataChanged || isDirectChildHeaderOrFooter(accessFocusedChild)) { + // If the views won't be changing, try to maintain + // focus on the current view host and (if + // applicable) its virtual view. + accessibilityFocusLayoutRestoreView = accessFocusedView; + accessibilityFocusLayoutRestoreNode = viewRootImpl + .getAccessibilityFocusedVirtualView(); + } else { + // Otherwise, try to maintain focus at the same + // position. + accessibilityFocusPosition = getPositionForView(accessFocusedChild); + } + } + } + } + // Clear out old views detachAllViewsFromParent(); recycleBin.removeSkippedScrap(); @@ -1682,6 +1712,22 @@ public class ListView extends AbsListView { } } + // Attempt to restore accessibility focus. + if (accessibilityFocusLayoutRestoreNode != null) { + accessibilityFocusLayoutRestoreNode.performAction( + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); + } else if (accessibilityFocusLayoutRestoreView != null) { + accessibilityFocusLayoutRestoreView.requestAccessibilityFocus(); + } else if (accessibilityFocusPosition != INVALID_POSITION) { + // Bound the position within the visible children. + final int position = MathUtils.constrain( + (accessibilityFocusPosition - mFirstPosition), 0, (getChildCount() - 1)); + final View restoreView = getChildAt(position); + if (restoreView != null) { + restoreView.requestAccessibilityFocus(); + } + } + // tell focus view we are done mucking with it, if it is still in // our view hierarchy. if (focusLayoutRestoreView != null @@ -1713,6 +1759,22 @@ public class ListView extends AbsListView { } /** + * @param focusedView the view that has accessibility focus. + * @return the direct child that contains accessibility focus. + */ + private View findAccessibilityFocusedChild(View focusedView) { + ViewParent viewParent = focusedView.getParent(); + while ((viewParent instanceof View) && (viewParent != this)) { + focusedView = (View) viewParent; + viewParent = viewParent.getParent(); + } + if (!(viewParent instanceof View)) { + return null; + } + return focusedView; + } + + /** * @param child a direct child of this list. * @return Whether child is a header or footer view. */ diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index a458f57..4918e48 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -51,10 +51,12 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.R; +import libcore.icu.LocaleData; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; /** * A widget that enables the user to select a number form a predefined range. @@ -138,13 +140,6 @@ public class NumberPicker extends LinearLayout { private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker; /** - * The numbers accepted by the input text's {@link Filter} - */ - private static final char[] DIGIT_CHARACTERS = new char[] { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' - }; - - /** * Constant for unspecified size. */ private static final int SIZE_UNSPECIFIED = -1; @@ -154,23 +149,53 @@ public class NumberPicker extends LinearLayout { * strings like "01". Keeping a static formatter etc. is the most efficient * way to do this; it avoids creating temporary objects on every call to * format(). - * - * @hide */ - public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() { + private static class TwoDigitFormatter implements NumberPicker.Formatter { final StringBuilder mBuilder = new StringBuilder(); - final java.util.Formatter mFmt = new java.util.Formatter(mBuilder, java.util.Locale.US); + char mZeroDigit; + java.util.Formatter mFmt; final Object[] mArgs = new Object[1]; + TwoDigitFormatter() { + final Locale locale = Locale.getDefault(); + init(locale); + } + + private void init(Locale locale) { + mFmt = createFormatter(locale); + mZeroDigit = getZeroDigit(locale); + } + public String format(int value) { + final Locale currentLocale = Locale.getDefault(); + if (mZeroDigit != getZeroDigit(currentLocale)) { + init(currentLocale); + } mArgs[0] = value; mBuilder.delete(0, mBuilder.length()); mFmt.format("%02d", mArgs); return mFmt.toString(); } - }; + + private static char getZeroDigit(Locale locale) { + return LocaleData.get(locale).zeroDigit; + } + + private java.util.Formatter createFormatter(Locale locale) { + return new java.util.Formatter(mBuilder, locale); + } + } + + private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter(); + + /** + * @hide + */ + public static final Formatter getTwoDigitFormatter() { + return sTwoDigitFormatter; + } /** * The increment button. @@ -1156,7 +1181,7 @@ public class NumberPicker extends LinearLayout { if (mDisplayedValues == null) { float maxDigitWidth = 0; for (int i = 0; i <= 9; i++) { - final float digitWidth = mSelectorWheelPaint.measureText(String.valueOf(i)); + final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i)); if (digitWidth > maxDigitWidth) { maxDigitWidth = digitWidth; } @@ -1336,6 +1361,14 @@ public class NumberPicker extends LinearLayout { // Allow text entry rather than strictly numeric entry. mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + // Make sure the min, max, respect the size of the displayed + // values. This will take care of the current value as well. + if (getMinValue() >= displayedValues.length) { + setMinValue(0); + } + if (getMaxValue() >= displayedValues.length) { + setMaxValue(displayedValues.length - 1); + } } else { mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER); } @@ -1417,19 +1450,6 @@ public class NumberPicker extends LinearLayout { } @Override - public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { - // We do not want the real descendant to be considered focus search - // since it is managed by the accessibility node provider. - if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) { - if (isAccessibilityFocusable()) { - views.add(this); - return; - } - } - super.addFocusables(views, direction, focusableMode); - } - - @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(NumberPicker.class.getName()); @@ -1702,7 +1722,7 @@ public class NumberPicker extends LinearLayout { } private String formatNumber(int value) { - return (mFormatter != null) ? mFormatter.format(value) : String.valueOf(value); + return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value); } private void validateInputTextView(View v) { @@ -1862,6 +1882,20 @@ public class NumberPicker extends LinearLayout { } /** + * The numbers accepted by the input text's {@link Filter} + */ + private static final char[] DIGIT_CHARACTERS = new char[] { + // Latin digits are the common case + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + // Arabic-Indic + '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668' + , '\u0669', + // Extended Arabic-Indic + '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8' + , '\u06f9' + }; + + /** * Filter for accepting only valid indices or prefixes of the string * representation of valid indices. */ @@ -2297,78 +2331,6 @@ public class NumberPicker extends LinearLayout { return super.performAction(virtualViewId, action, arguments); } - @Override - public AccessibilityNodeInfo findAccessibilityFocus(int virtualViewId) { - return createAccessibilityNodeInfo(mAccessibilityFocusedView); - } - - @Override - public AccessibilityNodeInfo accessibilityFocusSearch(int direction, int virtualViewId) { - switch (direction) { - case View.ACCESSIBILITY_FOCUS_DOWN: - case View.ACCESSIBILITY_FOCUS_FORWARD: { - switch (mAccessibilityFocusedView) { - case UNDEFINED: { - return createAccessibilityNodeInfo(View.NO_ID); - } - case View.NO_ID: { - if (hasVirtualDecrementButton()) { - return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT); - } - } - //$FALL-THROUGH$ - case VIRTUAL_VIEW_ID_DECREMENT: { - return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT); - } - case VIRTUAL_VIEW_ID_INPUT: { - if (hasVirtualIncrementButton()) { - return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT); - } - } - //$FALL-THROUGH$ - case VIRTUAL_VIEW_ID_INCREMENT: { - View nextFocus = NumberPicker.this.focusSearch(direction); - if (nextFocus != null) { - return nextFocus.createAccessibilityNodeInfo(); - } - return null; - } - } - } break; - case View.ACCESSIBILITY_FOCUS_UP: - case View.ACCESSIBILITY_FOCUS_BACKWARD: { - switch (mAccessibilityFocusedView) { - case UNDEFINED: { - return createAccessibilityNodeInfo(View.NO_ID); - } - case View.NO_ID: { - if (hasVirtualIncrementButton()) { - return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT); - } - } - //$FALL-THROUGH$ - case VIRTUAL_VIEW_ID_INCREMENT: { - return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT); - } - case VIRTUAL_VIEW_ID_INPUT: { - if (hasVirtualDecrementButton()) { - return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT); - } - } - //$FALL-THROUGH$ - case VIRTUAL_VIEW_ID_DECREMENT: { - View nextFocus = NumberPicker.this.focusSearch(direction); - if (nextFocus != null) { - return nextFocus.createAccessibilityNodeInfo(); - } - return null; - } - } - } break; - } - return null; - } - public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) { switch (virtualViewId) { case VIRTUAL_VIEW_ID_DECREMENT: { @@ -2390,22 +2352,26 @@ public class NumberPicker extends LinearLayout { } private void sendAccessibilityEventForVirtualText(int eventType) { - AccessibilityEvent event = AccessibilityEvent.obtain(eventType); - mInputText.onInitializeAccessibilityEvent(event); - mInputText.onPopulateAccessibilityEvent(event); - event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT); - requestSendAccessibilityEvent(NumberPicker.this, event); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + mInputText.onInitializeAccessibilityEvent(event); + mInputText.onPopulateAccessibilityEvent(event); + event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT); + requestSendAccessibilityEvent(NumberPicker.this, event); + } } private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType, String text) { - AccessibilityEvent event = AccessibilityEvent.obtain(eventType); - event.setClassName(Button.class.getName()); - event.setPackageName(mContext.getPackageName()); - event.getText().add(text); - event.setEnabled(NumberPicker.this.isEnabled()); - event.setSource(NumberPicker.this, virtualViewId); - requestSendAccessibilityEvent(NumberPicker.this, event); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + event.setClassName(Button.class.getName()); + event.setPackageName(mContext.getPackageName()); + event.getText().add(text); + event.setEnabled(NumberPicker.this.isEnabled()); + event.setSource(NumberPicker.this, virtualViewId); + requestSendAccessibilityEvent(NumberPicker.this, event); + } } private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase, @@ -2506,14 +2472,22 @@ public class NumberPicker extends LinearLayout { info.setParent((View) getParentForAccessibility()); info.setEnabled(NumberPicker.this.isEnabled()); info.setScrollable(true); + + final float applicationScale = + getContext().getResources().getCompatibilityInfo().applicationScale; + Rect boundsInParent = mTempRect; boundsInParent.set(left, top, right, bottom); + boundsInParent.scale(applicationScale); info.setBoundsInParent(boundsInParent); + info.setVisibleToUser(isVisibleToUser()); + Rect boundsInScreen = boundsInParent; int[] locationOnScreen = mTempArray; getLocationOnScreen(locationOnScreen); boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]); + boundsInScreen.scale(applicationScale); info.setBoundsInScreen(boundsInScreen); if (mAccessibilityFocusedView != View.NO_ID) { @@ -2566,4 +2540,8 @@ public class NumberPicker extends LinearLayout { return null; } } + + static private String formatNumberWithLocale(int value) { + return String.format(Locale.getDefault(), "%d", value); + } } diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java index 1c72a0d..f218199 100644 --- a/core/java/android/widget/OverScroller.java +++ b/core/java/android/widget/OverScroller.java @@ -72,10 +72,8 @@ public class OverScroller { public OverScroller(Context context, Interpolator interpolator, boolean flywheel) { mInterpolator = interpolator; mFlywheel = flywheel; - mScrollerX = new SplineOverScroller(); - mScrollerY = new SplineOverScroller(); - - SplineOverScroller.initFromContext(context); + mScrollerX = new SplineOverScroller(context); + mScrollerY = new SplineOverScroller(context); } /** @@ -585,8 +583,8 @@ public class OverScroller { // Constant gravity value, used in the deceleration phase. private static final float GRAVITY = 2000.0f; - // A device specific coefficient adjusted to physical values. - private static float PHYSICAL_COEF; + // A context-specific coefficient adjusted to physical values. + private float mPhysicalCoeff; private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9)); private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1) @@ -636,20 +634,17 @@ public class OverScroller { SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; } - static void initFromContext(Context context) { - final float ppi = context.getResources().getDisplayMetrics().density * 160.0f; - PHYSICAL_COEF = SensorManager.GRAVITY_EARTH // g (m/s^2) - * 39.37f // inch/meter - * ppi - * 0.84f; // look and feel tuning - } - void setFriction(float friction) { mFlingFriction = friction; } - SplineOverScroller() { + SplineOverScroller(Context context) { mFinished = true; + final float ppi = context.getResources().getDisplayMetrics().density * 160.0f; + mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2) + * 39.37f // inch/meter + * ppi + * 0.84f; // look and feel tuning } void updateScroll(float q) { @@ -785,13 +780,13 @@ public class OverScroller { } private double getSplineDeceleration(int velocity) { - return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * PHYSICAL_COEF)); + return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff)); } private double getSplineFlingDistance(int velocity) { final double l = getSplineDeceleration(velocity); final double decelMinusOne = DECELERATION_RATE - 1.0; - return mFlingFriction * PHYSICAL_COEF * Math.exp(DECELERATION_RATE / decelMinusOne * l); + return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l); } /* Returns the duration, expressed in milliseconds */ diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index f442912..af3365e 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -835,7 +835,7 @@ public class PopupWindow { preparePopup(p); if (gravity == Gravity.NO_GRAVITY) { - gravity = Gravity.TOP | Gravity.LEFT; + gravity = Gravity.TOP | Gravity.START; } p.gravity = gravity; p.x = x; @@ -1003,7 +1003,7 @@ public class PopupWindow { // screen. The view is then positioned to the appropriate location // by setting the x and y offsets to match the anchor's bottom // left corner - p.gravity = Gravity.LEFT | Gravity.TOP; + p.gravity = Gravity.START | Gravity.TOP; p.width = mLastWidth = mWidth; p.height = mLastHeight = mHeight; if (mBackground != null) { @@ -1100,7 +1100,7 @@ public class PopupWindow { boolean onTop = false; - p.gravity = Gravity.LEFT | Gravity.TOP; + p.gravity = Gravity.START | Gravity.TOP; anchor.getLocationOnScreen(mScreenLocation); final Rect displayFrame = new Rect(); @@ -1134,7 +1134,7 @@ public class PopupWindow { onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) < (mScreenLocation[1] - yoff - displayFrame.top); if (onTop) { - p.gravity = Gravity.LEFT | Gravity.BOTTOM; + p.gravity = Gravity.START | Gravity.BOTTOM; p.y = root.getHeight() - mDrawingLocation[1] + yoff; } else { p.y = mDrawingLocation[1] + anchor.getHeight() + yoff; diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index f3f18d5..ea50e2e 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -478,6 +478,9 @@ public class ProgressBar extends View { d.setCallback(this); } mIndeterminateDrawable = d; + if (mIndeterminateDrawable != null && canResolveLayoutDirection()) { + mIndeterminateDrawable.setLayoutDirection(getLayoutDirection()); + } if (mIndeterminate) { mCurrentDrawable = d; postInvalidate(); @@ -517,6 +520,9 @@ public class ProgressBar extends View { if (d != null) { d.setCallback(this); + if (canResolveLayoutDirection()) { + d.setLayoutDirection(getLayoutDirection()); + } // Make sure the ProgressBar is always tall enough int drawableHeight = d.getMinimumHeight(); @@ -559,6 +565,23 @@ public class ProgressBar extends View { if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); } + /** + * @hide + */ + @Override + public void onResolveDrawables(int layoutDirection) { + final Drawable d = mCurrentDrawable; + if (d != null) { + d.setLayoutDirection(layoutDirection); + } + if (mIndeterminateDrawable != null) { + mIndeterminateDrawable.setLayoutDirection(layoutDirection); + } + if (mProgressDrawable != null) { + mProgressDrawable.setLayoutDirection(layoutDirection); + } + } + @Override public void postInvalidate() { if (!mNoInvalidate) { @@ -648,6 +671,9 @@ public class ProgressBar extends View { if (d instanceof LayerDrawable) { progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id); + if (progressDrawable != null && canResolveLayoutDirection()) { + progressDrawable.setLayoutDirection(getLayoutDirection()); + } } final int level = (int) (scale * MAX_LEVEL); @@ -975,24 +1001,19 @@ public class ProgressBar extends View { } } - /** - * @hide - */ - @Override - public int getResolvedLayoutDirection(Drawable who) { - return (who == mProgressDrawable || who == mIndeterminateDrawable) ? - getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who); - } - @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { updateDrawableBounds(w, h); } private void updateDrawableBounds(int w, int h) { - // onDraw will translate the canvas so we draw starting at 0,0 - int right = w - mPaddingRight - mPaddingLeft; - int bottom = h - mPaddingBottom - mPaddingTop; + // onDraw will translate the canvas so we draw starting at 0,0. + // Subtract out padding for the purposes of the calculations below. + w -= mPaddingRight + mPaddingLeft; + h -= mPaddingTop + mPaddingBottom; + + int right = w; + int bottom = h; int top = 0; int left = 0; @@ -1019,6 +1040,11 @@ public class ProgressBar extends View { } } } + if (isLayoutRtl()) { + int tempLeft = left; + left = w - right; + right = w - tempLeft; + } mIndeterminateDrawable.setBounds(left, top, right, bottom); } @@ -1036,7 +1062,12 @@ public class ProgressBar extends View { // Translate canvas so a indeterminate circular progress bar with padding // rotates properly in its animation canvas.save(); - canvas.translate(mPaddingLeft, mPaddingTop); + if(isLayoutRtl()) { + canvas.translate(getWidth() - mPaddingRight, mPaddingTop); + canvas.scale(-1.0f, 1.0f); + } else { + canvas.translate(mPaddingLeft, mPaddingTop); + } long time = getDrawingTime(); if (mHasAnimation) { mAnimation.getTransformation(time, mTransformation); diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index f217c9c..42d63b2 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -297,33 +297,6 @@ public class RadioGroup extends LinearLayout { public LayoutParams(MarginLayoutParams source) { super(source); } - - /** - * <p>Fixes the child's width to - * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's - * height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} - * when not specified in the XML file.</p> - * - * @param a the styled attributes set - * @param widthAttr the width attribute to fetch - * @param heightAttr the height attribute to fetch - */ - @Override - protected void setBaseAttributes(TypedArray a, - int widthAttr, int heightAttr) { - - if (a.hasValue(widthAttr)) { - width = a.getLayoutDimension(widthAttr, "layout_width"); - } else { - width = WRAP_CONTENT; - } - - if (a.hasValue(heightAttr)) { - height = a.getLayoutDimension(heightAttr, "layout_height"); - } else { - height = WRAP_CONTENT; - } - } } /** @@ -376,7 +349,7 @@ public class RadioGroup extends LinearLayout { int id = child.getId(); // generates an id if it's missing if (id == View.NO_ID) { - id = child.hashCode(); + id = View.generateViewId(); child.setId(id); } ((RadioButton) child).setOnCheckedChangeWidgetListener( diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 569cf99..455355f 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -149,8 +149,34 @@ public class RelativeLayout extends ViewGroup { * bounds of its RelativeLayout parent. */ public static final int CENTER_VERTICAL = 15; + /** + * Rule that aligns a child's end edge with another child's start edge. + */ + public static final int START_OF = 16; + /** + * Rule that aligns a child's start edge with another child's end edge. + */ + public static final int END_OF = 17; + /** + * Rule that aligns a child's start edge with another child's start edge. + */ + public static final int ALIGN_START = 18; + /** + * Rule that aligns a child's end edge with another child's end edge. + */ + public static final int ALIGN_END = 19; + /** + * Rule that aligns the child's start edge with its RelativeLayout + * parent's start edge. + */ + public static final int ALIGN_PARENT_START = 20; + /** + * Rule that aligns the child's end edge with its RelativeLayout + * parent's end edge. + */ + public static final int ALIGN_PARENT_END = 21; - private static final int VERB_COUNT = 16; + private static final int VERB_COUNT = 22; private static final int[] RULES_VERTICAL = { @@ -158,13 +184,13 @@ public class RelativeLayout extends ViewGroup { }; private static final int[] RULES_HORIZONTAL = { - LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT + LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END }; private View mBaselineView = null; private boolean mHasBaselineAlignedChild; - private int mGravity = Gravity.LEFT | Gravity.TOP; + private int mGravity = Gravity.START | Gravity.TOP; private final Rect mContentBounds = new Rect(); private final Rect mSelfBounds = new Rect(); private int mIgnoreGravity; @@ -204,7 +230,7 @@ public class RelativeLayout extends ViewGroup { /** * Defines which View is ignored when the gravity is applied. This setting has no - * effect if the gravity is <code>Gravity.LEFT | Gravity.TOP</code>. + * effect if the gravity is <code>Gravity.START | Gravity.TOP</code>. * * @param viewId The id of the View to be ignored by gravity, or 0 if no View * should be ignored. @@ -234,7 +260,7 @@ public class RelativeLayout extends ViewGroup { /** * Describes how the child views are positioned. Defaults to - * <code>Gravity.LEFT | Gravity.TOP</code>. + * <code>Gravity.START | Gravity.TOP</code>. * * <p>Note that since RelativeLayout considers the positioning of each child * relative to one another to be significant, setting gravity will affect @@ -369,7 +395,7 @@ public class RelativeLayout extends ViewGroup { View ignore = null; int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; - final boolean horizontalGravity = gravity != Gravity.LEFT && gravity != 0; + final boolean horizontalGravity = gravity != Gravity.START && gravity != 0; gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0; @@ -457,6 +483,8 @@ public class RelativeLayout extends ViewGroup { } } + final int layoutDirection = getLayoutDirection(); + if (isWrapContentWidth) { // Width already has left padding in it since it was calculated by looking at // the right of each child view @@ -474,7 +502,7 @@ public class RelativeLayout extends ViewGroup { View child = getChildAt(i); if (child.getVisibility() != GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams(); - final int[] rules = params.getRules(); + final int[] rules = params.getRules(layoutDirection); if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) { centerHorizontal(child, params, width); } else if (rules[ALIGN_PARENT_RIGHT] != 0) { @@ -504,7 +532,7 @@ public class RelativeLayout extends ViewGroup { View child = getChildAt(i); if (child.getVisibility() != GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams(); - final int[] rules = params.getRules(); + final int[] rules = params.getRules(layoutDirection); if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { centerVertical(child, params, height); } else if (rules[ALIGN_PARENT_BOTTOM] != 0) { @@ -523,7 +551,6 @@ public class RelativeLayout extends ViewGroup { height - mPaddingBottom); final Rect contentBounds = mContentBounds; - final int layoutDirection = getResolvedLayoutDirection(); Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds, layoutDirection); @@ -551,7 +578,8 @@ public class RelativeLayout extends ViewGroup { } private void alignBaseline(View child, LayoutParams params) { - int[] rules = params.getRules(); + final int layoutDirection = getLayoutDirection(); + int[] rules = params.getRules(layoutDirection); int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE); if (anchorBaseline != -1) { @@ -619,7 +647,7 @@ public class RelativeLayout extends ViewGroup { /** * Get a measure spec that accounts for all of the constraints on this view. - * This includes size contstraints imposed by the RelativeLayout as well as + * This includes size constraints imposed by the RelativeLayout as well as * the View's desired dimension. * * @param childStart The left or top field of the child's layout params @@ -672,7 +700,7 @@ public class RelativeLayout extends ViewGroup { childSpecSize = childSize; } } else if (childSize == LayoutParams.MATCH_PARENT) { - // Child wanted to be as big as possible. Give all availble + // Child wanted to be as big as possible. Give all available // space childSpecMode = MeasureSpec.EXACTLY; childSpecSize = maxAvailable; @@ -681,7 +709,7 @@ public class RelativeLayout extends ViewGroup { // to communicate available space if we know // our max size if (maxAvailable >= 0) { - // We have a maxmum size in this dimension. + // We have a maximum size in this dimension. childSpecMode = MeasureSpec.AT_MOST; childSpecSize = maxAvailable; } else { @@ -699,7 +727,9 @@ public class RelativeLayout extends ViewGroup { private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth, boolean wrapContent) { - int[] rules = params.getRules(); + final int layoutDirection = getLayoutDirection(); + int[] rules = params.getRules(layoutDirection); + params.onResolveLayoutDirection(layoutDirection); if (params.mLeft < 0 && params.mRight >= 0) { // Right is fixed, but left varies @@ -718,11 +748,18 @@ public class RelativeLayout extends ViewGroup { } return true; } else { - params.mLeft = mPaddingLeft + params.leftMargin; - params.mRight = params.mLeft + child.getMeasuredWidth(); + // This is the default case. For RTL we start from the right and for LTR we start + // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL. + if (isLayoutRtl()) { + params.mRight = myWidth - mPaddingRight- params.rightMargin; + params.mLeft = params.mRight - child.getMeasuredWidth(); + } else { + params.mLeft = mPaddingLeft + params.leftMargin; + params.mRight = params.mLeft + child.getMeasuredWidth(); + } } } - return rules[ALIGN_PARENT_RIGHT] != 0; + return rules[ALIGN_PARENT_END] != 0; } private boolean positionChildVertical(View child, LayoutParams params, int myHeight, @@ -755,7 +792,8 @@ public class RelativeLayout extends ViewGroup { } private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) { - int[] rules = childParams.getRules(); + final int layoutDirection = getLayoutDirection(); + int[] rules = childParams.getRules(layoutDirection); RelativeLayout.LayoutParams anchorParams; // -1 indicated a "soft requirement" in that direction. For example: @@ -945,8 +983,8 @@ public class RelativeLayout extends ViewGroup { if (child.getVisibility() != GONE) { RelativeLayout.LayoutParams st = (RelativeLayout.LayoutParams) child.getLayoutParams(); + st.onResolveLayoutDirection(getLayoutDirection()); child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom); - } } } @@ -1061,6 +1099,12 @@ public class RelativeLayout extends ViewGroup { * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart + * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd */ public static class LayoutParams extends ViewGroup.MarginLayoutParams { @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = { @@ -1079,15 +1123,28 @@ public class RelativeLayout extends ViewGroup { @ViewDebug.IntToString(from = CENTER_IN_PARENT, to = "center"), @ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"), @ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"), - @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf") + @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf"), + @ViewDebug.IntToString(from = ALIGN_START, to = "alignStart"), + @ViewDebug.IntToString(from = ALIGN_END, to = "alignEnd"), + @ViewDebug.IntToString(from = ALIGN_PARENT_START, to = "alignParentStart"), + @ViewDebug.IntToString(from = ALIGN_PARENT_END, to = "alignParentEnd"), + @ViewDebug.IntToString(from = START_OF, to = "startOf"), + @ViewDebug.IntToString(from = END_OF, to = "endOf") }, mapping = { @ViewDebug.IntToString(from = TRUE, to = "true"), @ViewDebug.IntToString(from = 0, to = "false/NO_ID") }) + private int[] mRules = new int[VERB_COUNT]; + private int[] mInitialRules = new int[VERB_COUNT]; private int mLeft, mTop, mRight, mBottom; + private int mStart = DEFAULT_RELATIVE; + private int mEnd = DEFAULT_RELATIVE; + + private boolean mRulesChanged = false; + /** * When true, uses the parent as the anchor if the anchor doesn't exist or if * the anchor's visibility is GONE. @@ -1102,6 +1159,7 @@ public class RelativeLayout extends ViewGroup { com.android.internal.R.styleable.RelativeLayout_Layout); final int[] rules = mRules; + final int[] initialRules = mInitialRules; final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { @@ -1158,9 +1216,31 @@ public class RelativeLayout extends ViewGroup { case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical: rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0; break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf: + rules[START_OF] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf: + rules[END_OF] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart: + rules[ALIGN_START] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd: + rules[ALIGN_END] = a.getResourceId(attr, 0); + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart: + rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0; + break; + case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd: + rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0; + break; } } + for (int n = LEFT_OF; n < VERB_COUNT; n++) { + initialRules[n] = rules[n]; + } + a.recycle(); } @@ -1192,7 +1272,7 @@ public class RelativeLayout extends ViewGroup { * Adds a layout rule to be interpreted by the RelativeLayout. This * method should only be used for constraints that don't refer to another sibling * (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE} - * for true or - for false). To specify a verb that takes a subject, use + * for true or 0 for false). To specify a verb that takes a subject, use * {@link #addRule(int, int)} instead. * * @param verb One of the verbs defined by @@ -1202,6 +1282,8 @@ public class RelativeLayout extends ViewGroup { */ public void addRule(int verb) { mRules[verb] = TRUE; + mInitialRules[verb] = TRUE; + mRulesChanged = true; } /** @@ -1220,12 +1302,88 @@ public class RelativeLayout extends ViewGroup { */ public void addRule(int verb, int anchor) { mRules[verb] = anchor; + mInitialRules[verb] = anchor; + mRulesChanged = true; + } + + /** + * Removes a layout rule to be interpreted by the RelativeLayout. + * + * @param verb One of the verbs defined by + * {@link android.widget.RelativeLayout RelativeLayout}, such as + * ALIGN_WITH_PARENT_LEFT. + * @see #addRule(int) + * @see #addRule(int, int) + */ + public void removeRule(int verb) { + mRules[verb] = 0; + mInitialRules[verb] = 0; + mRulesChanged = true; + } + + private boolean hasRelativeRules() { + return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 || + mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 || + mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0); + } + + private void resolveRules(int layoutDirection) { + final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL); + // Reset to initial state + for (int n = LEFT_OF; n < VERB_COUNT; n++) { + mRules[n] = mInitialRules[n]; + } + // Apply rules depending on direction + if (mRules[ALIGN_START] != 0) { + mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START]; + } + if (mRules[ALIGN_END] != 0) { + mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END]; + } + if (mRules[START_OF] != 0) { + mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF]; + } + if (mRules[END_OF] != 0) { + mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF]; + } + if (mRules[ALIGN_PARENT_START] != 0) { + mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START]; + } + if (mRules[ALIGN_PARENT_END] != 0) { + mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END]; + } + mRulesChanged = false; } /** * Retrieves a complete list of all supported rules, where the index is the rule * verb, and the element value is the value specified, or "false" if it was never - * set. + * set. If there are relative rules defined (*_START / *_END), they will be resolved + * depending on the layout direction. + * + * @param layoutDirection the direction of the layout. + * Should be either {@link View#LAYOUT_DIRECTION_LTR} + * or {@link View#LAYOUT_DIRECTION_RTL} + * @return the supported rules + * @see #addRule(int, int) + * + * @hide + */ + public int[] getRules(int layoutDirection) { + if (hasRelativeRules() && + (mRulesChanged || layoutDirection != getLayoutDirection())) { + resolveRules(layoutDirection); + if (layoutDirection != getLayoutDirection()) { + setLayoutDirection(layoutDirection); + } + } + return mRules; + } + + /** + * Retrieves a complete list of all supported rules, where the index is the rule + * verb, and the element value is the value specified, or "false" if it was never + * set. There will be no resolution of relative rules done. * * @return the supported rules * @see #addRule(int, int) @@ -1233,6 +1391,24 @@ public class RelativeLayout extends ViewGroup { public int[] getRules() { return mRules; } + + @Override + public void onResolveLayoutDirection(int layoutDirection) { + final boolean isLayoutRtl = isLayoutRtl(); + if (isLayoutRtl) { + if (mStart != DEFAULT_RELATIVE) mRight = mStart; + if (mEnd != DEFAULT_RELATIVE) mLeft = mEnd; + } else { + if (mStart != DEFAULT_RELATIVE) mLeft = mStart; + if (mEnd != DEFAULT_RELATIVE) mRight = mEnd; + } + + if (hasRelativeRules() && layoutDirection != getLayoutDirection()) { + resolveRules(layoutDirection); + } + // This will set the layout direction + super.onResolveLayoutDirection(layoutDirection); + } } private static class DependencyGraph { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 4710798..8d83774 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -35,9 +34,9 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.LayoutInflater.Filter; import android.view.RemotableViewMethod; @@ -52,6 +51,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.HashMap; /** @@ -61,9 +61,9 @@ import java.util.ArrayList; * the content of the inflated hierarchy. */ public class RemoteViews implements Parcelable, Filter { - + private static final String LOG_TAG = "RemoteViews"; - + /** * The intent extra that contains the appWidgetId. * @hide @@ -71,11 +71,18 @@ public class RemoteViews implements Parcelable, Filter { static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; /** - * The package name of the package containing the layout + * User that these views should be applied as. Requires + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} when + * crossing user boundaries. + */ + private UserHandle mUser = android.os.Process.myUserHandle(); + + /** + * The package name of the package containing the layout * resource. (Added to the parcel) */ private final String mPackage; - + /** * The resource ID of the layout file. (Added to the parcel) */ @@ -86,7 +93,7 @@ public class RemoteViews implements Parcelable, Filter { * inflated */ private ArrayList<Action> mActions; - + /** * A class to keep track of memory usage by this RemoteViews */ @@ -187,6 +194,10 @@ public class RemoteViews implements Parcelable, Filter { public abstract void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException; + public static final int MERGE_REPLACE = 0; + public static final int MERGE_APPEND = 1; + public static final int MERGE_IGNORE = 2; + public int describeContents() { return 0; } @@ -203,6 +214,67 @@ public class RemoteViews implements Parcelable, Filter { public void setBitmapCache(BitmapCache bitmapCache) { // Do nothing } + + public int mergeBehavior() { + return MERGE_REPLACE; + } + + public abstract String getActionName(); + + public String getUniqueKey() { + return (getActionName() + viewId); + } + + int viewId; + } + + /** + * Merges the passed RemoteViews actions with this RemoteViews actions according to + * action-specific merge rules. + * + * @param newRv + * + * @hide + */ + public void mergeRemoteViews(RemoteViews newRv) { + if (newRv == null) return; + // We first copy the new RemoteViews, as the process of merging modifies the way the actions + // reference the bitmap cache. We don't want to modify the object as it may need to + // be merged and applied multiple times. + RemoteViews copy = newRv.clone(); + + HashMap<String, Action> map = new HashMap<String, Action>(); + if (mActions == null) { + mActions = new ArrayList<Action>(); + } + + int count = mActions.size(); + for (int i = 0; i < count; i++) { + Action a = mActions.get(i); + map.put(a.getUniqueKey(), a); + } + + ArrayList<Action> newActions = copy.mActions; + if (newActions == null) return; + count = newActions.size(); + for (int i = 0; i < count; i++) { + Action a = newActions.get(i); + String key = newActions.get(i).getUniqueKey(); + int mergeBehavior = newActions.get(i).mergeBehavior(); + if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) { + mActions.remove(map.get(key)); + map.remove(key); + } + + // If the merge behavior is ignore, we don't bother keeping the extra action + if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) { + mActions.add(a); + } + } + + // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache + mBitmapCache = new BitmapCache(); + setBitmapCache(mBitmapCache); } private class SetEmptyView extends Action { @@ -239,6 +311,10 @@ public class RemoteViews implements Parcelable, Filter { adapterView.setEmptyView(emptyView); } + + public String getActionName() { + return "SetEmptyView"; + } } private class SetOnClickFillInIntent extends Action { @@ -316,7 +392,10 @@ public class RemoteViews implements Parcelable, Filter { } } - int viewId; + public String getActionName() { + return "SetOnClickFillInIntent"; + } + Intent fillInIntent; public final static int TAG = 9; @@ -399,7 +478,10 @@ public class RemoteViews implements Parcelable, Filter { } } - int viewId; + public String getActionName() { + return "SetPendingIntentTemplate"; + } + PendingIntent pendingIntentTemplate; public final static int TAG = 8; @@ -447,13 +529,18 @@ public class RemoteViews implements Parcelable, Filter { if (target instanceof AbsListView) { AbsListView v = (AbsListView) target; v.setRemoteViewsAdapter(intent); + v.setRemoteViewsOnClickHandler(handler); } else if (target instanceof AdapterViewAnimator) { AdapterViewAnimator v = (AdapterViewAnimator) target; v.setRemoteViewsAdapter(intent); + v.setRemoteViewsOnClickHandler(handler); } } - int viewId; + public String getActionName() { + return "SetRemoteViewsAdapterIntent"; + } + Intent intent; public final static int TAG = 10; @@ -522,13 +609,13 @@ public class RemoteViews implements Parcelable, Filter { .getCompatibilityInfo().applicationScale; final int[] pos = new int[2]; v.getLocationOnScreen(pos); - + final Rect rect = new Rect(); rect.left = (int) (pos[0] * appScale + 0.5f); rect.top = (int) (pos[1] * appScale + 0.5f); rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); - + final Intent intent = new Intent(); intent.setSourceBounds(rect); handler.onClickHandler(v, pendingIntent, intent); @@ -539,7 +626,10 @@ public class RemoteViews implements Parcelable, Filter { } } - int viewId; + public String getActionName() { + return "SetOnClickPendingIntent"; + } + PendingIntent pendingIntent; public final static int TAG = 1; @@ -567,7 +657,7 @@ public class RemoteViews implements Parcelable, Filter { this.filterMode = mode; this.level = level; } - + public SetDrawableParameters(Parcel parcel) { viewId = parcel.readInt(); targetBackground = parcel.readInt() != 0; @@ -581,7 +671,7 @@ public class RemoteViews implements Parcelable, Filter { } level = parcel.readInt(); } - + public void writeToParcel(Parcel dest, int flags) { dest.writeInt(TAG); dest.writeInt(viewId); @@ -596,12 +686,12 @@ public class RemoteViews implements Parcelable, Filter { } dest.writeInt(level); } - + @Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { final View target = root.findViewById(viewId); if (target == null) return; - + // Pick the correct drawable to modify for this view Drawable targetDrawable = null; if (targetBackground) { @@ -610,7 +700,7 @@ public class RemoteViews implements Parcelable, Filter { ImageView imageView = (ImageView) target; targetDrawable = imageView.getDrawable(); } - + if (targetDrawable != null) { // Perform modifications only if values are set correctly if (alpha != -1) { @@ -625,7 +715,10 @@ public class RemoteViews implements Parcelable, Filter { } } - int viewId; + public String getActionName() { + return "SetDrawableParameters"; + } + boolean targetBackground; int alpha; int colorFilter; @@ -634,9 +727,8 @@ public class RemoteViews implements Parcelable, Filter { public final static int TAG = 3; } - + private class ReflectionActionWithoutParams extends Action { - int viewId; String methodName; public final static int TAG = 5; @@ -688,6 +780,19 @@ public class RemoteViews implements Parcelable, Filter { throw new ActionException(ex); } } + + public int mergeBehavior() { + // we don't need to build up showNext or showPrevious calls + if (methodName.equals("showNext") || methodName.equals("showPrevious")) { + return MERGE_IGNORE; + } else { + return MERGE_REPLACE; + } + } + + public String getActionName() { + return "ReflectionActionWithoutParams"; + } } private static class BitmapCache { @@ -755,7 +860,6 @@ public class RemoteViews implements Parcelable, Filter { private class BitmapReflectionAction extends Action { int bitmapId; - int viewId; Bitmap bitmap; String methodName; @@ -794,6 +898,10 @@ public class RemoteViews implements Parcelable, Filter { bitmapId = bitmapCache.getBitmapId(bitmap); } + public String getActionName() { + return "BitmapReflectionAction"; + } + public final static int TAG = 12; } @@ -814,11 +922,12 @@ public class RemoteViews implements Parcelable, Filter { static final int STRING = 9; static final int CHAR_SEQUENCE = 10; static final int URI = 11; + // BITMAP actions are never stored in the list of actions. They are only used locally + // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache. static final int BITMAP = 12; static final int BUNDLE = 13; static final int INTENT = 14; - int viewId; String methodName; int type; Object value; @@ -938,7 +1047,7 @@ public class RemoteViews implements Parcelable, Filter { out.writeString((String)this.value); break; case CHAR_SEQUENCE: - TextUtils.writeToParcel((CharSequence)this.value, out, flags); + TextUtils.writeToParcel((CharSequence)this.value, out, flags); break; case URI: out.writeInt(this.value != null ? 1 : 0); @@ -1041,20 +1150,20 @@ public class RemoteViews implements Parcelable, Filter { } } - @Override - public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { - // We currently only calculate Bitmap memory usage - switch (this.type) { - case BITMAP: - if (this.value != null) { - final Bitmap b = (Bitmap) this.value; - counter.addBitmapMemory(b); - } - break; - default: - break; + public int mergeBehavior() { + // smoothScrollBy is cumulative, everything else overwites. + if (methodName.equals("smoothScrollBy")) { + return MERGE_APPEND; + } else { + return MERGE_REPLACE; } } + + public String getActionName() { + // Each type of reflection action corresponds to a setter, so each should be seen as + // unique from the standpoint of merging. + return "ReflectionAction" + this.methodName + this.type; + } } private void configureRemoteViewsAsChild(RemoteViews rv) { @@ -1131,7 +1240,14 @@ public class RemoteViews implements Parcelable, Filter { } } - int viewId; + public String getActionName() { + return "ViewGroupAction" + this.nestedViews == null ? "Remove" : "Add"; + } + + public int mergeBehavior() { + return MERGE_APPEND; + } + RemoteViews nestedViews; public final static int TAG = 4; @@ -1182,7 +1298,10 @@ public class RemoteViews implements Parcelable, Filter { } } - int viewId; + public String getActionName() { + return "TextViewDrawableAction"; + } + boolean isRelative = false; int d1, d2, d3, d4; @@ -1220,7 +1339,10 @@ public class RemoteViews implements Parcelable, Filter { target.setTextSize(units, size); } - int viewId; + public String getActionName() { + return "TextViewSizeAction"; + } + int units; float size; @@ -1264,7 +1386,10 @@ public class RemoteViews implements Parcelable, Filter { target.setPadding(left, top, right, bottom); } - int viewId; + public String getActionName() { + return "ViewPaddingAction"; + } + int left, top, right, bottom; public final static int TAG = 14; @@ -1314,7 +1439,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Create a new RemoteViews object that will display the views contained * in the specified layout file. - * + * * @param packageName Name of the package that contains the layout resource * @param layoutId The id of the layout resource */ @@ -1328,11 +1453,16 @@ public class RemoteViews implements Parcelable, Filter { recalculateMemoryUsage(); } + /** {@hide} */ + public void setUser(UserHandle user) { + mUser = user; + } + private boolean hasLandscapeAndPortraitLayouts() { return (mLandscape != null) && (mPortrait != null); } - /** + /** * Create a new RemoteViews object that will inflate as the specified * landspace or portrait RemoteViews, depending on the current configuration. * @@ -1364,7 +1494,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Reads a RemoteViews object from a parcel. - * + * * @param parcel */ public RemoteViews(Parcel parcel) { @@ -1450,23 +1580,12 @@ public class RemoteViews implements Parcelable, Filter { recalculateMemoryUsage(); } - @Override - public RemoteViews clone() { - RemoteViews that; - if (!hasLandscapeAndPortraitLayouts()) { - that = new RemoteViews(mPackage, mLayoutId); - if (mActions != null) { - that.mActions = (ArrayList<Action>)mActions.clone(); - } - } else { - RemoteViews land = mLandscape.clone(); - RemoteViews port = mPortrait.clone(); - that = new RemoteViews(land, port); - } - // update the memory usage stats of the cloned RemoteViews - that.recalculateMemoryUsage(); - return that; + public RemoteViews clone() { + Parcel p = Parcel.obtain(); + writeToParcel(p, 0); + p.setDataPosition(0); + return new RemoteViews(p); } public String getPackage() { @@ -1547,7 +1666,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Add an action to be executed on the remote side when apply is called. - * + * * @param a The action to add */ private void addAction(Action a) { @@ -1619,7 +1738,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling View.setVisibility - * + * * @param viewId The id of the view whose visibility should change * @param visibility The new visibility for the view */ @@ -1629,7 +1748,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling TextView.setText - * + * * @param viewId The id of the view whose text should change * @param text The new text for the view */ @@ -1639,7 +1758,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling {@link TextView#setTextSize(int, float)} - * + * * @param viewId The id of the view whose text size should change * @param units The units of size (e.g. COMPLEX_UNIT_SP) * @param size The size of the text @@ -1649,20 +1768,23 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Equivalent to calling + * Equivalent to calling * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. * * @param viewId The id of the view whose text should change * @param left The id of a drawable to place to the left of the text, or 0 * @param top The id of a drawable to place above the text, or 0 * @param right The id of a drawable to place to the right of the text, or 0 - * @param bottom The id of a drawable to place below the text, or 0 + * @param bottom The id of a drawable to place below the text, or 0 */ public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); } /** + * Equivalent to calling {@link + * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. + * * @param viewId The id of the view whose text should change * @param start The id of a drawable to place before the text (relative to the * layout direction), or 0 @@ -1676,17 +1798,17 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling ImageView.setImageResource - * + * * @param viewId The id of the view whose drawable should change * @param srcId The new resource id for the drawable */ - public void setImageViewResource(int viewId, int srcId) { + public void setImageViewResource(int viewId, int srcId) { setInt(viewId, "setImageResource", srcId); } /** * Equivalent to calling ImageView.setImageURI - * + * * @param viewId The id of the view whose drawable should change * @param uri The Uri for the image */ @@ -1696,7 +1818,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling ImageView.setImageBitmap - * + * * @param viewId The id of the view whose bitmap should change * @param bitmap The new Bitmap for the drawable */ @@ -1719,7 +1841,7 @@ public class RemoteViews implements Parcelable, Filter { * {@link Chronometer#setFormat Chronometer.setFormat}, * and {@link Chronometer#start Chronometer.start()} or * {@link Chronometer#stop Chronometer.stop()}. - * + * * @param viewId The id of the {@link Chronometer} to change * @param base The time at which the timer would have read 0:00. This * time should be based off of @@ -1733,21 +1855,21 @@ public class RemoteViews implements Parcelable, Filter { setString(viewId, "setFormat", format); setBoolean(viewId, "setStarted", started); } - + /** * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, * {@link ProgressBar#setProgress ProgressBar.setProgress}, and * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} * * If indeterminate is true, then the values for max and progress are ignored. - * + * * @param viewId The id of the {@link ProgressBar} to change * @param max The 100% value for the progress bar * @param progress The current value of the progress bar. - * @param indeterminate True if the progress bar is indeterminate, + * @param indeterminate True if the progress bar is indeterminate, * false if not. */ - public void setProgressBar(int viewId, int max, int progress, + public void setProgressBar(int viewId, int max, int progress, boolean indeterminate) { setBoolean(viewId, "setIndeterminate", indeterminate); if (!indeterminate) { @@ -1755,12 +1877,12 @@ public class RemoteViews implements Parcelable, Filter { setInt(viewId, "setProgress", progress); } } - + /** * Equivalent to calling * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} * to launch the provided {@link PendingIntent}. - * + * * When setting the on-click action of items within collections (eg. {@link ListView}, * {@link StackView} etc.), this method will not work. Instead, use {@link * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with @@ -1820,7 +1942,7 @@ public class RemoteViews implements Parcelable, Filter { * view. * <p> * You can omit specific calls by marking their values with null or -1. - * + * * @param viewId The id of the view that contains the target * {@link Drawable} * @param targetBackground If true, apply these parameters to the @@ -1846,7 +1968,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. - * + * * @param viewId The id of the view whose text color should change * @param color Sets the text color for all the states (normal, selected, * focused) to be this color. @@ -2034,6 +2156,8 @@ public class RemoteViews implements Parcelable, Filter { * @param value The value to pass to the method. */ public void setUri(int viewId, String methodName, Uri value) { + // Resolve any filesystem path before sending remotely + value = value.getCanonicalUri(); addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); } @@ -2074,15 +2198,25 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Equivalent to calling View.setContentDescription + * Equivalent to calling View.setContentDescription(CharSequence). * - * @param viewId The id of the view whose content description should change - * @param contentDescription The new content description for the view + * @param viewId The id of the view whose content description should change. + * @param contentDescription The new content description for the view. */ public void setContentDescription(int viewId, CharSequence contentDescription) { setCharSequence(viewId, "setContentDescription", contentDescription); } + /** + * Equivalent to calling View.setLabelFor(int). + * + * @param viewId The id of the view whose property to set. + * @param labeledId The id of a view for which this view serves as a label. + */ + public void setLabelFor(int viewId, int labeledId) { + setInt(viewId, "setLabelFor", labeledId); + } + private RemoteViews getRemoteViewsToApply(Context context) { if (hasLandscapeAndPortraitLayouts()) { int orientation = context.getResources().getConfiguration().orientation; @@ -2098,16 +2232,16 @@ public class RemoteViews implements Parcelable, Filter { /** * Inflates the view hierarchy represented by this object and applies * all of the actions. - * + * * <p><strong>Caller beware: this may throw</strong> - * + * * @param context Default context to use * @param parent Parent that the resulting view hierarchy will be attached to. This method * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. * @return The inflated view hierarchy */ public View apply(Context context, ViewGroup parent) { - return apply(context, parent, DEFAULT_ON_CLICK_HANDLER); + return apply(context, parent, null); } /** @hide */ @@ -2135,12 +2269,12 @@ public class RemoteViews implements Parcelable, Filter { * Applies all of the actions to the provided view. * * <p><strong>Caller beware: this may throw</strong> - * + * * @param v The view to apply the actions to. This should be the result of * the {@link #apply(Context,ViewGroup)} call. */ public void reapply(Context context, View v) { - reapply(context, v, DEFAULT_ON_CLICK_HANDLER); + reapply(context, v, null); } /** @hide */ @@ -2163,6 +2297,7 @@ public class RemoteViews implements Parcelable, Filter { private void performApply(View v, ViewGroup parent, OnClickHandler handler) { if (mActions != null) { + handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler; final int count = mActions.size(); for (int i = 0; i < count; i++) { Action a = mActions.get(i); @@ -2177,7 +2312,8 @@ public class RemoteViews implements Parcelable, Filter { if (packageName != null) { try { - c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED); + c = context.createPackageContextAsUser( + packageName, Context.CONTEXT_RESTRICTED, mUser); } catch (NameNotFoundException e) { Log.e(LOG_TAG, "Package name " + packageName + " not found"); c = context; @@ -2191,7 +2327,7 @@ public class RemoteViews implements Parcelable, Filter { /* (non-Javadoc) * Used to restrict the views which can be inflated - * + * * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) */ public boolean onLoadClass(Class clazz) { diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index f0109ce..e481702 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -36,6 +36,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; +import android.widget.RemoteViews.OnClickHandler; import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; @@ -68,6 +69,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback private LayoutInflater mLayoutInflater; private RemoteViewsAdapterServiceConnection mServiceConnection; private WeakReference<RemoteAdapterConnectionCallback> mCallback; + private OnClickHandler mRemoteViewsOnClickHandler; private FixedSizeRemoteViewsCache mCache; private int mVisibleWindowLowerBound; private int mVisibleWindowUpperBound; @@ -277,11 +279,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded * successfully. */ - public void onRemoteViewsLoaded(RemoteViews view) { + public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler) { try { // Remove all the children of this layout first removeAllViews(); - addView(view.apply(getContext(), this)); + addView(view.apply(getContext(), this, handler)); } catch (Exception e) { Log.e(TAG, "Failed to apply RemoteViews."); } @@ -330,7 +332,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // Notify all the references for that position of the newly loaded RemoteViews final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos); for (final RemoteViewsFrameLayout ref : refs) { - ref.onRemoteViewsLoaded(view); + ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler); } refs.clear(); @@ -421,7 +423,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } private RemoteViewsFrameLayout createLoadingView(int position, View convertView, - ViewGroup parent, Object lock, LayoutInflater layoutInflater) { + ViewGroup parent, Object lock, LayoutInflater layoutInflater, OnClickHandler + handler) { // Create and return a new FrameLayout, and setup the references for this position final Context context = parent.getContext(); RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context); @@ -433,7 +436,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback if (mUserLoadingView != null) { // Try to inflate user-specified loading view try { - View loadingView = mUserLoadingView.apply(parent.getContext(), parent); + View loadingView = mUserLoadingView.apply(parent.getContext(), parent, + handler); loadingView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(0)); layout.addView(loadingView); @@ -448,7 +452,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // Use the size of the first row as a guide for the size of the loading view if (mFirstViewHeight < 0) { try { - View firstView = mFirstView.apply(parent.getContext(), parent); + View firstView = mFirstView.apply(parent.getContext(), parent, handler); firstView.measure( MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); @@ -815,6 +819,10 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback return mDataReady; } + public void setRemoteViewsOnClickHandler(OnClickHandler handler) { + mRemoteViewsOnClickHandler = handler; + } + public void saveRemoteViewsCache() { final Pair<Intent.FilterComparison, Integer> key = new Pair<Intent.FilterComparison, Integer> (new Intent.FilterComparison(mIntent), mAppWidgetId); @@ -1102,7 +1110,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // Reuse the convert view where possible if (layout != null) { if (convertViewTypeId == typeId) { - rv.reapply(context, convertViewChild); + rv.reapply(context, convertViewChild, mRemoteViewsOnClickHandler); return layout; } layout.removeAllViews(); @@ -1111,7 +1119,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } // Otherwise, create a new view to be returned - View newView = rv.apply(context, parent); + View newView = rv.apply(context, parent, mRemoteViewsOnClickHandler); newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId)); layout.addView(newView); @@ -1127,7 +1135,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback final RemoteViewsMetaData metaData = mCache.getMetaData(); synchronized (metaData) { loadingView = metaData.createLoadingView(position, convertView, parent, - mCache, mLayoutInflater); + mCache, mLayoutInflater, mRemoteViewsOnClickHandler); } return loadingView; } finally { @@ -1140,7 +1148,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback final RemoteViewsMetaData metaData = mCache.getMetaData(); synchronized (metaData) { loadingView = metaData.createLoadingView(position, convertView, parent, - mCache, mLayoutInflater); + mCache, mLayoutInflater, mRemoteViewsOnClickHandler); } mRequestedViews.add(position, loadingView); diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 8747dc3..bc41931 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -463,6 +463,13 @@ public class ScrollView extends FrameLayout { return true; } + /* + * Don't try to intercept touch if we can't scroll anyway. + */ + if (getScrollY() == 0 && !canScrollVertically(1)) { + return false; + } + switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { /* diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java index a6e83f0..3a28e75 100644 --- a/core/java/android/widget/Scroller.java +++ b/core/java/android/widget/Scroller.java @@ -57,45 +57,70 @@ public class Scroller { private boolean mFlywheel; private float mVelocity; + private float mCurrVelocity; + private int mDistance; + + private float mFlingFriction = ViewConfiguration.getScrollFriction(); private static final int DEFAULT_DURATION = 250; private static final int SCROLL_MODE = 0; private static final int FLING_MODE = 1; - private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9)); - private static float ALPHA = 800; // pixels / seconds - private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance) - private static float END_TENSION = 1.0f - START_TENSION; + private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9)); + private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1) + private static final float START_TENSION = 0.5f; + private static final float END_TENSION = 1.0f; + private static final float P1 = START_TENSION * INFLEXION; + private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION); + private static final int NB_SAMPLES = 100; - private static final float[] SPLINE = new float[NB_SAMPLES + 1]; + private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1]; + private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1]; private float mDeceleration; private final float mPpi; + // A context-specific coefficient adjusted to physical values. + private float mPhysicalCoeff; + static { float x_min = 0.0f; - for (int i = 0; i <= NB_SAMPLES; i++) { - final float t = (float) i / NB_SAMPLES; + float y_min = 0.0f; + for (int i = 0; i < NB_SAMPLES; i++) { + final float alpha = (float) i / NB_SAMPLES; + float x_max = 1.0f; float x, tx, coef; while (true) { x = x_min + (x_max - x_min) / 2.0f; coef = 3.0f * x * (1.0f - x); - tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x; - if (Math.abs(tx - t) < 1E-5) break; - if (tx > t) x_max = x; + tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x; + if (Math.abs(tx - alpha) < 1E-5) break; + if (tx > alpha) x_max = x; else x_min = x; } - final float d = coef + x * x * x; - SPLINE[i] = d; + SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x; + + float y_max = 1.0f; + float y, dy; + while (true) { + y = y_min + (y_max - y_min) / 2.0f; + coef = 3.0f * y * (1.0f - y); + dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y; + if (Math.abs(dy - alpha) < 1E-5) break; + if (dy > alpha) y_max = y; + else y_min = y; + } + SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y; } - SPLINE[NB_SAMPLES] = 1.0f; + SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; // This controls the viscous fluid effect (how much of it) sViscousFluidScale = 8.0f; // must be set to 1.0 (used in viscousFluid()) sViscousFluidNormalize = 1.0f; sViscousFluidNormalize = 1.0f / viscousFluid(1.0f); + } private static float sViscousFluidScale; @@ -129,6 +154,8 @@ public class Scroller { mPpi = context.getResources().getDisplayMetrics().density * 160.0f; mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction()); mFlywheel = flywheel; + + mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning } /** @@ -140,6 +167,7 @@ public class Scroller { */ public final void setFriction(float friction) { mDeceleration = computeDeceleration(friction); + mFlingFriction = friction; } private float computeDeceleration(float friction) { @@ -202,7 +230,8 @@ public class Scroller { * negative. */ public float getCurrVelocity() { - return mVelocity - mDeceleration * timePassed() / 2000.0f; + return mMode == FLING_MODE ? + mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f; } /** @@ -269,11 +298,18 @@ public class Scroller { case FLING_MODE: final float t = (float) timePassed / mDuration; final int index = (int) (NB_SAMPLES * t); - final float t_inf = (float) index / NB_SAMPLES; - final float t_sup = (float) (index + 1) / NB_SAMPLES; - final float d_inf = SPLINE[index]; - final float d_sup = SPLINE[index + 1]; - final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf); + float distanceCoef = 1.f; + float velocityCoef = 0.f; + if (index < NB_SAMPLES) { + final float t_inf = (float) index / NB_SAMPLES; + final float t_sup = (float) (index + 1) / NB_SAMPLES; + final float d_inf = SPLINE_POSITION[index]; + final float d_sup = SPLINE_POSITION[index + 1]; + velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); + distanceCoef = d_inf + (t - t_inf) * velocityCoef; + } + + mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f; mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX)); // Pin to mMinX <= mCurrX <= mMaxX @@ -392,8 +428,7 @@ public class Scroller { float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY); mVelocity = velocity; - final double l = Math.log(START_TENSION * velocity / ALPHA); - mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0))); + mDuration = getSplineFlingDuration(velocity); mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartX = startX; mStartY = startY; @@ -401,25 +436,41 @@ public class Scroller { float coeffX = velocity == 0 ? 1.0f : velocityX / velocity; float coeffY = velocity == 0 ? 1.0f : velocityY / velocity; - int totalDistance = - (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l)); + double totalDistance = getSplineFlingDistance(velocity); + mDistance = (int) (totalDistance * Math.signum(velocity)); mMinX = minX; mMaxX = maxX; mMinY = minY; mMaxY = maxY; - mFinalX = startX + Math.round(totalDistance * coeffX); + mFinalX = startX + (int) Math.round(totalDistance * coeffX); // Pin to mMinX <= mFinalX <= mMaxX mFinalX = Math.min(mFinalX, mMaxX); mFinalX = Math.max(mFinalX, mMinX); - mFinalY = startY + Math.round(totalDistance * coeffY); + mFinalY = startY + (int) Math.round(totalDistance * coeffY); // Pin to mMinY <= mFinalY <= mMaxY mFinalY = Math.min(mFinalY, mMaxY); mFinalY = Math.max(mFinalY, mMinY); } + private double getSplineDeceleration(float velocity) { + return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff)); + } + + private int getSplineFlingDuration(float velocity) { + final double l = getSplineDeceleration(velocity); + final double decelMinusOne = DECELERATION_RATE - 1.0; + return (int) (1000.0 * Math.exp(l / decelMinusOne)); + } + + private double getSplineFlingDistance(float velocity) { + final double l = getSplineDeceleration(velocity); + final double decelMinusOne = DECELERATION_RATE - 1.0; + return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l); + } + static float viscousFluid(float x) { x *= sViscousFluidScale; diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 86433d4..cd8638d 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -326,7 +326,6 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { int oldLeft, int oldTop, int oldRight, int oldBottom) { adjustDropDownSizeAndPosition(); } - }); } @@ -1285,15 +1284,22 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { Resources res = getContext().getResources(); int anchorPadding = mSearchPlate.getPaddingLeft(); Rect dropDownPadding = new Rect(); + final boolean isLayoutRtl = isLayoutRtl(); int iconOffset = mIconifiedByDefault ? res.getDimensionPixelSize(R.dimen.dropdownitem_icon_width) + res.getDimensionPixelSize(R.dimen.dropdownitem_text_padding_left) : 0; mQueryTextView.getDropDownBackground().getPadding(dropDownPadding); - mQueryTextView.setDropDownHorizontalOffset(-(dropDownPadding.left + iconOffset) - + anchorPadding); - mQueryTextView.setDropDownWidth(mDropDownAnchor.getWidth() + dropDownPadding.left - + dropDownPadding.right + iconOffset - (anchorPadding)); + int offset; + if (isLayoutRtl) { + offset = - dropDownPadding.left; + } else { + offset = anchorPadding - (dropDownPadding.left + iconOffset); + } + mQueryTextView.setDropDownHorizontalOffset(offset); + final int width = mDropDownAnchor.getWidth() + dropDownPadding.left + + dropDownPadding.right + iconOffset - anchorPadding; + mQueryTextView.setDropDownWidth(width); } } @@ -1347,6 +1353,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } }; + @Override + public void onRtlPropertiesChanged(int layoutDirection) { + mQueryTextView.setLayoutDirection(layoutDirection); + } + /** * Query rewriting. */ diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java index 14edd10..517246b 100644 --- a/core/java/android/widget/SlidingDrawer.java +++ b/core/java/android/widget/SlidingDrawer.java @@ -78,7 +78,12 @@ import android.view.accessibility.AccessibilityNodeInfo; * @attr ref android.R.styleable#SlidingDrawer_orientation * @attr ref android.R.styleable#SlidingDrawer_allowSingleTap * @attr ref android.R.styleable#SlidingDrawer_animateOnClick + * + * @deprecated This class is not supported anymore. It is recommended you + * base your own implementation on the source code for the Android Open + * Source Project if you must use it in your application. */ +@Deprecated public class SlidingDrawer extends ViewGroup { public static final int ORIENTATION_HORIZONTAL = 0; public static final int ORIENTATION_VERTICAL = 1; diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 64834b2..317baf1 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -355,7 +355,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { public void setGravity(int gravity) { if (mGravity != gravity) { if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) { - gravity |= Gravity.LEFT; + gravity |= Gravity.START; } mGravity = gravity; requestLayout(); @@ -460,7 +460,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { /** * Creates and positions all views for this Spinner. * - * @param delta Change in the selected position. +1 moves selection is moving to the right, + * @param delta Change in the selected position. +1 means selection is moving to the right, * so views are scrolling to the left. -1 means selection is moving to the left. */ @Override @@ -492,7 +492,9 @@ public class Spinner extends AbsSpinner implements OnClickListener { View sel = makeAndAddView(mSelectedPosition); int width = sel.getMeasuredWidth(); int selectedOffset = childrenLeft; - switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + final int layoutDirection = getLayoutDirection(); + final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2); break; @@ -939,19 +941,18 @@ public class Spinner extends AbsSpinner implements OnClickListener { @Override public void show() { final Drawable background = getBackground(); - int bgOffset = 0; + int hOffset = 0; if (background != null) { background.getPadding(mTempRect); - bgOffset = -mTempRect.left; + hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left; } else { mTempRect.left = mTempRect.right = 0; } final int spinnerPaddingLeft = Spinner.this.getPaddingLeft(); + final int spinnerPaddingRight = Spinner.this.getPaddingRight(); + final int spinnerWidth = Spinner.this.getWidth(); if (mDropDownWidth == WRAP_CONTENT) { - final int spinnerWidth = Spinner.this.getWidth(); - final int spinnerPaddingRight = Spinner.this.getPaddingRight(); - int contentWidth = measureContentWidth( (SpinnerAdapter) mAdapter, getBackground()); final int contentWidthLimit = mContext.getResources() @@ -959,17 +960,20 @@ public class Spinner extends AbsSpinner implements OnClickListener { if (contentWidth > contentWidthLimit) { contentWidth = contentWidthLimit; } - setContentWidth(Math.max( contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight)); } else if (mDropDownWidth == MATCH_PARENT) { - final int spinnerWidth = Spinner.this.getWidth(); - final int spinnerPaddingRight = Spinner.this.getPaddingRight(); setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight); } else { setContentWidth(mDropDownWidth); } - setHorizontalOffset(bgOffset + spinnerPaddingLeft); + + if (isLayoutRtl()) { + hOffset += spinnerWidth - spinnerPaddingRight - getWidth(); + } else { + hOffset += spinnerPaddingLeft; + } + setHorizontalOffset(hOffset); setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); super.show(); getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index 293eda1..6853660 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -1412,8 +1412,8 @@ public class StackView extends AdapterViewAnimator { return null; } - Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), - Bitmap.Config.ARGB_8888); + Bitmap bitmap = Bitmap.createBitmap(v.getResources().getDisplayMetrics(), + v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888); mCanvas.setBitmap(bitmap); float rotationX = v.getRotationX(); diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index cea613f..e754c17 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -480,12 +480,6 @@ public class Switch extends CompoundButton { @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - if (mOnLayout == null) { mOnLayout = makeLayout(mTextOn); } @@ -501,34 +495,6 @@ public class Switch extends CompoundButton { mThumbWidth = maxTextWidth + mThumbTextPadding * 2; - switch (widthMode) { - case MeasureSpec.AT_MOST: - widthSize = Math.min(widthSize, switchWidth); - break; - - case MeasureSpec.UNSPECIFIED: - widthSize = switchWidth; - break; - - case MeasureSpec.EXACTLY: - // Just use what we were given - break; - } - - switch (heightMode) { - case MeasureSpec.AT_MOST: - heightSize = Math.min(heightSize, switchHeight); - break; - - case MeasureSpec.UNSPECIFIED: - heightSize = switchHeight; - break; - - case MeasureSpec.EXACTLY: - // Just use what we were given - break; - } - mSwitchWidth = switchWidth; mSwitchHeight = switchHeight; @@ -542,9 +508,9 @@ public class Switch extends CompoundButton { @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(event); - CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText(); - if (!TextUtils.isEmpty(text)) { - event.getText().add(text); + Layout layout = isChecked() ? mOnLayout : mOffLayout; + if (layout != null && !TextUtils.isEmpty(layout.getText())) { + event.getText().add(layout.getText()); } } @@ -662,7 +628,7 @@ public class Switch extends CompoundButton { mVelocityTracker.computeCurrentVelocity(1000); float xvel = mVelocityTracker.getXVelocity(); if (Math.abs(xvel) > mMinFlingVelocity) { - newState = xvel > 0; + newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0); } else { newState = getTargetCheckedState(); } @@ -680,13 +646,25 @@ public class Switch extends CompoundButton { } private boolean getTargetCheckedState() { - return mThumbPosition >= getThumbScrollRange() / 2; + if (isLayoutRtl()) { + return mThumbPosition <= getThumbScrollRange() / 2; + } else { + return mThumbPosition >= getThumbScrollRange() / 2; + } + } + + private void setThumbPosition(boolean checked) { + if (isLayoutRtl()) { + mThumbPosition = checked ? 0 : getThumbScrollRange(); + } else { + mThumbPosition = checked ? getThumbScrollRange() : 0; + } } @Override public void setChecked(boolean checked) { super.setChecked(checked); - mThumbPosition = isChecked() ? getThumbScrollRange() : 0; + setThumbPosition(isChecked()); invalidate(); } @@ -694,10 +672,19 @@ public class Switch extends CompoundButton { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - mThumbPosition = isChecked() ? getThumbScrollRange() : 0; + setThumbPosition(isChecked()); + + int switchRight; + int switchLeft; + + if (isLayoutRtl()) { + switchLeft = getPaddingLeft(); + switchRight = switchLeft + mSwitchWidth; + } else { + switchRight = getWidth() - getPaddingRight(); + switchLeft = switchRight - mSwitchWidth; + } - int switchRight = getWidth() - getPaddingRight(); - int switchLeft = switchRight - mSwitchWidth; int switchTop = 0; int switchBottom = 0; switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) { @@ -763,16 +750,32 @@ public class Switch extends CompoundButton { mTextPaint.drawableState = getDrawableState(); Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout; - - canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2, - (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2); - switchText.draw(canvas); + if (switchText != null) { + canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2, + (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2); + switchText.draw(canvas); + } canvas.restore(); } @Override + public int getCompoundPaddingLeft() { + if (!isLayoutRtl()) { + return super.getCompoundPaddingLeft(); + } + int padding = super.getCompoundPaddingLeft() + mSwitchWidth; + if (!TextUtils.isEmpty(getText())) { + padding += mSwitchPadding; + } + return padding; + } + + @Override public int getCompoundPaddingRight() { + if (isLayoutRtl()) { + return super.getCompoundPaddingRight(); + } int padding = super.getCompoundPaddingRight() + mSwitchWidth; if (!TextUtils.isEmpty(getText())) { padding += mSwitchPadding; diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java index 78e9453..113299a 100644 --- a/core/java/android/widget/TableLayout.java +++ b/core/java/android/widget/TableLayout.java @@ -184,6 +184,10 @@ public class TableLayout extends LinearLayout { mShrinkableColumns = new SparseBooleanArray(); } + // TableLayouts are always in vertical orientation; keep this tracked + // for shared LinearLayout code. + setOrientation(VERTICAL); + mPassThroughListener = new PassThroughHierarchyChangeListener(); // make sure to call the parent class method to avoid potential // infinite loops @@ -739,11 +743,7 @@ public class TableLayout extends LinearLayout { @Override protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { this.width = MATCH_PARENT; - if (a.hasValue(heightAttr)) { - this.height = a.getLayoutDimension(heightAttr, "layout_height"); - } else { - this.height = WRAP_CONTENT; - } + this.height = a.getLayoutDimension(heightAttr, WRAP_CONTENT); } } diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index 01c4c2c..db3853f 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -226,7 +226,7 @@ public class TableRow extends LinearLayout { final int childWidth = child.getMeasuredWidth(); lp.mOffset[LayoutParams.LOCATION_NEXT] = columnWidth - childWidth; - final int layoutDirection = getResolvedLayoutDirection(); + final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: @@ -502,19 +502,8 @@ public class TableRow extends LinearLayout { @Override protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { - // We don't want to force users to specify a layout_width - if (a.hasValue(widthAttr)) { - width = a.getLayoutDimension(widthAttr, "layout_width"); - } else { - width = MATCH_PARENT; - } - - // We don't want to force users to specify a layout_height - if (a.hasValue(heightAttr)) { - height = a.getLayoutDimension(heightAttr, "layout_height"); - } else { - height = WRAP_CONTENT; - } + width = a.getLayoutDimension(widthAttr, MATCH_PARENT); + height = a.getLayoutDimension(heightAttr, WRAP_CONTENT); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 01617da..410a0ca 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -302,6 +302,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // The alignment to pass to Layout, or null if not resolved. private Layout.Alignment mLayoutAlignment; + private int mResolvedTextAlignment; private boolean mResolvedDrawables; @@ -1542,11 +1543,10 @@ 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(); - switch(getResolvedLayoutDirection()) { + switch(getLayoutDirection()) { default: case LAYOUT_DIRECTION_LTR: return getCompoundPaddingLeft(); @@ -1558,11 +1558,10 @@ 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(); - switch(getResolvedLayoutDirection()) { + switch(getLayoutDirection()) { default: case LAYOUT_DIRECTION_LTR: return getCompoundPaddingRight(); @@ -1656,7 +1655,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(); @@ -1665,7 +1663,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(); @@ -1868,7 +1865,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) { @@ -1990,7 +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 */ @android.view.RemotableViewMethod public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, @@ -2014,7 +2009,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) { @@ -2061,7 +2055,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 Drawable[] getCompoundDrawablesRelative() { final Drawables dr = mDrawables; @@ -2211,6 +2204,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Get the default {@link Locale} of the text in this TextView. + * @return the default {@link Locale} of the text in this TextView. + */ + public Locale getTextLocale() { + return mTextPaint.getTextLocale(); + } + + /** + * Set the default {@link Locale} of the text in this TextView to the given value. This value + * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK + * locales to disambiguate Hanzi/Kanji/Hanja characters. + * + * @param locale the {@link Locale} for drawing text, must not be null. + * + * @see Paint#setTextLocale + */ + public void setTextLocale(Locale locale) { + mTextPaint.setTextLocale(locale); + } + + /** * @return the size (in pixels) of the default text size in this TextView. */ @ViewDebug.ExportedProperty(category = "text") @@ -4449,9 +4463,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTemporaryDetach = false; - // Resolve drawables as the layout direction has been resolved - resolveDrawables(); - if (mEditor != null) mEditor.onAttachedToWindow(); } @@ -4586,23 +4597,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - /** - * @hide - */ - @Override - public int getResolvedLayoutDirection(Drawable who) { - if (who == null) return View.LAYOUT_DIRECTION_LTR; - if (mDrawables != null) { - final Drawables drawables = mDrawables; - if (who == drawables.mDrawableLeft || who == drawables.mDrawableRight || - who == drawables.mDrawableTop || who == drawables.mDrawableBottom || - who == drawables.mDrawableStart || who == drawables.mDrawableEnd) { - return getResolvedLayoutDirection(); - } - } - return super.getResolvedLayoutDirection(who); - } - @Override public boolean hasOverlappingRendering() { return (getBackground() != null || mText instanceof Spannable || hasSelection()); @@ -4862,18 +4856,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); - final int layoutDirection = getResolvedLayoutDirection(); + final boolean isLayoutRtl = isLayoutRtl(); + + final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); if (mEllipsize == TextUtils.TruncateAt.MARQUEE && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { if (!mSingleLine && getLineCount() == 1 && canMarquee() && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { - canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft - - getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f); + final int width = mRight - mLeft; + final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); + final float dx = mLayout.getLineRight(0) - (width - padding); + canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f); } if (mMarquee != null && mMarquee.isRunning()) { - canvas.translate(-mMarquee.mScroll, 0.0f); + final float dx = -mMarquee.getScroll(); + canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f); } } @@ -4887,7 +4886,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (mMarquee != null && mMarquee.shouldDrawGhost()) { - canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); + final int dx = (int) mMarquee.getGhostOffset(); + canvas.translate(isLayoutRtl ? -dx : dx, 0.0f); layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } @@ -5634,13 +5634,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener physicalWidth, false); } - /** @hide */ @Override - public void onResolvedLayoutDirectionReset() { + public void onRtlPropertiesChanged(int layoutDirection) { if (mLayoutAlignment != null) { - int resolvedTextAlignment = getResolvedTextAlignment(); - if (resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START || - resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) { + if (mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START || + mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) { mLayoutAlignment = null; } } @@ -5648,8 +5646,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Layout.Alignment getLayoutAlignment() { if (mLayoutAlignment == null) { - int textAlign = getResolvedTextAlignment(); - switch (textAlign) { + mResolvedTextAlignment = getTextAlignment(); + switch (mResolvedTextAlignment) { case TEXT_ALIGNMENT_GRAVITY: switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { case Gravity.START: @@ -5682,11 +5680,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; break; case TEXT_ALIGNMENT_VIEW_START: - mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; break; case TEXT_ALIGNMENT_VIEW_END: - mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; break; case TEXT_ALIGNMENT_INHERIT: @@ -5735,7 +5733,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (mTextDir == null) { - resolveTextDirection(); + mTextDir = getTextDirectionHeuristic(); } mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, @@ -5997,7 +5995,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener BoringLayout.Metrics hintBoring = UNKNOWN_BORING; if (mTextDir == null) { - resolveTextDirection(); + getTextDirectionHeuristic(); } int des = -1; @@ -7481,12 +7479,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mMarquee != null && !mMarquee.isStopped()) { final Marquee marquee = mMarquee; if (marquee.shouldDrawLeftFade()) { - return marquee.mScroll / getHorizontalFadingEdgeLength(); + final float scroll = marquee.getScroll(); + return scroll / getHorizontalFadingEdgeLength(); } else { return 0.0f; } } else if (getLineCount() == 1) { - final int layoutDirection = getResolvedLayoutDirection(); + final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: @@ -7509,9 +7508,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { if (mMarquee != null && !mMarquee.isStopped()) { final Marquee marquee = mMarquee; - return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength(); + final float maxFadeScroll = marquee.getMaxFadeScroll(); + final float scroll = marquee.getScroll(); + return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength(); } else if (getLineCount() == 1) { - final int layoutDirection = getResolvedLayoutDirection(); + final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: @@ -8180,49 +8181,38 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mEditor.mInBatchEditControllers; } - /** @hide */ - @Override - public void onResolvedTextDirectionChanged() { + TextDirectionHeuristic getTextDirectionHeuristic() { if (hasPasswordTransformationMethod()) { // TODO: take care of the content direction to show the password text and dots justified // to the left or to the right - mTextDir = TextDirectionHeuristics.LOCALE; - return; + return TextDirectionHeuristics.LOCALE; } // Always need to resolve layout direction first - final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL); + final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); // Now, we can select the heuristic - int textDir = getResolvedTextDirection(); - switch (textDir) { + switch (getTextDirection()) { default: case TEXT_DIRECTION_FIRST_STRONG: - mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : + return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : TextDirectionHeuristics.FIRSTSTRONG_LTR); - break; case TEXT_DIRECTION_ANY_RTL: - mTextDir = TextDirectionHeuristics.ANYRTL_LTR; - break; + return TextDirectionHeuristics.ANYRTL_LTR; case TEXT_DIRECTION_LTR: - mTextDir = TextDirectionHeuristics.LTR; - break; + return TextDirectionHeuristics.LTR; case TEXT_DIRECTION_RTL: - mTextDir = TextDirectionHeuristics.RTL; - break; + return TextDirectionHeuristics.RTL; case TEXT_DIRECTION_LOCALE: - mTextDir = TextDirectionHeuristics.LOCALE; - break; + return TextDirectionHeuristics.LOCALE; } } /** - * Subclasses will need to override this method to implement their own way of resolving - * drawables depending on the layout direction. - * - * A call to the super method will be required from the subclasses implementation. + * @hide */ - protected void resolveDrawables() { + @Override + public void onResolveDrawables(int layoutDirection) { // No need to resolve twice if (mResolvedDrawables) { return; @@ -8238,7 +8228,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } Drawables dr = mDrawables; - switch(getResolvedLayoutDirection()) { + switch(layoutDirection) { case LAYOUT_DIRECTION_RTL: if (dr.mDrawableStart != null) { dr.mDrawableRight = dr.mDrawableStart; @@ -8270,9 +8260,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } break; } + updateDrawablesLayoutDirection(dr, layoutDirection); mResolvedDrawables = true; } + private void updateDrawablesLayoutDirection(Drawables dr, int layoutDirection) { + if (dr.mDrawableLeft != null) { + dr.mDrawableLeft.setLayoutDirection(layoutDirection); + } + if (dr.mDrawableRight != null) { + dr.mDrawableRight.setLayoutDirection(layoutDirection); + } + if (dr.mDrawableTop != null) { + dr.mDrawableTop.setLayoutDirection(layoutDirection); + } + if (dr.mDrawableBottom != null) { + dr.mDrawableBottom.setLayoutDirection(layoutDirection); + } + } + protected void resetResolvedDrawables() { mResolvedDrawables = false; } @@ -8341,7 +8347,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override public CharSequence getIterableTextForAccessibility() { - if (getContentDescription() == null) { + if (!TextUtils.isEmpty(mText)) { if (!(mText instanceof Spannable)) { setText(mText, BufferType.SPANNABLE); } @@ -8593,13 +8599,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private byte mStatus = MARQUEE_STOPPED; private final float mScrollUnit; private float mMaxScroll; - float mMaxFadeScroll; + private float mMaxFadeScroll; private float mGhostStart; private float mGhostOffset; private float mFadeStop; private int mRepeatLimit; - float mScroll; + private float mScroll; Marquee(TextView v) { final float density = v.getContext().getResources().getDisplayMetrics().density; @@ -8691,6 +8697,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mGhostOffset; } + float getScroll() { + return mScroll; + } + + float getMaxFadeScroll() { + return mMaxFadeScroll; + } + boolean shouldDrawLeftFade() { return mScroll <= mFadeStop; } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index cb9ed61..e6796cb 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -172,7 +172,7 @@ public class TimePicker extends FrameLayout { mMinuteSpinner.setMinValue(0); mMinuteSpinner.setMaxValue(59); mMinuteSpinner.setOnLongPressUpdateInterval(100); - mMinuteSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); + mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { updateInputState(); @@ -500,7 +500,7 @@ public class TimePicker extends FrameLayout { if (is24HourView()) { mHourSpinner.setMinValue(0); mHourSpinner.setMaxValue(23); - mHourSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); + mHourSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); } else { mHourSpinner.setMinValue(1); mHourSpinner.setMaxValue(12); diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index fafc113..485bd37 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -19,6 +19,7 @@ package android.widget; import android.app.INotificationManager; import android.app.ITransientNotification; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.Handler; @@ -29,7 +30,6 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; -import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -305,12 +305,14 @@ public class Toast { private static class TN extends ITransientNotification.Stub { final Runnable mShow = new Runnable() { + @Override public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { + @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() @@ -329,8 +331,8 @@ public class Toast { View mView; View mNextView; - - WindowManagerImpl mWM; + + WindowManager mWM; TN() { // XXX This should be changed to use a Dialog, with a Theme.Toast @@ -350,6 +352,7 @@ public class Toast { /** * schedule handleShow into the right thread */ + @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); @@ -358,6 +361,7 @@ public class Toast { /** * schedule handleHide into the right thread */ + @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); @@ -370,8 +374,12 @@ public class Toast { // remove the old view if necessary handleHide(); mView = mNextView; - mWM = WindowManagerImpl.getDefault(); - final int gravity = mGravity; + mWM = (WindowManager)mView.getContext().getApplicationContext() + .getSystemService(Context.WINDOW_SERVICE); + // We can resolve the Gravity here by using the Locale for getting + // the layout direction + final Configuration config = mView.getContext().getResources().getConfiguration(); + final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); mParams.gravity = gravity; if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java index e707ea3..f7e5266 100644 --- a/core/java/android/widget/TwoLineListItem.java +++ b/core/java/android/widget/TwoLineListItem.java @@ -37,7 +37,11 @@ import android.widget.RelativeLayout; * layout for this object. * * @attr ref android.R.styleable#TwoLineListItem_mode + * + * @deprecated This class can be implemented easily by apps using a {@link RelativeLayout} + * or a {@link LinearLayout}. */ +@Deprecated @Widget public class TwoLineListItem extends RelativeLayout { diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index 0fba498..7c8196d 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -26,6 +26,7 @@ import android.media.MediaPlayer; import android.media.Metadata; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnInfoListener; import android.net.Uri; import android.util.AttributeSet; import android.util.Log; @@ -84,6 +85,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { private MediaPlayer.OnPreparedListener mOnPreparedListener; private int mCurrentBufferPercentage; private OnErrorListener mOnErrorListener; + private OnInfoListener mOnInfoListener; private int mSeekWhenPrepared; // recording the seek position while preparing private boolean mCanPause; private boolean mCanSeekBack; @@ -230,6 +232,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { mDuration = -1; mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); + mMediaPlayer.setOnInfoListener(mOnInfoListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mCurrentBufferPercentage = 0; mMediaPlayer.setDataSource(mContext, mUri, mHeaders); @@ -281,6 +284,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { mVideoHeight = mp.getVideoHeight(); if (mVideoWidth != 0 && mVideoHeight != 0) { getHolder().setFixedSize(mVideoWidth, mVideoHeight); + requestLayout(); } } }; @@ -455,6 +459,16 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { mOnErrorListener = l; } + /** + * Register a callback to be invoked when an informational event + * occurs during playback or setup. + * + * @param l The callback that will be run + */ + public void setOnInfoListener(OnInfoListener l) { + mOnInfoListener = l; + } + SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { public void surfaceChanged(SurfaceHolder holder, int format, diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java index 6a68240..eee914e 100644 --- a/core/java/android/widget/ViewAnimator.java +++ b/core/java/android/widget/ViewAnimator.java @@ -329,8 +329,21 @@ public class ViewAnimator extends FrameLayout { } /** + * Returns whether the current View should be animated the first time the ViewAnimator + * is displayed. + * + * @return true if the current View will be animated the first time it is displayed, + * false otherwise. + * + * @see #setAnimateFirstView(boolean) + */ + public boolean getAnimateFirstView() { + return mAnimateFirstTime; + } + + /** * Indicates whether the current View should be animated the first time - * the ViewAnimation is displayed. + * the ViewAnimator is displayed. * * @param animate True to animate the current View the first time it is displayed, * false otherwise. diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index 02dc27b..a89c9c1 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -242,7 +242,7 @@ public class ZoomButtonsController implements View.OnTouchListener { private FrameLayout createContainer() { LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); // Controls are positioned BOTTOM | CENTER with respect to the owner view. - lp.gravity = Gravity.TOP | Gravity.LEFT; + lp.gravity = Gravity.TOP | Gravity.START; lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_NO_LIMITS | diff --git a/core/java/android/widget/package.html b/core/java/android/widget/package.html index 7d94a4b..91d327c 100644 --- a/core/java/android/widget/package.html +++ b/core/java/android/widget/package.html @@ -1,11 +1,16 @@ <HTML> <BODY> +<p> The widget package contains (mostly visual) UI elements to use -on your Application screen. You can design your own <p> +on your Application screen. You can also design your own. +</p> + +<p> To create your own widget, extend {@link android.view.View} or a subclass. To use your widget in layout XML, there are two additional files for you to create. Here is a list of files you'll need to create to implement a custom widget: +</p> <ul> <li><b>Java implementation file</b> - This is the file that implements the behavior of the widget. If you can instantiate the object from layout XML, @@ -19,14 +24,16 @@ another in their layout XML.</li> res/layout/ that describes the layout of your widget. You could also do this in code in your Java file.</li> </ul> + +<p> ApiDemos sample application has an example of creating a custom layout XML tag, LabelView. See the following files that demonstrate implementing and using -a custom widget:</p> +a custom widget: +</p> <ul> - <li><strong>LabelView.java</strong> - The implentation file</li> + <li><strong>LabelView.java</strong> - The implementation file</li> <li><strong>res/values/attrs.xml</strong> - Definition file</li> - <li><strong>res/layout/custom_view_1.xml</strong> - Layout -file</li> + <li><strong>res/layout/custom_view_1.xml</strong> - Layout file</li> </ul> </BODY> </HTML> |