diff options
Diffstat (limited to 'core/java')
145 files changed, 4899 insertions, 2663 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 91def67..1ba8eee 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -1033,8 +1033,12 @@ public class AccountManagerService mContext.getString(R.string.permission_request_notification_with_subtitle, account.name); final int index = titleAndSubtitle.indexOf('\n'); - final String title = titleAndSubtitle.substring(0, index); - final String subtitle = titleAndSubtitle.substring(index + 1); + String title = titleAndSubtitle; + String subtitle = ""; + if (index > 0) { + title = titleAndSubtitle.substring(0, index); + subtitle = titleAndSubtitle.substring(index + 1); + } n.setLatestEventInfo(mContext, title, subtitle, PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); diff --git a/core/java/android/accounts/ChooseAccountTypeActivity.java b/core/java/android/accounts/ChooseAccountTypeActivity.java index 448b2c0..acc8549 100644 --- a/core/java/android/accounts/ChooseAccountTypeActivity.java +++ b/core/java/android/accounts/ChooseAccountTypeActivity.java @@ -43,7 +43,7 @@ import java.util.Set; * @hide */ public class ChooseAccountTypeActivity extends Activity { - private static final String TAG = "AccountManager"; + private static final String TAG = "AccountChooser"; private HashMap<String, AuthInfo> mTypeToAuthenticatorInfo = new HashMap<String, AuthInfo>(); private ArrayList<AuthInfo> mAuthenticatorInfosToDisplay; @@ -52,6 +52,11 @@ public class ChooseAccountTypeActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseAccountTypeActivity.onCreate(savedInstanceState=" + + savedInstanceState + ")"); + } + // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes Set<String> setOfAllowableAccountTypes = null; String[] validAccountTypes = getIntent().getStringArrayExtra( @@ -111,8 +116,10 @@ public class ChooseAccountTypeActivity extends Activity { Bundle bundle = new Bundle(); bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, type); setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); - Log.d(TAG, "ChooseAccountTypeActivity.setResultAndFinish: " - + "selected account type " + type); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseAccountTypeActivity.setResultAndFinish: " + + "selected account type " + type); + } finish(); } diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 8cc2002..5f38eb4 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -47,7 +47,7 @@ import java.util.Set; */ public class ChooseTypeAndAccountActivity extends Activity implements AccountManagerCallback<Bundle> { - private static final String TAG = "AccountManager"; + private static final String TAG = "AccountChooser"; /** * A Parcelable ArrayList of Account objects that limits the choosable accounts to those @@ -100,13 +100,39 @@ public class ChooseTypeAndAccountActivity extends Activity public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = "descriptionTextOverride"; + public static final int REQUEST_NULL = 0; + public static final int REQUEST_CHOOSE_TYPE = 1; + public static final int REQUEST_ADD_ACCOUNT = 2; + + private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest"; + private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts"; + private ArrayList<AccountInfo> mAccountInfos; + private int mPendingRequest = REQUEST_NULL; + private Parcelable[] mExistingAccounts = null; + private Parcelable[] mSavedAccounts = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.onCreate(savedInstanceState=" + + savedInstanceState + ")"); + } + setContentView(R.layout.choose_type_and_account); + if (savedInstanceState != null) { + mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST); + mSavedAccounts = + savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS); + mExistingAccounts = null; + } else { + mPendingRequest = REQUEST_NULL; + mSavedAccounts = null; + mExistingAccounts = null; + } + // save some items we use frequently final AccountManager accountManager = AccountManager.get(this); final Intent intent = getIntent(); @@ -171,20 +197,6 @@ public class ChooseTypeAndAccountActivity extends Activity account.equals(selectedAccount))); } - // If there are no allowable accounts go directly to add account - if (mAccountInfos.isEmpty()) { - startChooseAccountTypeActivity(); - return; - } - - // if there is only one allowable account return it - if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false) - && mAccountInfos.size() == 1) { - Account account = mAccountInfos.get(0).account; - setResultAndFinish(account.name, account.type); - return; - } - // there is more than one allowable account. initialize the list adapter to allow // the user to select an account. ListView list = (ListView) findViewById(android.R.id.list); @@ -204,6 +216,37 @@ public class ChooseTypeAndAccountActivity extends Activity startChooseAccountTypeActivity(); } }); + + if (mPendingRequest == REQUEST_NULL) { + // If there are no allowable accounts go directly to add account + if (mAccountInfos.isEmpty()) { + startChooseAccountTypeActivity(); + return; + } + + // if there is only one allowable account return it + if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false) + && mAccountInfos.size() == 1) { + Account account = mAccountInfos.get(0).account; + setResultAndFinish(account.name, account.type); + return; + } + } + } + + @Override + protected void onDestroy() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()"); + } + super.onDestroy(); + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest); + outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts); } // Called when the choose account type activity (for adding an account) returns. @@ -212,20 +255,75 @@ public class ChooseTypeAndAccountActivity extends Activity @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - if (resultCode == RESULT_OK && data != null) { - String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); - if (accountType != null) { - runAddAccountForAuthenticator(accountType); - return; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + if (data != null && data.getExtras() != null) data.getExtras().keySet(); + Bundle extras = data != null ? data.getExtras() : null; + Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode + + ", resCode=" + resultCode + ", extras=" + extras + ")"); + } + + // we got our result, so clear the fact that we had a pending request + mPendingRequest = REQUEST_NULL; + mExistingAccounts = null; + + if (resultCode == RESULT_CANCELED) { + return; + } + + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_CHOOSE_TYPE) { + if (data != null) { + String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); + if (accountType != null) { + runAddAccountForAuthenticator(accountType); + return; + } + } + Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account " + + "type, pretending the request was canceled"); + } else if (requestCode == REQUEST_ADD_ACCOUNT) { + String accountName = null; + String accountType = null; + + if (data != null) { + accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); + accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); + } + + if (accountName == null || accountType == null) { + Account[] currentAccounts = AccountManager.get(this).getAccounts(); + Set<Account> preExistingAccounts = new HashSet<Account>(); + for (Parcelable accountParcel : mSavedAccounts) { + preExistingAccounts.add((Account) accountParcel); + } + for (Account account : currentAccounts) { + if (!preExistingAccounts.contains(account)) { + accountName = account.name; + accountType = account.type; + break; + } + } + } + + if (accountName != null || accountType != null) { + setResultAndFinish(accountName, accountType); + return; + } } + Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added " + + "account, pretending the request was canceled"); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled"); } - Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled"); setResult(Activity.RESULT_CANCELED); finish(); } protected void runAddAccountForAuthenticator(String type) { - Log.d(TAG, "selected account type " + type); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "runAddAccountForAuthenticator: " + type); + } final Bundle options = getIntent().getBundleExtra( ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE); final String[] requiredFeatures = getIntent().getStringArrayExtra( @@ -233,20 +331,19 @@ public class ChooseTypeAndAccountActivity extends Activity final String authTokenType = getIntent().getStringExtra( ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING); AccountManager.get(this).addAccount(type, authTokenType, requiredFeatures, - options, this, this, null /* Handler */); + options, null /* activity */, this /* callback */, null /* Handler */); } public void run(final AccountManagerFuture<Bundle> accountManagerFuture) { try { final Bundle accountManagerResult = accountManagerFuture.getResult(); - final String name = accountManagerResult.getString(AccountManager.KEY_ACCOUNT_NAME); - final String type = accountManagerResult.getString(AccountManager.KEY_ACCOUNT_TYPE); - if (name != null && type != null) { - final Bundle bundle = new Bundle(); - bundle.putString(AccountManager.KEY_ACCOUNT_NAME, name); - bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, type); - setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); - finish(); + final Intent intent = (Intent)accountManagerResult.getParcelable( + AccountManager.KEY_INTENT); + if (intent != null) { + mPendingRequest = REQUEST_ADD_ACCOUNT; + mExistingAccounts = AccountManager.get(this).getAccounts(); + intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityForResult(intent, REQUEST_ADD_ACCOUNT); return; } } catch (OperationCanceledException e) { @@ -297,12 +394,17 @@ public class ChooseTypeAndAccountActivity extends Activity bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName); bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType); setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); - Log.d(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: " - + "selected account " + accountName + ", " + accountType); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: " + + "selected account " + accountName + ", " + accountType); + } finish(); } private void startChooseAccountTypeActivity() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()"); + } final Intent intent = new Intent(this, ChooseAccountTypeActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, @@ -313,7 +415,8 @@ public class ChooseTypeAndAccountActivity extends Activity getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY)); intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING, getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING)); - startActivityForResult(intent, 0); + startActivityForResult(intent, REQUEST_CHOOSE_TYPE); + mPendingRequest = REQUEST_CHOOSE_TYPE; } private static class AccountInfo { diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index 355b1fc..f383af9 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -24,6 +24,7 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.DecelerateInterpolator; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -615,10 +616,13 @@ public class LayoutTransition { observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { public boolean onPreDraw() { parent.getViewTreeObserver().removeOnPreDrawListener(this); - int numChildren = parent.getChildCount(); - for (int i = 0; i < numChildren; ++i) { - final View child = parent.getChildAt(i); - child.removeOnLayoutChangeListener(layoutChangeListenerMap.get(child)); + int count = layoutChangeListenerMap.size(); + if (count > 0) { + Collection<View> views = layoutChangeListenerMap.keySet(); + for (View view : views) { + View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); + view.removeOnLayoutChangeListener(listener); + } } layoutChangeListenerMap.clear(); return true; diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 51c6f3a..24d3a6b 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -43,15 +43,18 @@ import android.widget.SpinnerAdapter; * modify various characteristics of the action bar or remove it completely.</p> * <p>From your activity, you can retrieve an instance of {@link ActionBar} by calling {@link * android.app.Activity#getActionBar getActionBar()}.</p> - * <p>For information about how to use the action bar, including how to add action items, navigation - * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action - * Bar</a> developer guide.</p> * <p>In some cases, the action bar may be overlayed by another bar that enables contextual actions, * using an {@link android.view.ActionMode}. For example, when the user selects one or more items in * your activity, you can enable an action mode that offers actions specific to the selected * items, with a UI that temporarily replaces the action bar. Although the UI may occupy the * same space, the {@link android.view.ActionMode} APIs are distinct and independent from those for * {@link ActionBar}. + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For information about how to use the action bar, including how to add action items, navigation + * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action + * Bar</a> developer guide.</p> + * </div> */ public abstract class ActionBar { /** diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 034e3c7..8e8d37d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -111,18 +111,6 @@ import java.util.HashMap; * {@link android.R.styleable#AndroidManifestActivity <activity>} * declaration in their package's <code>AndroidManifest.xml</code>.</p> * - * <p>The Activity class is an important part of an application's overall lifecycle, - * and the way activities are launched and put together is a fundamental - * part of the platform's application model. For a detailed perspective on the structure of an - * Android application and how activities behave, please read the - * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a> and - * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> - * documents.</p> - * - * <p>You can also find a detailed discussion about how to create activities in the - * <a href="{@docRoot}guide/topics/fundamentals/activities.html">Activities</a> - * document.</p> - * * <p>Topics covered here: * <ol> * <li><a href="#Fragments">Fragments</a> @@ -133,7 +121,22 @@ import java.util.HashMap; * <li><a href="#Permissions">Permissions</a> * <li><a href="#ProcessLifecycle">Process Lifecycle</a> * </ol> - * + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>The Activity class is an important part of an application's overall lifecycle, + * and the way activities are launched and put together is a fundamental + * part of the platform's application model. For a detailed perspective on the structure of an + * Android application and how activities behave, please read the + * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a> and + * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> + * developer guides.</p> + * + * <p>You can also find a detailed discussion about how to create activities in the + * <a href="{@docRoot}guide/topics/fundamentals/activities.html">Activities</a> + * developer guide.</p> + * </div> + * * <a name="Fragments"></a> * <h3>Fragments</h3> * @@ -3246,6 +3249,7 @@ public class Activity extends ContextThemeWrapper try { String resolvedType = null; if (fillInIntent != null) { + fillInIntent.setAllowFds(false); resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); } int result = ActivityManagerNative.getDefault() @@ -3370,6 +3374,7 @@ public class Activity extends ContextThemeWrapper if (mParent == null) { int result = IActivityManager.START_RETURN_INTENT_TO_CALLER; try { + intent.setAllowFds(false); result = ActivityManagerNative.getDefault() .startActivity(mMainThread.getApplicationThread(), intent, intent.resolveTypeIfNeeded( @@ -3419,6 +3424,7 @@ public class Activity extends ContextThemeWrapper public boolean startNextMatchingActivity(Intent intent) { if (mParent == null) { try { + intent.setAllowFds(false); return ActivityManagerNative.getDefault() .startNextMatchingActivity(mToken, intent); } catch (RemoteException e) { @@ -3692,6 +3698,9 @@ public class Activity extends ContextThemeWrapper } if (false) Log.v(TAG, "Finishing self: token=" + mToken); try { + if (resultData != null) { + resultData.setAllowFds(false); + } if (ActivityManagerNative.getDefault() .finishActivity(mToken, resultCode, resultData)) { mFinished = true; @@ -3812,6 +3821,7 @@ public class Activity extends ContextThemeWrapper int flags) { String packageName = getPackageName(); try { + data.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 0776e10..8afe9bf 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -490,6 +490,15 @@ public final class ActivityThread { // Formatting for checkin service - update version if row format changes private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1; + private void updatePendingConfiguration(Configuration config) { + synchronized (mPackages) { + if (mPendingConfiguration == null || + mPendingConfiguration.isOtherSeqNewer(config)) { + mPendingConfiguration = config; + } + } + } + public final void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges) { queueOrSendMessage( @@ -530,8 +539,8 @@ public final class ActivityThread { // we use token to identify this activity without having to send the // activity itself back to the activity manager. (matters more with ipc) public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, CompatibilityInfo compatInfo, Bundle state, - List<ResultInfo> pendingResults, + ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { ActivityClientRecord r = new ActivityClientRecord(); @@ -553,6 +562,8 @@ public final class ActivityThread { r.profileFd = profileFd; r.autoStopProfiler = autoStopProfiler; + updatePendingConfiguration(curConfig); + queueOrSendMessage(H.LAUNCH_ACTIVITY, r); } @@ -697,12 +708,7 @@ public final class ActivityThread { } public void scheduleConfigurationChanged(Configuration config) { - synchronized (mPackages) { - if (mPendingConfiguration == null || - mPendingConfiguration.isOtherSeqNewer(config)) { - mPendingConfiguration = config; - } - } + updatePendingConfiguration(config); queueOrSendMessage(H.CONFIGURATION_CHANGED, config); } @@ -1478,7 +1484,7 @@ public final class ActivityThread { } //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); - DisplayMetrics metrics = getDisplayMetricsLocked(compInfo, false); + DisplayMetrics metrics = getDisplayMetricsLocked(null, false); r = new Resources(assets, metrics, getConfiguration(), compInfo); if (false) { Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " @@ -1966,6 +1972,9 @@ public final class ActivityThread { mProfiler.autoStopProfiler = r.autoStopProfiler; } + // Make sure we are running with the most recent config. + handleConfigurationChanged(null, null); + if (localLOGV) Slog.v( TAG, "Handling launch of " + r); Activity a = performLaunchActivity(r, customIntent); @@ -2674,6 +2683,7 @@ public final class ActivityThread { // Next have the activity save its current state and managed dialogs... if (!r.activity.mFinished && saveState) { state = new Bundle(); + state.setAllowFds(false); mInstrumentation.callActivityOnSaveInstanceState(r.activity, state); r.state = state; } @@ -2775,6 +2785,7 @@ public final class ActivityThread { if (!r.activity.mFinished && saveState) { if (r.state == null) { state = new Bundle(); + state.setAllowFds(false); mInstrumentation.callActivityOnSaveInstanceState(r.activity, state); r.state = state; } else { @@ -3306,6 +3317,7 @@ public final class ActivityThread { } if (r.state == null && !r.stopped && !r.isPreHoneycomb()) { r.state = new Bundle(); + r.state.setAllowFds(false); mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state); } @@ -3473,7 +3485,7 @@ public final class ActivityThread { return false; } int changes = mResConfiguration.updateFrom(config); - DisplayMetrics dm = getDisplayMetricsLocked(compat, true); + DisplayMetrics dm = getDisplayMetricsLocked(null, true); if (compat != null && (mResCompatibilityInfo == null || !mResCompatibilityInfo.equals(compat))) { @@ -3514,7 +3526,20 @@ public final class ActivityThread { return changes != 0; } - + + final Configuration applyCompatConfiguration() { + Configuration config = mConfiguration; + if (mCompatConfiguration == null) { + mCompatConfiguration = new Configuration(); + } + mCompatConfiguration.setTo(mConfiguration); + if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { + mResCompatibilityInfo.applyToConfiguration(mCompatConfiguration); + config = mCompatConfiguration; + } + return config; + } + final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { ArrayList<ComponentCallbacks2> callbacks = null; @@ -3543,14 +3568,7 @@ public final class ActivityThread { return; } mConfiguration.updateFrom(config); - if (mCompatConfiguration == null) { - mCompatConfiguration = new Configuration(); - } - mCompatConfiguration.setTo(mConfiguration); - if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { - mResCompatibilityInfo.applyToConfiguration(mCompatConfiguration); - config = mCompatConfiguration; - } + config = applyCompatConfiguration(); callbacks = collectComponentCallbacksLocked(false, config); } @@ -3749,6 +3767,7 @@ public final class ActivityThread { * in AppBindData can be safely assumed to be up to date */ applyConfigurationToResourcesLocked(data.config, data.compatInfo); + applyCompatConfiguration(); data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index cde06cd..c4a4fea 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -132,6 +132,7 @@ public abstract class ApplicationThreadNative extends Binder IBinder b = data.readStrongBinder(); int ident = data.readInt(); ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data); + Configuration curConfig = Configuration.CREATOR.createFromParcel(data); CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data); Bundle state = data.readBundle(); List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR); @@ -142,7 +143,7 @@ public abstract class ApplicationThreadNative extends Binder ParcelFileDescriptor profileFd = data.readInt() != 0 ? data.readFileDescriptor() : null; boolean autoStopProfiler = data.readInt() != 0; - scheduleLaunchActivity(intent, b, ident, info, compatInfo, state, ri, pi, + scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, state, ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler); return true; } @@ -630,10 +631,10 @@ class ApplicationThreadProxy implements IApplicationThread { } public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, CompatibilityInfo compatInfo, Bundle state, - List<ResultInfo> pendingResults, - List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, - String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) + ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + Bundle state, List<ResultInfo> pendingResults, + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, + String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); @@ -641,6 +642,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeStrongBinder(token); data.writeInt(ident); info.writeToParcel(data, 0); + curConfig.writeToParcel(data, 0); compatInfo.writeToParcel(data, 0); data.writeBundle(state); data.writeTypedList(pendingResults); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 2139704..2bf1fb7 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -874,6 +874,7 @@ class ContextImpl extends Context { try { String resolvedType = null; if (fillInIntent != null) { + fillInIntent.setAllowFds(false); resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); } int result = ActivityManagerNative.getDefault() @@ -892,6 +893,7 @@ class ContextImpl extends Context { public void sendBroadcast(Intent intent) { 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); @@ -903,6 +905,7 @@ class ContextImpl extends Context { 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, receiverPermission, false, false); @@ -915,6 +918,7 @@ class ContextImpl extends Context { 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); @@ -946,6 +950,7 @@ class ContextImpl extends Context { } String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { + intent.setAllowFds(false); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, receiverPermission, @@ -958,6 +963,7 @@ class ContextImpl extends Context { public void sendStickyBroadcast(Intent intent) { 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); @@ -989,6 +995,7 @@ class ContextImpl extends Context { } String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { + intent.setAllowFds(false); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, rd, initialCode, initialData, initialExtras, null, @@ -1005,6 +1012,7 @@ class ContextImpl extends Context { intent.setDataAndType(intent.getData(), resolvedType); } try { + intent.setAllowFds(false); ActivityManagerNative.getDefault().unbroadcastIntent( mMainThread.getApplicationThread(), intent); } catch (RemoteException e) { @@ -1069,6 +1077,7 @@ class ContextImpl extends Context { @Override public ComponentName startService(Intent service) { try { + service.setAllowFds(false); ComponentName cn = ActivityManagerNative.getDefault().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver())); @@ -1086,6 +1095,7 @@ class ContextImpl extends Context { @Override public boolean stopService(Intent service) { try { + service.setAllowFds(false); int res = ActivityManagerNative.getDefault().stopService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver())); @@ -1116,6 +1126,7 @@ class ContextImpl extends Context { < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } + service.setAllowFds(false); int res = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), @@ -1148,6 +1159,9 @@ class ContextImpl extends Context { public boolean startInstrumentation(ComponentName className, String profileFile, Bundle arguments) { try { + if (arguments != null) { + arguments.setAllowFds(false); + } return ActivityManagerNative.getDefault().startInstrumentation( className, profileFile, 0, arguments, null); } catch (RemoteException e) { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 3a08e6d..d423d98 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -162,9 +162,6 @@ final class FragmentState implements Parcelable { * constructor to instantiate it. If the empty constructor is not available, * a runtime exception will occur in some cases during state restore. * - * <p>For more documentation, also see the <a - * href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p> - * * <p>Topics covered here: * <ol> * <li><a href="#OlderPlatforms">Older Platforms</a> @@ -173,6 +170,12 @@ final class FragmentState implements Parcelable { * <li><a href="#BackStack">Back Stack</a> * </ol> * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about using fragments, read the + * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p> + * </div> + * * <a name="OlderPlatforms"></a> * <h3>Older Platforms</h3> * diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 7a6759f..3da4f29 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -45,6 +45,12 @@ import java.util.Arrays; /** * Interface for interacting with {@link Fragment} objects inside of an * {@link Activity} + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about using fragments, read the + * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p> + * </div> */ public abstract class FragmentManager { /** diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index c1f3cd6..6e99899 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -2,6 +2,12 @@ package android.app; /** * API for performing a set of Fragment operations. + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about using fragments, read the + * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p> + * </div> */ public abstract class FragmentTransaction { /** diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 5d200b4..1253fe7 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -53,10 +53,10 @@ public interface IApplicationThread extends IInterface { void scheduleResumeActivity(IBinder token, boolean isForward) throws RemoteException; void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException; void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, CompatibilityInfo compatInfo, Bundle state, - List<ResultInfo> pendingResults, - List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, - String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) + ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + Bundle state, List<ResultInfo> pendingResults, + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, + String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException; void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, int configChanges, diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index f3bc495..d7f5c55 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1375,6 +1375,7 @@ public class Instrumentation { } } try { + intent.setAllowFds(false); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), @@ -1415,6 +1416,7 @@ public class Instrumentation { try { String[] resolvedTypes = new String[intents.length]; for (int i=0; i<intents.length; i++) { + intents[i].setAllowFds(false); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver()); } int result = ActivityManagerNative.getDefault() @@ -1471,6 +1473,7 @@ public class Instrumentation { } } try { + intent.setAllowFds(false); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java index 57a2695..96767ae 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -41,6 +41,12 @@ import android.os.Message; * long as necessary (and will not block the application's main loop), but * only one request will be processed at a time. * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For a detailed discussion about how to create services, read the + * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a> developer guide.</p> + * </div> + * * @see android.os.AsyncTask */ public abstract class IntentService extends Service { diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 2549c84..522f477 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -661,6 +661,9 @@ public final class LoadedApk { "Finishing broadcast to unregistered receiver"); IActivityManager mgr = ActivityManagerNative.getDefault(); try { + if (extras != null) { + extras.setAllowFds(false); + } mgr.finishReceiver(this, resultCode, data, extras, false); } catch (RemoteException e) { Slog.w(ActivityThread.TAG, "Couldn't finish broadcast to unregistered receiver"); diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index 46a008d..89e9ddd 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -47,6 +47,12 @@ import java.lang.reflect.Modifier; * * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java * fragment_cursor} + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about using loaders, read the + * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p> + * </div> */ public abstract class LoaderManager { /** diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index b4827cb..b0637a7 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -192,6 +192,7 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { + intent.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_ACTIVITY, packageName, @@ -249,6 +250,7 @@ public final class PendingIntent implements Parcelable { 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 { @@ -287,6 +289,7 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { + intent.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_BROADCAST, packageName, @@ -324,6 +327,7 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { + intent.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_SERVICE, packageName, diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index ebde6e0..35bd8c0 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -50,10 +50,6 @@ import java.io.PrintWriter; * Threads</a>. The {@link IntentService} class is available * as a standard implementation of Service that has its own thread where it * schedules its work to be done.</p> - * - * <p>You can find a detailed discussion about how to create services in the - * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a> - * document.</p> * * <p>Topics covered here: * <ol> @@ -64,7 +60,13 @@ import java.io.PrintWriter; * <li><a href="#LocalServiceSample">Local Service Sample</a> * <li><a href="#RemoteMessengerServiceSample">Remote Messenger Service Sample</a> * </ol> - * + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For a detailed discussion about how to create services, read the + * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a> developer guide.</p> + * </div> + * * <a name="WhatIsAService"></a> * <h3>What is a Service?</h3> * diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java index 8aee65c..615e8ce 100644 --- a/core/java/android/app/SharedPreferencesImpl.java +++ b/core/java/android/app/SharedPreferencesImpl.java @@ -29,6 +29,7 @@ import dalvik.system.BlockGuard; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -107,7 +108,8 @@ final class SharedPreferencesImpl implements SharedPreferences { FileStatus stat = new FileStatus(); if (FileUtils.getFileStatus(mFile.getPath(), stat) && mFile.canRead()) { try { - FileInputStream str = new FileInputStream(mFile); + BufferedInputStream str = new BufferedInputStream( + new FileInputStream(mFile), 16*1024); map = XmlUtils.readMapXml(str); str.close(); } catch (XmlPullParserException e) { diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index c154296..acdd0b5 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -157,11 +157,15 @@ interface IBackupManager { * @param allApps If <code>true</code>, the resulting tar stream will include all * installed applications' data, not just those named in the <code>packageNames</code> * parameter. + * @param allIncludesSystem If {@code true}, then {@code allApps} will be interpreted + * as including packages pre-installed as part of the system. If {@code false}, + * then setting {@code allApps} to {@code true} will mean only that all 3rd-party + * applications will be included in the dataset. * @param packageNames The package names of the apps whose data (and optionally .apk files) * are to be backed up. The <code>allApps</code> parameter supersedes this. */ void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeShared, - boolean allApps, in String[] packageNames); + boolean allApps, boolean allIncludesSystem, in String[] packageNames); /** * Restore device content from the data stream passed through the given socket. The diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java index 0c034cf..170171e 100644 --- a/core/java/android/app/backup/WallpaperBackupHelper.java +++ b/core/java/android/app/backup/WallpaperBackupHelper.java @@ -35,7 +35,7 @@ import java.io.File; */ public class WallpaperBackupHelper extends FileBackupHelperBase implements BackupHelper { private static final String TAG = "WallpaperBackupHelper"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; // This path must match what the WallpaperManagerService uses private static final String WALLPAPER_IMAGE = "/data/data/com.android.settings/files/wallpaper"; diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 960d7fb..761c7eb 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -16,13 +16,17 @@ package android.appwidget; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; @@ -107,6 +111,54 @@ public class AppWidgetHostView extends FrameLayout { public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) { mAppWidgetId = appWidgetId; mInfo = info; + + // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for + // a widget, eg. for some widgets in safe mode. + if (info != null) { + // We add padding to the AppWidgetHostView if necessary + Padding padding = getPaddingForWidget(info.provider); + setPadding(padding.left, padding.top, padding.right, padding.bottom); + } + } + + private static class Padding { + int left = 0; + int right = 0; + int top = 0; + int bottom = 0; + } + + /** + * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting + * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend + * that widget developers do not add extra padding to their widgets. This will help + * achieve consistency among widgets. + */ + private Padding getPaddingForWidget(ComponentName component) { + PackageManager packageManager = mContext.getPackageManager(); + Padding p = new Padding(); + ApplicationInfo appInfo; + + try { + appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0); + } catch (Exception e) { + // if we can't find the package, return 0 padding + return p; + } + + if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + Resources r = getResources(); + p.left = r.getDimensionPixelSize(com.android.internal. + R.dimen.default_app_widget_padding_left); + p.right = r.getDimensionPixelSize(com.android.internal. + R.dimen.default_app_widget_padding_right); + p.top = r.getDimensionPixelSize(com.android.internal. + R.dimen.default_app_widget_padding_top); + p.bottom = r.getDimensionPixelSize(com.android.internal. + R.dimen.default_app_widget_padding_bottom); + } + + return p; } public int getAppWidgetId() { diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 028149b..d71a8d6 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -90,7 +90,14 @@ import android.util.Slog; * <li><a href="#Permissions">Permissions</a> * <li><a href="#ProcessLifecycle">Process Lifecycle</a> * </ol> - * + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For information about how to use this class to receive and resolve intents, read the + * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a> + * developer guide.</p> + * </div> + * * <a name="ReceiverLifecycle"></a> * <h3>Receiver Lifecycle</h3> * @@ -366,6 +373,9 @@ public abstract class BroadcastReceiver { mFinished = true; try { + if (mResultExtras != null) { + mResultExtras.setAllowFds(false); + } if (mOrderedHint) { am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras, mAbortBroadcast); @@ -462,6 +472,7 @@ public abstract class BroadcastReceiver { IActivityManager am = ActivityManagerNative.getDefault(); IBinder binder = null; try { + service.setAllowFds(false); binder = am.peekService(service, service.resolveTypeIfNeeded( myContext.getContentResolver())); } catch (RemoteException e) { diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 8057d4b..e452f1f 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -22,10 +22,6 @@ import android.content.pm.ProviderInfo; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.database.Cursor; -import android.database.CursorToBulkCursorAdaptor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; import android.database.SQLException; import android.net.Uri; import android.os.AsyncTask; @@ -168,22 +164,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return ContentProvider.this; } - /** - * Remote version of a query, which returns an IBulkCursor. The bulk - * cursor should be wrapped with BulkCursorToCursorAdaptor before use. - */ - public IBulkCursor bulkQuery(Uri uri, String[] projection, - String selection, String[] selectionArgs, String sortOrder, - IContentObserver observer, CursorWindow window) { - enforceReadPermission(uri); - Cursor cursor = ContentProvider.this.query(uri, projection, - selection, selectionArgs, sortOrder); - if (cursor == null) { - return null; - } - return new CursorToBulkCursorAdaptor(cursor, observer, - ContentProvider.this.getClass().getName(), - hasWritePermission(uri), window); + @Override + public String getProviderName() { + return getContentProvider().getClass().getName(); } public Cursor query(Uri uri, String[] projection, diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index abeeb74..b089bf2 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -20,6 +20,7 @@ import android.content.res.AssetFileDescriptor; import android.database.BulkCursorNative; import android.database.BulkCursorToCursorAdaptor; import android.database.Cursor; +import android.database.CursorToBulkCursorAdaptor; import android.database.CursorWindow; import android.database.DatabaseUtils; import android.database.IBulkCursor; @@ -65,6 +66,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return new ContentProviderProxy(obj); } + /** + * Gets the name of the content provider. + * Should probably be part of the {@link IContentProvider} interface. + * @return The content provider name. + */ + public abstract String getProviderName(); + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -98,33 +106,24 @@ abstract public class ContentProviderNative extends Binder implements IContentPr } String sortOrder = data.readString(); - IContentObserver observer = IContentObserver.Stub. - asInterface(data.readStrongBinder()); - CursorWindow window = CursorWindow.CREATOR.createFromParcel(data); - - // Flag for whether caller wants the number of - // rows in the cursor and the position of the - // "_id" column index (or -1 if non-existent) - // Only to be returned if binder != null. - boolean wantsCursorMetadata = data.readInt() != 0; - - IBulkCursor bulkCursor = bulkQuery(url, projection, selection, - selectionArgs, sortOrder, observer, window); - if (bulkCursor != null) { - final IBinder binder = bulkCursor.asBinder(); - if (wantsCursorMetadata) { - final int count = bulkCursor.count(); - final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex( - bulkCursor.getColumnNames()); - - reply.writeNoException(); - reply.writeStrongBinder(binder); - reply.writeInt(count); - reply.writeInt(index); - } else { - reply.writeNoException(); - reply.writeStrongBinder(binder); - } + IContentObserver observer = IContentObserver.Stub.asInterface( + data.readStrongBinder()); + + Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder); + if (cursor != null) { + CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor( + cursor, observer, getProviderName()); + final IBinder binder = adaptor.asBinder(); + final int count = adaptor.count(); + final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex( + adaptor.getColumnNames()); + final boolean wantsAllOnMoveCalls = adaptor.getWantsAllOnMoveCalls(); + + reply.writeNoException(); + reply.writeStrongBinder(binder); + reply.writeInt(count); + reply.writeInt(index); + reply.writeInt(wantsAllOnMoveCalls ? 1 : 0); } else { reply.writeNoException(); reply.writeStrongBinder(null); @@ -324,332 +323,301 @@ final class ContentProviderProxy implements IContentProvider return mRemote; } - // Like bulkQuery() but sets up provided 'adaptor' if not null. - private IBulkCursor bulkQueryInternal( - Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, - IContentObserver observer, CursorWindow window, - BulkCursorToCursorAdaptor adaptor) throws RemoteException { + public Cursor query(Uri url, String[] projection, String selection, + String[] selectionArgs, String sortOrder) throws RemoteException { + BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - int length = 0; - if (projection != null) { - length = projection.length; - } - data.writeInt(length); - for (int i = 0; i < length; i++) { - data.writeString(projection[i]); - } - data.writeString(selection); - if (selectionArgs != null) { - length = selectionArgs.length; - } else { - length = 0; - } - data.writeInt(length); - for (int i = 0; i < length; i++) { - data.writeString(selectionArgs[i]); - } - data.writeString(sortOrder); - data.writeStrongBinder(observer.asBinder()); - window.writeToParcel(data, 0); - - // Flag for whether or not we want the number of rows in the - // cursor and the position of the "_id" column index (or -1 if - // non-existent). Only to be returned if binder != null. - final boolean wantsCursorMetadata = (adaptor != null); - data.writeInt(wantsCursorMetadata ? 1 : 0); - - mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); + url.writeToParcel(data, 0); + int length = 0; + if (projection != null) { + length = projection.length; + } + data.writeInt(length); + for (int i = 0; i < length; i++) { + data.writeString(projection[i]); + } + data.writeString(selection); + if (selectionArgs != null) { + length = selectionArgs.length; + } else { + length = 0; + } + data.writeInt(length); + for (int i = 0; i < length; i++) { + data.writeString(selectionArgs[i]); + } + data.writeString(sortOrder); + data.writeStrongBinder(adaptor.getObserver().asBinder()); - DatabaseUtils.readExceptionFromParcel(reply); + mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); - IBulkCursor bulkCursor = null; - IBinder bulkCursorBinder = reply.readStrongBinder(); - if (bulkCursorBinder != null) { - bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder); + DatabaseUtils.readExceptionFromParcel(reply); - if (wantsCursorMetadata) { + IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder()); + if (bulkCursor != null) { int rowCount = reply.readInt(); int idColumnPosition = reply.readInt(); - if (bulkCursor != null) { - adaptor.set(bulkCursor, rowCount, idColumnPosition); - } + boolean wantsAllOnMoveCalls = reply.readInt() != 0; + adaptor.initialize(bulkCursor, rowCount, idColumnPosition, wantsAllOnMoveCalls); + } else { + adaptor.close(); + adaptor = null; } + return adaptor; + } catch (RemoteException ex) { + adaptor.close(); + throw ex; + } catch (RuntimeException ex) { + adaptor.close(); + throw ex; + } finally { + data.recycle(); + reply.recycle(); } - - data.recycle(); - reply.recycle(); - - return bulkCursor; - } - - public IBulkCursor bulkQuery(Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, IContentObserver observer, - CursorWindow window) throws RemoteException { - return bulkQueryInternal( - url, projection, selection, selectionArgs, sortOrder, - observer, window, - null /* BulkCursorToCursorAdaptor */); - } - - public Cursor query(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder) throws RemoteException { - //TODO make a pool of windows so we can reuse memory dealers - CursorWindow window = new CursorWindow(false /* window will be used remotely */); - BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); - IBulkCursor bulkCursor = bulkQueryInternal( - url, projection, selection, selectionArgs, sortOrder, - adaptor.getObserver(), window, - adaptor); - if (bulkCursor == null) { - return null; - } - return adaptor; } public String getType(Uri url) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - - mRemote.transact(IContentProvider.GET_TYPE_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - String out = reply.readString(); + url.writeToParcel(data, 0); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.GET_TYPE_TRANSACTION, data, reply, 0); - return out; + DatabaseUtils.readExceptionFromParcel(reply); + String out = reply.readString(); + return out; + } finally { + data.recycle(); + reply.recycle(); + } } public Uri insert(Uri url, ContentValues values) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - values.writeToParcel(data, 0); - - mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - Uri out = Uri.CREATOR.createFromParcel(reply); + url.writeToParcel(data, 0); + values.writeToParcel(data, 0); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0); - return out; + DatabaseUtils.readExceptionFromParcel(reply); + Uri out = Uri.CREATOR.createFromParcel(reply); + return out; + } finally { + data.recycle(); + reply.recycle(); + } } public int bulkInsert(Uri url, ContentValues[] values) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - data.writeTypedArray(values, 0); - - mRemote.transact(IContentProvider.BULK_INSERT_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - int count = reply.readInt(); + url.writeToParcel(data, 0); + data.writeTypedArray(values, 0); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.BULK_INSERT_TRANSACTION, data, reply, 0); - return count; + DatabaseUtils.readExceptionFromParcel(reply); + int count = reply.readInt(); + return count; + } finally { + data.recycle(); + reply.recycle(); + } } public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); - - data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInt(operations.size()); - for (ContentProviderOperation operation : operations) { - operation.writeToParcel(data, 0); + try { + data.writeInterfaceToken(IContentProvider.descriptor); + data.writeInt(operations.size()); + for (ContentProviderOperation operation : operations) { + operation.writeToParcel(data, 0); + } + mRemote.transact(IContentProvider.APPLY_BATCH_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionWithOperationApplicationExceptionFromParcel(reply); + final ContentProviderResult[] results = + reply.createTypedArray(ContentProviderResult.CREATOR); + return results; + } finally { + data.recycle(); + reply.recycle(); } - mRemote.transact(IContentProvider.APPLY_BATCH_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionWithOperationApplicationExceptionFromParcel(reply); - final ContentProviderResult[] results = - reply.createTypedArray(ContentProviderResult.CREATOR); - - data.recycle(); - reply.recycle(); - - return results; } public int delete(Uri url, String selection, String[] selectionArgs) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - data.writeString(selection); - data.writeStringArray(selectionArgs); - - mRemote.transact(IContentProvider.DELETE_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - int count = reply.readInt(); + url.writeToParcel(data, 0); + data.writeString(selection); + data.writeStringArray(selectionArgs); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.DELETE_TRANSACTION, data, reply, 0); - return count; + DatabaseUtils.readExceptionFromParcel(reply); + int count = reply.readInt(); + return count; + } finally { + data.recycle(); + reply.recycle(); + } } public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - values.writeToParcel(data, 0); - data.writeString(selection); - data.writeStringArray(selectionArgs); - - mRemote.transact(IContentProvider.UPDATE_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - int count = reply.readInt(); + url.writeToParcel(data, 0); + values.writeToParcel(data, 0); + data.writeString(selection); + data.writeStringArray(selectionArgs); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.UPDATE_TRANSACTION, data, reply, 0); - return count; + DatabaseUtils.readExceptionFromParcel(reply); + int count = reply.readInt(); + return count; + } finally { + data.recycle(); + reply.recycle(); + } } public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - data.writeString(mode); - - mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply); - int has = reply.readInt(); - ParcelFileDescriptor fd = has != 0 ? reply.readFileDescriptor() : null; + url.writeToParcel(data, 0); + data.writeString(mode); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0); - return fd; + DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply); + int has = reply.readInt(); + ParcelFileDescriptor fd = has != 0 ? reply.readFileDescriptor() : null; + return fd; + } finally { + data.recycle(); + reply.recycle(); + } } public AssetFileDescriptor openAssetFile(Uri url, String mode) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - data.writeString(mode); - - mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply); - int has = reply.readInt(); - AssetFileDescriptor fd = has != 0 - ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null; + url.writeToParcel(data, 0); + data.writeString(mode); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0); - return fd; + DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply); + int has = reply.readInt(); + AssetFileDescriptor fd = has != 0 + ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null; + return fd; + } finally { + data.recycle(); + reply.recycle(); + } } public Bundle call(String method, String request, Bundle args) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - data.writeString(method); - data.writeString(request); - data.writeBundle(args); - - mRemote.transact(IContentProvider.CALL_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - Bundle bundle = reply.readBundle(); + data.writeString(method); + data.writeString(request); + data.writeBundle(args); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.CALL_TRANSACTION, data, reply, 0); - return bundle; + DatabaseUtils.readExceptionFromParcel(reply); + Bundle bundle = reply.readBundle(); + return bundle; + } finally { + data.recycle(); + reply.recycle(); + } } public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - data.writeString(mimeTypeFilter); - - mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - String[] out = reply.createStringArray(); + url.writeToParcel(data, 0); + data.writeString(mimeTypeFilter); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0); - return out; + DatabaseUtils.readExceptionFromParcel(reply); + String[] out = reply.createStringArray(); + return out; + } finally { + data.recycle(); + reply.recycle(); + } } public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); - - data.writeInterfaceToken(IContentProvider.descriptor); - - url.writeToParcel(data, 0); - data.writeString(mimeType); - data.writeBundle(opts); - - mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply); - int has = reply.readInt(); - AssetFileDescriptor fd = has != 0 - ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null; - - data.recycle(); - reply.recycle(); - - return fd; + try { + data.writeInterfaceToken(IContentProvider.descriptor); + + url.writeToParcel(data, 0); + data.writeString(mimeType); + data.writeBundle(opts); + + mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply); + int has = reply.readInt(); + AssetFileDescriptor fd = has != 0 + ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null; + return fd; + } finally { + data.recycle(); + reply.recycle(); + } } private IBinder mRemote; diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 1e72092..0d25926 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -16,6 +16,8 @@ package android.content; +import dalvik.system.CloseGuard; + import android.accounts.Account; import android.app.ActivityManagerNative; import android.app.ActivityThread; @@ -33,6 +35,7 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.text.TextUtils; import android.util.EventLog; @@ -1562,27 +1565,39 @@ public abstract class ContentResolver { private final class CursorWrapperInner extends CursorWrapper { private final IContentProvider mContentProvider; public static final String TAG="CursorWrapperInner"; - private boolean mCloseFlag = false; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + private boolean mProviderReleased; CursorWrapperInner(Cursor cursor, IContentProvider icp) { super(cursor); mContentProvider = icp; + mCloseGuard.open("close"); } @Override public void close() { super.close(); ContentResolver.this.releaseProvider(mContentProvider); - mCloseFlag = true; + mProviderReleased = true; + + if (mCloseGuard != null) { + mCloseGuard.close(); + } } @Override protected void finalize() throws Throwable { - // TODO: integrate CloseGuard support. try { - if(!mCloseFlag) { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + + if (!mProviderReleased && mContentProvider != null) { + // Even though we are using CloseGuard, log this anyway so that + // application developers always see the message in the log. Log.w(TAG, "Cursor finalized without prior close()"); - close(); + ContentResolver.this.releaseProvider(mContentProvider); } } finally { super.finalize(); diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index 6228bd0..7af535b 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -19,6 +19,7 @@ package android.content; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; +import android.os.AsyncTask; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 72bc9c2..2a67ff8 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -18,9 +18,6 @@ package android.content; import android.content.res.AssetFileDescriptor; import android.database.Cursor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -36,13 +33,6 @@ import java.util.ArrayList; * @hide */ public interface IContentProvider extends IInterface { - /** - * @hide - hide this because return type IBulkCursor and parameter - * IContentObserver are system private classes. - */ - public IBulkCursor bulkQuery(Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, IContentObserver observer, - CursorWindow window) throws RemoteException; public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException; public String getType(Uri url) throws RemoteException; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index f44d038..45a42e4 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -55,13 +55,21 @@ import java.util.Set; * {@link android.content.Context#bindService} to communicate with a * background {@link android.app.Service}. * - * <p>An Intent provides a facility for performing late runtime binding between - * the code in different applications. Its most significant use is in the - * launching of activities, where it can be thought of as the glue between - * activities. It is - * basically a passive data structure holding an abstract description of an - * action to be performed. The primary pieces of information in an intent - * are:</p> + * <p>An Intent provides a facility for performing late runtime binding between the code in + * different applications. Its most significant use is in the launching of activities, where it + * can be thought of as the glue between activities. It is basically a passive data structure + * holding an abstract description of an action to be performed.</p> + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For information about how to create and resolve intents, read the + * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a> + * developer guide.</p> + * </div> + * + * <a name="IntentStructure"></a> + * <h3>Intent Structure</h3> + * <p>The primary pieces of information in an intent are:</p> * * <ul> * <li> <p><b>action</b> -- The general action to be performed, such as @@ -2558,7 +2566,7 @@ public class Intent implements Parcelable, Cloneable { */ public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY"; - + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). @@ -3568,6 +3576,13 @@ public class Intent implements Parcelable, Cloneable { return mExtras != null && mExtras.hasFileDescriptors(); } + /** @hide */ + public void setAllowFds(boolean allowFds) { + if (mExtras != null) { + mExtras.setAllowFds(allowFds); + } + } + /** * Retrieve extended data from the intent. * @@ -5276,7 +5291,7 @@ public class Intent implements Parcelable, Cloneable { if (r != null) { mSourceBounds = new Rect(r); } else { - r = null; + mSourceBounds = null; } } diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index f9b3fd5..3b0d846 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -71,6 +71,14 @@ import java.util.Set; * To specify a path, you also must specify both one or more authorities and * one or more schemes it is associated with. * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For information about how to create and resolve intents, read the + * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a> + * developer guide.</p> + * </div> + * + * <h3>Filter Rules</h3> * <p>A match is based on the following rules. Note that * for an IntentFilter to match an Intent, three conditions must hold: * the <strong>action</strong> and <strong>category</strong> must match, and diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java index 368c33e..b962800 100644 --- a/core/java/android/content/Loader.java +++ b/core/java/android/content/Loader.java @@ -40,8 +40,12 @@ import java.io.PrintWriter; * * <p>Most implementations should not derive directly from this class, but * instead inherit from {@link AsyncTaskLoader}.</p> - * <p>For more information, see the <a - * href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p> + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about using loaders, read the + * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p> + * </div> * * @param <D> The result returned when the load is complete */ diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 127efa2..4225393 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -1301,7 +1301,7 @@ public class SyncManager implements OnAccountsUpdateListener { elapsedTime = authoritySyncStats.elapsedTime; times = authoritySyncStats.times; - timeStr = String.format("%d/%d%%", + timeStr = String.format("%ds/%d%%", elapsedTime / 1000, elapsedTime * 100 / totalElapsedTime); timesStr = String.format("%d/%d%%", @@ -1309,32 +1309,30 @@ public class SyncManager implements OnAccountsUpdateListener { times * 100 / totalTimes); pw.printf(authorityFormat, name, timesStr, timeStr); - if (authoritySyncStats.accountMap.size() > 1) { - final List<AccountSyncStats> sortedAccounts = - new ArrayList<AccountSyncStats>( - authoritySyncStats.accountMap.values()); - Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() { - @Override - public int compare(AccountSyncStats lhs, AccountSyncStats rhs) { - // reverse order - int compare = Integer.compare(rhs.times, lhs.times); - if (compare == 0) { - compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime); - } - return compare; + final List<AccountSyncStats> sortedAccounts = + new ArrayList<AccountSyncStats>( + authoritySyncStats.accountMap.values()); + Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() { + @Override + public int compare(AccountSyncStats lhs, AccountSyncStats rhs) { + // reverse order + int compare = Integer.compare(rhs.times, lhs.times); + if (compare == 0) { + compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime); } - }); - for (AccountSyncStats stats: sortedAccounts) { - elapsedTime = stats.elapsedTime; - times = stats.times; - timeStr = String.format("%d/%d%%", - elapsedTime / 1000, - elapsedTime * 100 / totalElapsedTime); - timesStr = String.format("%d/%d%%", - times, - times * 100 / totalTimes); - pw.printf(accountFormat, stats.name, timesStr, timeStr); + return compare; } + }); + for (AccountSyncStats stats: sortedAccounts) { + elapsedTime = stats.elapsedTime; + times = stats.times; + timeStr = String.format("%ds/%d%%", + elapsedTime / 1000, + elapsedTime * 100 / totalElapsedTime); + timesStr = String.format("%d/%d%%", + times, + times * 100 / totalTimes); + pw.printf(accountFormat, stats.name, timesStr, timeStr); } pw.println(separator); } @@ -1342,9 +1340,8 @@ public class SyncManager implements OnAccountsUpdateListener { pw.println(); pw.println("Recent Sync History"); final String format = " %-" + maxAccount + "s %s\n"; - String lastAuthorityName = null; - String lastAccountKey = null; - long lastEventTime = 0; + final Map<String, Long> lastTimeMap = Maps.newHashMap(); + for (int i = 0; i < N; i++) { SyncStorageEngine.SyncHistoryItem item = items.get(i); SyncStorageEngine.AuthorityInfo authority @@ -1363,21 +1360,32 @@ public class SyncManager implements OnAccountsUpdateListener { final long eventTime = item.eventTime; time.set(eventTime); - pw.printf(" #%-3d: %s %8s %5.1fs", - i + 1, - formatTime(eventTime), - SyncStorageEngine.SOURCES[item.source], - ((float) elapsedTime) / 1000); - if (authorityName.equals(lastAuthorityName) && accountKey.equals(lastAccountKey)) { - final long span = (lastEventTime - eventTime) / 1000; - pw.printf(" %02d:%02d\n", span / 60, span % 60); + final String key = authorityName + "/" + accountKey; + final Long lastEventTime = lastTimeMap.get(key); + final String diffString; + if (lastEventTime == null) { + diffString = ""; } else { - pw.printf(format, accountKey, authorityName); + final long diff = (lastEventTime - eventTime) / 1000; + if (diff < 60) { + diffString = String.valueOf(diff); + } else if (diff < 3600) { + diffString = String.format("%02d:%02d", diff / 60, diff % 60); + } else { + final long sec = diff % 3600; + diffString = String.format("%02d:%02d:%02d", + diff / 3600, sec / 60, sec % 60); + } } + lastTimeMap.put(key, eventTime); - lastAuthorityName = authorityName; - lastAccountKey = accountKey; - lastEventTime = eventTime; + pw.printf(" #%-3d: %s %8s %5.1fs %8s", + i + 1, + formatTime(eventTime), + SyncStorageEngine.SOURCES[item.source], + ((float) elapsedTime) / 1000, + diffString); + pw.printf(format, accountKey, authorityName); if (item.event != SyncStorageEngine.EVENT_STOP || item.upstreamActivity != 0 diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index a3bcc28..decb974 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -362,4 +362,6 @@ interface IPackageManager { void verifyPendingInstall(int id, int verificationCode); VerifierDeviceIdentity getVerifierDeviceIdentity(); + + boolean isFirstBoot(); } diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index 5fe42db..ee6aec6 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -64,7 +64,10 @@ public abstract class AbstractCursor implements CrossProcessCursor { /* Methods that may optionally be implemented by subclasses */ /** - * returns a pre-filled window, return NULL if no such window + * If the cursor is backed by a {@link CursorWindow}, returns a pre-filled + * window with the contents of the cursor, otherwise null. + * + * @return The pre-filled window that backs this cursor, or null if none. */ public CursorWindow getWindow() { return null; @@ -75,13 +78,11 @@ public abstract class AbstractCursor implements CrossProcessCursor { } public void deactivate() { - deactivateInternal(); + onDeactivateOrClose(); } - /** - * @hide - */ - public void deactivateInternal() { + /** @hide */ + protected void onDeactivateOrClose() { if (mSelfObserver != null) { mContentResolver.unregisterContentObserver(mSelfObserver); mSelfObserverRegistered = false; @@ -105,7 +106,7 @@ public abstract class AbstractCursor implements CrossProcessCursor { public void close() { mClosed = true; mContentObservable.unregisterAll(); - deactivateInternal(); + onDeactivateOrClose(); } /** diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java index 3d95769..d0aedd2 100644 --- a/core/java/android/database/AbstractWindowedCursor.java +++ b/core/java/android/database/AbstractWindowedCursor.java @@ -18,8 +18,27 @@ package android.database; /** * A base class for Cursors that store their data in {@link CursorWindow}s. + * <p> + * The cursor owns the cursor window it uses. When the cursor is closed, + * its window is also closed. Likewise, when the window used by the cursor is + * changed, its old window is closed. This policy of strict ownership ensures + * that cursor windows are not leaked. + * </p><p> + * Subclasses are responsible for filling the cursor window with data during + * {@link #onMove(int, int)}, allocating a new cursor window if necessary. + * During {@link #requery()}, the existing cursor window should be cleared and + * filled with new data. + * </p><p> + * If the contents of the cursor change or become invalid, the old window must be closed + * (because it is owned by the cursor) and set to null. + * </p> */ public abstract class AbstractWindowedCursor extends AbstractCursor { + /** + * The cursor window owned by this cursor. + */ + protected CursorWindow mWindow; + @Override public byte[] getBlob(int columnIndex) { checkPosition(); @@ -126,25 +145,67 @@ public abstract class AbstractWindowedCursor extends AbstractCursor { public CursorWindow getWindow() { return mWindow; } - + /** - * Set a new cursor window to cursor, usually set a remote cursor window - * @param window cursor window + * Sets a new cursor window for the cursor to use. + * <p> + * The cursor takes ownership of the provided cursor window; the cursor window + * will be closed when the cursor is closed or when the cursor adopts a new + * cursor window. + * </p><p> + * If the cursor previously had a cursor window, then it is closed when the + * new cursor window is assigned. + * </p> + * + * @param window The new cursor window, typically a remote cursor window. */ public void setWindow(CursorWindow window) { - if (mWindow != null) { - mWindow.close(); + if (window != mWindow) { + closeWindow(); + mWindow = window; } - mWindow = window; } - + + /** + * Returns true if the cursor has an associated cursor window. + * + * @return True if the cursor has an associated cursor window. + */ public boolean hasWindow() { return mWindow != null; } /** - * This needs be updated in {@link #onMove} by subclasses, and - * needs to be set to NULL when the contents of the cursor change. + * Closes the cursor window and sets {@link #mWindow} to null. + * @hide */ - protected CursorWindow mWindow; + protected void closeWindow() { + if (mWindow != null) { + mWindow.close(); + mWindow = null; + } + } + + /** + * If there is a window, clear it. + * Otherwise, creates a local window. + * + * @param name The window name. + * @hide + */ + protected void clearOrCreateLocalWindow(String name) { + if (mWindow == null) { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(name, true /* the window is local only */); + } else { + mWindow.clear(); + } + } + + /** @hide */ + @Override + protected void onDeactivateOrClose() { + super.onDeactivateOrClose(); + closeWindow(); + } } diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java index fa62d69..20a9c67 100644 --- a/core/java/android/database/BulkCursorNative.java +++ b/core/java/android/database/BulkCursorNative.java @@ -20,12 +20,13 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; /** * Native implementation of the bulk cursor. This is only for use in implementing * IPC, application code should use the Cursor interface. - * + * * {@hide} */ public abstract class BulkCursorNative extends Binder implements IBulkCursor @@ -61,13 +62,13 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor data.enforceInterface(IBulkCursor.descriptor); int startPos = data.readInt(); CursorWindow window = getWindow(startPos); + reply.writeNoException(); if (window == null) { reply.writeInt(0); - return true; + } else { + reply.writeInt(1); + window.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } - reply.writeNoException(); - reply.writeInt(1); - window.writeToParcel(reply, 0); return true; } @@ -108,9 +109,8 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor case REQUERY_TRANSACTION: { data.enforceInterface(IBulkCursor.descriptor); IContentObserver observer = - IContentObserver.Stub.asInterface(data.readStrongBinder()); - CursorWindow window = CursorWindow.CREATOR.createFromParcel(data); - int count = requery(observer, window); + IContentObserver.Stub.asInterface(data.readStrongBinder()); + int count = requery(observer); reply.writeNoException(); reply.writeInt(count); reply.writeBundle(getExtras()); @@ -184,172 +184,171 @@ final class BulkCursorProxy implements IBulkCursor { { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); + data.writeInt(startPos); - data.writeInterfaceToken(IBulkCursor.descriptor); - - data.writeInt(startPos); - - mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0); + mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); - DatabaseUtils.readExceptionFromParcel(reply); - - CursorWindow window = null; - if (reply.readInt() == 1) { - window = CursorWindow.newFromParcel(reply); + CursorWindow window = null; + if (reply.readInt() == 1) { + window = CursorWindow.newFromParcel(reply); + } + return window; + } finally { + data.recycle(); + reply.recycle(); } - - data.recycle(); - reply.recycle(); - - return window; } public void onMove(int position) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); + data.writeInt(position); - data.writeInterfaceToken(IBulkCursor.descriptor); - - data.writeInt(position); - - mRemote.transact(ON_MOVE_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - - data.recycle(); - reply.recycle(); + mRemote.transact(ON_MOVE_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); + } finally { + data.recycle(); + reply.recycle(); + } } public int count() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); - data.writeInterfaceToken(IBulkCursor.descriptor); - - boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0); + boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); - DatabaseUtils.readExceptionFromParcel(reply); - - int count; - if (result == false) { - count = -1; - } else { - count = reply.readInt(); + int count; + if (result == false) { + count = -1; + } else { + count = reply.readInt(); + } + return count; + } finally { + data.recycle(); + reply.recycle(); } - data.recycle(); - reply.recycle(); - return count; } public String[] getColumnNames() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); - data.writeInterfaceToken(IBulkCursor.descriptor); - - mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0); + mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); - DatabaseUtils.readExceptionFromParcel(reply); - - String[] columnNames = null; - int numColumns = reply.readInt(); - columnNames = new String[numColumns]; - for (int i = 0; i < numColumns; i++) { - columnNames[i] = reply.readString(); + String[] columnNames = null; + int numColumns = reply.readInt(); + columnNames = new String[numColumns]; + for (int i = 0; i < numColumns; i++) { + columnNames[i] = reply.readString(); + } + return columnNames; + } finally { + data.recycle(); + reply.recycle(); } - - data.recycle(); - reply.recycle(); - return columnNames; } public void deactivate() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); - data.writeInterfaceToken(IBulkCursor.descriptor); - - mRemote.transact(DEACTIVATE_TRANSACTION, data, reply, 0); - DatabaseUtils.readExceptionFromParcel(reply); - - data.recycle(); - reply.recycle(); + mRemote.transact(DEACTIVATE_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); + } finally { + data.recycle(); + reply.recycle(); + } } public void close() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); - data.writeInterfaceToken(IBulkCursor.descriptor); - - mRemote.transact(CLOSE_TRANSACTION, data, reply, 0); - DatabaseUtils.readExceptionFromParcel(reply); - - data.recycle(); - reply.recycle(); + mRemote.transact(CLOSE_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); + } finally { + data.recycle(); + reply.recycle(); + } } - public int requery(IContentObserver observer, CursorWindow window) throws RemoteException { + public int requery(IContentObserver observer) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); + data.writeStrongInterface(observer); - data.writeInterfaceToken(IBulkCursor.descriptor); - - data.writeStrongInterface(observer); - window.writeToParcel(data, 0); - - boolean result = mRemote.transact(REQUERY_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); + boolean result = mRemote.transact(REQUERY_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); - int count; - if (!result) { - count = -1; - } else { - count = reply.readInt(); - mExtras = reply.readBundle(); + int count; + if (!result) { + count = -1; + } else { + count = reply.readInt(); + mExtras = reply.readBundle(); + } + return count; + } finally { + data.recycle(); + reply.recycle(); } - - data.recycle(); - reply.recycle(); - - return count; } public boolean getWantsAllOnMoveCalls() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); - data.writeInterfaceToken(IBulkCursor.descriptor); - - mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); + mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); - int result = reply.readInt(); - data.recycle(); - reply.recycle(); - return result != 0; + int result = reply.readInt(); + return result != 0; + } finally { + data.recycle(); + reply.recycle(); + } } public Bundle getExtras() throws RemoteException { if (mExtras == null) { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); - data.writeInterfaceToken(IBulkCursor.descriptor); + mRemote.transact(GET_EXTRAS_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); - mRemote.transact(GET_EXTRAS_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - - mExtras = reply.readBundle(); - data.recycle(); - reply.recycle(); + mExtras = reply.readBundle(); + } finally { + data.recycle(); + reply.recycle(); + } } return mExtras; } @@ -357,19 +356,19 @@ final class BulkCursorProxy implements IBulkCursor { public Bundle respond(Bundle extras) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IBulkCursor.descriptor); + data.writeBundle(extras); - data.writeInterfaceToken(IBulkCursor.descriptor); - - data.writeBundle(extras); - - mRemote.transact(RESPOND_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); + mRemote.transact(RESPOND_TRANSACTION, data, reply, 0); + DatabaseUtils.readExceptionFromParcel(reply); - Bundle returnExtras = reply.readBundle(); - data.recycle(); - reply.recycle(); - return returnExtras; + Bundle returnExtras = reply.readBundle(); + return returnExtras; + } finally { + data.recycle(); + reply.recycle(); + } } } diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java index 16becf5..885046b 100644 --- a/core/java/android/database/BulkCursorToCursorAdaptor.java +++ b/core/java/android/database/BulkCursorToCursorAdaptor.java @@ -21,44 +21,30 @@ import android.os.RemoteException; import android.util.Log; /** - * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local - * process. + * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local process. * * {@hide} */ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { private static final String TAG = "BulkCursor"; - private SelfContentObserver mObserverBridge; + private SelfContentObserver mObserverBridge = new SelfContentObserver(this); private IBulkCursor mBulkCursor; private int mCount; private String[] mColumns; private boolean mWantsAllOnMoveCalls; - public void set(IBulkCursor bulkCursor) { - mBulkCursor = bulkCursor; - - try { - mCount = mBulkCursor.count(); - mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls(); - - // Search for the rowID column index and set it for our parent - mColumns = mBulkCursor.getColumnNames(); - mRowIdColumnIndex = findRowIdColumnIndex(mColumns); - } catch (RemoteException ex) { - Log.e(TAG, "Setup failed because the remote process is dead"); - } - } - /** - * Version of set() that does fewer Binder calls if the caller - * already knows BulkCursorToCursorAdaptor's properties. + * Initializes the adaptor. + * Must be called before first use. */ - public void set(IBulkCursor bulkCursor, int count, int idIndex) { + public void initialize(IBulkCursor bulkCursor, int count, int idIndex, + boolean wantsAllOnMoveCalls) { mBulkCursor = bulkCursor; mColumns = null; // lazily retrieved mCount = count; mRowIdColumnIndex = idIndex; + mWantsAllOnMoveCalls = wantsAllOnMoveCalls; } /** @@ -80,31 +66,34 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { * * @return A SelfContentObserver hooked up to this Cursor */ - public synchronized IContentObserver getObserver() { - if (mObserverBridge == null) { - mObserverBridge = new SelfContentObserver(this); - } + public IContentObserver getObserver() { return mObserverBridge.getContentObserver(); } + private void throwIfCursorIsClosed() { + if (mBulkCursor == null) { + throw new StaleDataException("Attempted to access a cursor after it has been closed."); + } + } + @Override public int getCount() { + throwIfCursorIsClosed(); return mCount; } @Override public boolean onMove(int oldPosition, int newPosition) { + throwIfCursorIsClosed(); + try { // Make sure we have the proper window - if (mWindow != null) { - if (newPosition < mWindow.getStartPosition() || - newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { - mWindow = mBulkCursor.getWindow(newPosition); - } else if (mWantsAllOnMoveCalls) { - mBulkCursor.onMove(newPosition); - } - } else { - mWindow = mBulkCursor.getWindow(newPosition); + if (mWindow == null + || newPosition < mWindow.getStartPosition() + || newPosition >= mWindow.getStartPosition() + mWindow.getNumRows()) { + setWindow(mBulkCursor.getWindow(newPosition)); + } else if (mWantsAllOnMoveCalls) { + mBulkCursor.onMove(newPosition); } } catch (RemoteException ex) { // We tried to get a window and failed @@ -126,38 +115,39 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { // which is what actually makes the data set invalid. super.deactivate(); - try { - mBulkCursor.deactivate(); - } catch (RemoteException ex) { - Log.w(TAG, "Remote process exception when deactivating"); + if (mBulkCursor != null) { + try { + mBulkCursor.deactivate(); + } catch (RemoteException ex) { + Log.w(TAG, "Remote process exception when deactivating"); + } } - mWindow = null; } @Override public void close() { super.close(); - try { - mBulkCursor.close(); - } catch (RemoteException ex) { - Log.w(TAG, "Remote process exception when closing"); + + if (mBulkCursor != null) { + try { + mBulkCursor.close(); + } catch (RemoteException ex) { + Log.w(TAG, "Remote process exception when closing"); + } finally { + mBulkCursor = null; + } } - mWindow = null; } @Override public boolean requery() { + throwIfCursorIsClosed(); + try { - int oldCount = mCount; - //TODO get the window from a pool somewhere to avoid creating the memory dealer - mCount = mBulkCursor.requery(getObserver(), new CursorWindow( - false /* the window will be accessed across processes */)); + mCount = mBulkCursor.requery(getObserver()); if (mCount != -1) { mPos = -1; - if (mWindow != null) { - mWindow.close(); - mWindow = null; - } + closeWindow(); // super.requery() will call onChanged. Do it here instead of relying on the // observer from the far side so that observers can see a correct value for mCount @@ -177,6 +167,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public String[] getColumnNames() { + throwIfCursorIsClosed(); + if (mColumns == null) { try { mColumns = mBulkCursor.getColumnNames(); @@ -190,6 +182,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public Bundle getExtras() { + throwIfCursorIsClosed(); + try { return mBulkCursor.getExtras(); } catch (RemoteException e) { @@ -201,6 +195,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public Bundle respond(Bundle extras) { + throwIfCursorIsClosed(); + try { return mBulkCursor.respond(extras); } catch (RemoteException e) { diff --git a/core/java/android/database/CrossProcessCursor.java b/core/java/android/database/CrossProcessCursor.java index 77ba3a5..8e6a5aa 100644 --- a/core/java/android/database/CrossProcessCursor.java +++ b/core/java/android/database/CrossProcessCursor.java @@ -16,7 +16,7 @@ package android.database; -public interface CrossProcessCursor extends Cursor{ +public interface CrossProcessCursor extends Cursor { /** * returns a pre-filled window, return NULL if no such window */ diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java index 8fa4d3b..dd2c9b7 100644 --- a/core/java/android/database/CursorToBulkCursorAdaptor.java +++ b/core/java/android/database/CursorToBulkCursorAdaptor.java @@ -24,19 +24,38 @@ import android.util.Log; /** * Wraps a BulkCursor around an existing Cursor making it remotable. + * <p> + * If the wrapped cursor is a {@link AbstractWindowedCursor} then it owns + * the cursor window. Otherwise, the adaptor takes ownership of the + * cursor itself and ensures it gets closed as needed during deactivation + * and requeries. + * </p> * * {@hide} */ public final class CursorToBulkCursorAdaptor extends BulkCursorNative implements IBinder.DeathRecipient { private static final String TAG = "Cursor"; - private final CrossProcessCursor mCursor; - private CursorWindow mWindow; + + private final Object mLock = new Object(); private final String mProviderName; private ContentObserverProxy mObserver; - private static final class ContentObserverProxy extends ContentObserver - { + /** + * The cursor that is being adapted. + * This field is set to null when the cursor is closed. + */ + private CrossProcessCursor mCursor; + + /** + * The cursor window used by the cross process cursor. + * This field is always null for abstract windowed cursors since they are responsible + * for managing the lifetime of their window. + */ + private CursorWindow mWindowForNonWindowedCursor; + private boolean mWindowForNonWindowedCursorWasFilled; + + private static final class ContentObserverProxy extends ContentObserver { protected IContentObserver mRemote; public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) { @@ -69,102 +88,171 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } } - public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName, - boolean allowWrite, CursorWindow window) { + public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, + String providerName) { try { mCursor = (CrossProcessCursor) cursor; - if (mCursor instanceof AbstractWindowedCursor) { - AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor; - if (windowedCursor.hasWindow()) { - if (Log.isLoggable(TAG, Log.VERBOSE) || false) { - Log.v(TAG, "Cross process cursor has a local window before setWindow in " - + providerName, new RuntimeException()); - } - } - windowedCursor.setWindow(window); - } else { - mWindow = window; - mCursor.fillWindow(0, window); - } } catch (ClassCastException e) { - // TODO Implement this case. throw new UnsupportedOperationException( "Only CrossProcessCursor cursors are supported across process for now", e); } mProviderName = providerName; - createAndRegisterObserverProxy(observer); + synchronized (mLock) { + createAndRegisterObserverProxyLocked(observer); + } + } + + private void closeWindowForNonWindowedCursorLocked() { + if (mWindowForNonWindowedCursor != null) { + mWindowForNonWindowedCursor.close(); + mWindowForNonWindowedCursor = null; + mWindowForNonWindowedCursorWasFilled = false; + } } - + + private void disposeLocked() { + if (mCursor != null) { + unregisterObserverProxyLocked(); + mCursor.close(); + mCursor = null; + } + + closeWindowForNonWindowedCursorLocked(); + } + + private void throwIfCursorIsClosed() { + if (mCursor == null) { + throw new StaleDataException("Attempted to access a cursor after it has been closed."); + } + } + + @Override public void binderDied() { - mCursor.close(); - if (mWindow != null) { - mWindow.close(); + synchronized (mLock) { + disposeLocked(); } } - + + @Override public CursorWindow getWindow(int startPos) { - mCursor.moveToPosition(startPos); - - if (mWindow != null) { - if (startPos < mWindow.getStartPosition() || - startPos >= (mWindow.getStartPosition() + mWindow.getNumRows())) { - mCursor.fillWindow(startPos, mWindow); - } - return mWindow; - } else { - return ((AbstractWindowedCursor)mCursor).getWindow(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + CursorWindow window; + if (mCursor instanceof AbstractWindowedCursor) { + AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor)mCursor; + window = windowedCursor.getWindow(); + if (window == null) { + window = new CursorWindow(mProviderName, false /*localOnly*/); + windowedCursor.setWindow(window); + } + + mCursor.moveToPosition(startPos); + } else { + window = mWindowForNonWindowedCursor; + if (window == null) { + window = new CursorWindow(mProviderName, false /*localOnly*/); + mWindowForNonWindowedCursor = window; + } + + mCursor.moveToPosition(startPos); + + if (!mWindowForNonWindowedCursorWasFilled + || startPos < window.getStartPosition() + || startPos >= window.getStartPosition() + window.getNumRows()) { + mCursor.fillWindow(startPos, window); + mWindowForNonWindowedCursorWasFilled = true; + } + } + + // Acquire a reference before returning from this RPC. + // The Binder proxy will decrement the reference count again as part of writing + // the CursorWindow to the reply parcel as a return value. + if (window != null) { + window.acquireReference(); + } + return window; } } + @Override public void onMove(int position) { - mCursor.onMove(mCursor.getPosition(), position); + synchronized (mLock) { + throwIfCursorIsClosed(); + + mCursor.onMove(mCursor.getPosition(), position); + } } + @Override public int count() { - return mCursor.getCount(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getCount(); + } } + @Override public String[] getColumnNames() { - return mCursor.getColumnNames(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getColumnNames(); + } } + @Override public void deactivate() { - maybeUnregisterObserverProxy(); - mCursor.deactivate(); + synchronized (mLock) { + if (mCursor != null) { + unregisterObserverProxyLocked(); + mCursor.deactivate(); + } + + closeWindowForNonWindowedCursorLocked(); + } } + @Override public void close() { - maybeUnregisterObserverProxy(); - mCursor.close(); + synchronized (mLock) { + disposeLocked(); + } } - public int requery(IContentObserver observer, CursorWindow window) { - if (mWindow == null) { - ((AbstractWindowedCursor)mCursor).setWindow(window); - } - try { - if (!mCursor.requery()) { - return -1; + @Override + public int requery(IContentObserver observer) { + synchronized (mLock) { + throwIfCursorIsClosed(); + + closeWindowForNonWindowedCursorLocked(); + + try { + if (!mCursor.requery()) { + return -1; + } + } catch (IllegalStateException e) { + IllegalStateException leakProgram = new IllegalStateException( + mProviderName + " Requery misuse db, mCursor isClosed:" + + mCursor.isClosed(), e); + throw leakProgram; } - } catch (IllegalStateException e) { - IllegalStateException leakProgram = new IllegalStateException( - mProviderName + " Requery misuse db, mCursor isClosed:" + - mCursor.isClosed(), e); - throw leakProgram; - } - - if (mWindow != null) { - mCursor.fillWindow(0, window); - mWindow = window; + + unregisterObserverProxyLocked(); + createAndRegisterObserverProxyLocked(observer); + return mCursor.getCount(); } - maybeUnregisterObserverProxy(); - createAndRegisterObserverProxy(observer); - return mCursor.getCount(); } + @Override public boolean getWantsAllOnMoveCalls() { - return mCursor.getWantsAllOnMoveCalls(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getWantsAllOnMoveCalls(); + } } /** @@ -173,7 +261,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative * @param observer the IContentObserver that wants to monitor the cursor * @throws IllegalStateException if an observer is already registered */ - private void createAndRegisterObserverProxy(IContentObserver observer) { + private void createAndRegisterObserverProxyLocked(IContentObserver observer) { if (mObserver != null) { throw new IllegalStateException("an observer is already registered"); } @@ -182,7 +270,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } /** Unregister the observer if it is already registered. */ - private void maybeUnregisterObserverProxy() { + private void unregisterObserverProxyLocked() { if (mObserver != null) { mCursor.unregisterContentObserver(mObserver); mObserver.unlinkToDeath(this); @@ -190,11 +278,21 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } } + @Override public Bundle getExtras() { - return mCursor.getExtras(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getExtras(); + } } + @Override public Bundle respond(Bundle extras) { - return mCursor.respond(extras); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.respond(extras); + } } } diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index f7cbf7a..a18a721 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -16,10 +16,12 @@ package android.database; +import dalvik.system.CloseGuard; + import android.content.res.Resources; import android.database.sqlite.SQLiteClosable; +import android.database.sqlite.SQLiteException; import android.os.Binder; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; @@ -28,6 +30,13 @@ import android.util.SparseIntArray; /** * A buffer containing multiple cursor rows. + * <p> + * A {@link CursorWindow} is read-write when created and used locally. When sent + * to a remote process (by writing it to a {@link Parcel}), the remote process + * receives a read-only view of the cursor window. Typically the cursor window + * will be allocated by the producer, filled with data, and then sent to the + * consumer for reading. + * </p> */ public class CursorWindow extends SQLiteClosable implements Parcelable { private static final String STATS_TAG = "CursorWindowStats"; @@ -39,538 +48,648 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { Resources.getSystem().getInteger( com.android.internal.R.integer.config_cursorWindowSize) * 1024; - /** The pointer to the native window class. set by the native methods in - * android_database_CursorWindow.cpp + /** + * The native CursorWindow object pointer. (FOR INTERNAL USE ONLY) + * @hide */ - private int nWindow; + public int mWindowPtr; private int mStartPos; - /** - * Creates a new empty window. + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private static native int nativeCreate(String name, + int cursorWindowSize, boolean localOnly); + private static native int nativeCreateFromParcel(Parcel parcel); + private static native void nativeDispose(int windowPtr); + private static native void nativeWriteToParcel(int windowPtr, Parcel parcel); + + private static native void nativeClear(int windowPtr); + + private static native int nativeGetNumRows(int windowPtr); + private static native boolean nativeSetNumColumns(int windowPtr, int columnNum); + private static native boolean nativeAllocRow(int windowPtr); + private static native void nativeFreeLastRow(int windowPtr); + + private static native int nativeGetType(int windowPtr, int row, int column); + private static native byte[] nativeGetBlob(int windowPtr, int row, int column); + private static native String nativeGetString(int windowPtr, int row, int column); + private static native long nativeGetLong(int windowPtr, int row, int column); + private static native double nativeGetDouble(int windowPtr, int row, int column); + private static native void nativeCopyStringToBuffer(int windowPtr, int row, int column, + CharArrayBuffer buffer); + + private static native boolean nativePutBlob(int windowPtr, byte[] value, int row, int column); + private static native boolean nativePutString(int windowPtr, String value, int row, int column); + private static native boolean nativePutLong(int windowPtr, long value, int row, int column); + private static native boolean nativePutDouble(int windowPtr, double value, int row, int column); + private static native boolean nativePutNull(int windowPtr, int row, int column); + + /** + * Creates a new empty cursor window and gives it a name. + * <p> + * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to + * set the number of columns before adding any rows to the cursor. + * </p> * - * @param localWindow true if this window will be used in this process only + * @param name The name of the cursor window, or null if none. + * @param localWindow True if this window will be used in this process only, + * false if it might be sent to another processes. + * + * @hide */ - public CursorWindow(boolean localWindow) { + public CursorWindow(String name, boolean localWindow) { mStartPos = 0; - int rslt = native_init(sCursorWindowSize, localWindow); - printDebugMsgIfError(rslt); - recordNewWindow(Binder.getCallingPid(), nWindow); - } - - private void printDebugMsgIfError(int rslt) { - if (rslt > 0) { - // cursor window allocation failed. either low memory or too many cursors being open. - // print info to help in debugging this. - throw new CursorWindowAllocationException("Cursor Window allocation of " + - sCursorWindowSize/1024 + " kb failed. " + printStats()); + mWindowPtr = nativeCreate(name, sCursorWindowSize, localWindow); + if (mWindowPtr == 0) { + throw new CursorWindowAllocationException("Cursor window allocation of " + + (sCursorWindowSize / 1024) + " kb failed. " + printStats()); } + mCloseGuard.open("close"); + recordNewWindow(Binder.getCallingPid(), mWindowPtr); } /** - * Returns the starting position of this window within the entire - * Cursor's result set. + * Creates a new empty cursor window. + * <p> + * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to + * set the number of columns before adding any rows to the cursor. + * </p> * - * @return the starting position of this window within the entire - * Cursor's result set. + * @param localWindow True if this window will be used in this process only, + * false if it might be sent to another processes. */ - public int getStartPosition() { - return mStartPos; + public CursorWindow(boolean localWindow) { + this(null, localWindow); } - /** - * Set the start position of cursor window - * @param pos - */ - public void setStartPosition(int pos) { - mStartPos = pos; - } - - /** - * Returns the number of rows in this window. - * - * @return the number of rows in this window. - */ - public int getNumRows() { - acquireReference(); - try { - return getNumRows_native(); - } finally { - releaseReference(); + private CursorWindow(Parcel source) { + mStartPos = source.readInt(); + mWindowPtr = nativeCreateFromParcel(source); + if (mWindowPtr == 0) { + throw new CursorWindowAllocationException("Cursor window could not be " + + "created from binder."); } + mCloseGuard.open("close"); } - - private native int getNumRows_native(); - /** - * Set number of Columns - * @param columnNum - * @return true if success - */ - public boolean setNumColumns(int columnNum) { - acquireReference(); + + @Override + protected void finalize() throws Throwable { try { - return setNumColumns_native(columnNum); + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + dispose(); } finally { - releaseReference(); + super.finalize(); } } - - private native boolean setNumColumns_native(int columnNum); - + + private void dispose() { + if (mCloseGuard != null) { + mCloseGuard.close(); + } + if (mWindowPtr != 0) { + recordClosingOfWindow(mWindowPtr); + nativeDispose(mWindowPtr); + mWindowPtr = 0; + } + } + /** - * Allocate a row in cursor window - * @return false if cursor window is out of memory + * Closes the cursor window and frees its underlying resources when all other + * remaining references have been released. */ - public boolean allocRow(){ - acquireReference(); - try { - return allocRow_native(); - } finally { - releaseReference(); - } + public void close() { + releaseReference(); } - - private native boolean allocRow_native(); - + /** - * Free the last row + * Clears out the existing contents of the window, making it safe to reuse + * for new data. + * <p> + * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}), + * and number of columns in the cursor are all reset to zero. + * </p> */ - public void freeLastRow(){ + public void clear() { acquireReference(); try { - freeLastRow_native(); + mStartPos = 0; + nativeClear(mWindowPtr); } finally { releaseReference(); } } - - private native void freeLastRow_native(); /** - * copy byte array to cursor window - * @param value - * @param row - * @param col - * @return false if fail to copy + * Gets the start position of this cursor window. + * <p> + * The start position is the zero-based index of the first row that this window contains + * relative to the entire result set of the {@link Cursor}. + * </p> + * + * @return The zero-based start position. */ - public boolean putBlob(byte[] value, int row, int col) { - acquireReference(); - try { - return putBlob_native(value, row - mStartPos, col); - } finally { - releaseReference(); - } + public int getStartPosition() { + return mStartPos; } - - private native boolean putBlob_native(byte[] value, int row, int col); /** - * Copy String to cursor window - * @param value - * @param row - * @param col - * @return false if fail to copy + * Sets the start position of this cursor window. + * <p> + * The start position is the zero-based index of the first row that this window contains + * relative to the entire result set of the {@link Cursor}. + * </p> + * + * @param pos The new zero-based start position. */ - public boolean putString(String value, int row, int col) { - acquireReference(); - try { - return putString_native(value, row - mStartPos, col); - } finally { - releaseReference(); - } + public void setStartPosition(int pos) { + mStartPos = pos; } - - private native boolean putString_native(String value, int row, int col); - + /** - * Copy integer to cursor window - * @param value - * @param row - * @param col - * @return false if fail to copy + * Gets the number of rows in this window. + * + * @return The number of rows in this cursor window. */ - public boolean putLong(long value, int row, int col) { + public int getNumRows() { acquireReference(); try { - return putLong_native(value, row - mStartPos, col); + return nativeGetNumRows(mWindowPtr); } finally { releaseReference(); } } - - private native boolean putLong_native(long value, int row, int col); - /** - * Copy double to cursor window - * @param value - * @param row - * @param col - * @return false if fail to copy + * Sets the number of columns in this window. + * <p> + * This method must be called before any rows are added to the window, otherwise + * it will fail to set the number of columns if it differs from the current number + * of columns. + * </p> + * + * @param columnNum The new number of columns. + * @return True if successful. */ - public boolean putDouble(double value, int row, int col) { + public boolean setNumColumns(int columnNum) { acquireReference(); try { - return putDouble_native(value, row - mStartPos, col); + return nativeSetNumColumns(mWindowPtr, columnNum); } finally { releaseReference(); } } - - private native boolean putDouble_native(double value, int row, int col); /** - * Set the [row, col] value to NULL - * @param row - * @param col - * @return false if fail to copy + * Allocates a new row at the end of this cursor window. + * + * @return True if successful, false if the cursor window is out of memory. */ - public boolean putNull(int row, int col) { + public boolean allocRow(){ acquireReference(); try { - return putNull_native(row - mStartPos, col); + return nativeAllocRow(mWindowPtr); } finally { releaseReference(); } } - - private native boolean putNull_native(int row, int col); - /** - * Returns {@code true} if given field is {@code NULL}. - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return {@code true} if given field is {@code NULL} - * @deprecated use {@link #getType(int, int)} instead - */ - @Deprecated - public boolean isNull(int row, int col) { - return getType(row, col) == Cursor.FIELD_TYPE_NULL; - } - - /** - * Returns a byte array for the given field. - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return a String value for the given field + * Frees the last row in this cursor window. */ - public byte[] getBlob(int row, int col) { + public void freeLastRow(){ acquireReference(); try { - return getBlob_native(row - mStartPos, col); + nativeFreeLastRow(mWindowPtr); } finally { releaseReference(); } } /** - * Returns the value at (<code>row</code>, <code>col</code>) as a <code>byte</code> array. - * - * <p>If the value is null, then <code>null</code> is returned. If the - * type of column <code>col</code> is a string type, then the result - * is the array of bytes that make up the internal representation of the - * string value. If the type of column <code>col</code> is integral or floating-point, - * then an {@link SQLiteException} is thrown. - */ - private native byte[] getBlob_native(int row, int col); - - /** - * Returns data type of the given column's value. - *<p> - * Returned column types are - * <ul> - * <li>{@link Cursor#FIELD_TYPE_NULL}</li> - * <li>{@link Cursor#FIELD_TYPE_INTEGER}</li> - * <li>{@link Cursor#FIELD_TYPE_FLOAT}</li> - * <li>{@link Cursor#FIELD_TYPE_STRING}</li> - * <li>{@link Cursor#FIELD_TYPE_BLOB}</li> - *</ul> - *</p> + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_NULL}. * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return the value type + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. */ - public int getType(int row, int col) { - acquireReference(); - try { - return getType_native(row - mStartPos, col); - } finally { - releaseReference(); - } + @Deprecated + public boolean isNull(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_NULL; } /** - * Checks if a field contains either a blob or is null. + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}. * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return {@code true} if given field is {@code NULL} or a blob - * @deprecated use {@link #getType(int, int)} instead + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or + * {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated - public boolean isBlob(int row, int col) { - int type = getType(row, col); + public boolean isBlob(int row, int column) { + int type = getType(row, column); return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL; } /** - * Checks if a field contains a long + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_INTEGER}. * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return {@code true} if given field is a long - * @deprecated use {@link #getType(int, int)} instead + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}. + * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated - public boolean isLong(int row, int col) { - return getType(row, col) == Cursor.FIELD_TYPE_INTEGER; + public boolean isLong(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_INTEGER; } /** - * Checks if a field contains a float. + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_FLOAT}. * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return {@code true} if given field is a float - * @deprecated use {@link #getType(int, int)} instead + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}. + * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated - public boolean isFloat(int row, int col) { - return getType(row, col) == Cursor.FIELD_TYPE_FLOAT; + public boolean isFloat(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_FLOAT; } /** - * Checks if a field contains either a String or is null. + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}. * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return {@code true} if given field is {@code NULL} or a String - * @deprecated use {@link #getType(int, int)} instead + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING} + * or {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. */ @Deprecated - public boolean isString(int row, int col) { - int type = getType(row, col); + public boolean isString(int row, int column) { + int type = getType(row, column); return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL; } - private native int getType_native(int row, int col); + /** + * Returns the type of the field at the specified row and column index. + * <p> + * The returned field types are: + * <ul> + * <li>{@link Cursor#FIELD_TYPE_NULL}</li> + * <li>{@link Cursor#FIELD_TYPE_INTEGER}</li> + * <li>{@link Cursor#FIELD_TYPE_FLOAT}</li> + * <li>{@link Cursor#FIELD_TYPE_STRING}</li> + * <li>{@link Cursor#FIELD_TYPE_BLOB}</li> + * </ul> + * </p> + * + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return The field type. + */ + public int getType(int row, int column) { + acquireReference(); + try { + return nativeGetType(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); + } + } /** - * Returns a String for the given field. - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return a String value for the given field + * Gets the value of the field at the specified row and column index as a byte array. + * <p> + * The result is determined as follows: + * <ul> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is <code>null</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result + * is the blob value.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the array of bytes that make up the internal representation of the + * string value.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or + * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.</li> + * </ul> + * </p> + * + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return The value of the field as a byte array. */ - public String getString(int row, int col) { + public byte[] getBlob(int row, int column) { acquireReference(); try { - return getString_native(row - mStartPos, col); + return nativeGetBlob(mWindowPtr, row - mStartPos, column); } finally { releaseReference(); } } - + /** - * Returns the value at (<code>row</code>, <code>col</code>) as a <code>String</code>. + * Gets the value of the field at the specified row and column index as a string. + * <p> + * The result is determined as follows: + * <ul> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is <code>null</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the string value.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is a string representation of the integer in decimal, obtained by formatting the + * value with the <code>printf</code> family of functions using + * format specifier <code>%lld</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is a string representation of the floating-point value in decimal, obtained by + * formatting the value with the <code>printf</code> family of functions using + * format specifier <code>%g</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.</li> + * </ul> + * </p> * - * <p>If the value is null, then <code>null</code> is returned. If the - * type of column <code>col</code> is integral, then the result is the string - * that is obtained by formatting the integer value with the <code>printf</code> - * family of functions using format specifier <code>%lld</code>. If the - * type of column <code>col</code> is floating-point, then the result is the string - * that is obtained by formatting the floating-point value with the - * <code>printf</code> family of functions using format specifier <code>%g</code>. - * If the type of column <code>col</code> is a blob type, then an - * {@link SQLiteException} is thrown. + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return The value of the field as a string. */ - private native String getString_native(int row, int col); + public String getString(int row, int column) { + acquireReference(); + try { + return nativeGetString(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); + } + } /** - * copy the text for the given field in the provided char array. - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @param buffer the CharArrayBuffer to copy the text into, - * If the requested string is larger than the buffer - * a new char buffer will be created to hold the string. and assigne to - * CharArrayBuffer.data + * Copies the text of the field at the specified row and column index into + * a {@link CharArrayBuffer}. + * <p> + * The buffer is populated as follows: + * <ul> + * <li>If the buffer is too small for the value to be copied, then it is + * automatically resized.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer + * is set to an empty string.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer + * is set to the contents of the string.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer + * is set to a string representation of the integer in decimal, obtained by formatting the + * value with the <code>printf</code> family of functions using + * format specifier <code>%lld</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is + * set to a string representation of the floating-point value in decimal, obtained by + * formatting the value with the <code>printf</code> family of functions using + * format specifier <code>%g</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.</li> + * </ul> + * </p> + * + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically + * resized if the requested string is larger than the buffer's current capacity. */ - public void copyStringToBuffer(int row, int col, CharArrayBuffer buffer) { + public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) { if (buffer == null) { throw new IllegalArgumentException("CharArrayBuffer should not be null"); } - if (buffer.data == null) { - buffer.data = new char[64]; - } acquireReference(); try { - char[] newbuf = copyStringToBuffer_native( - row - mStartPos, col, buffer.data.length, buffer); - if (newbuf != null) { - buffer.data = newbuf; - } + nativeCopyStringToBuffer(mWindowPtr, row, column, buffer); } finally { releaseReference(); } } - - private native char[] copyStringToBuffer_native( - int row, int col, int bufferSize, CharArrayBuffer buffer); - + /** - * Returns a long for the given field. - * row is 0 based - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return a long value for the given field + * Gets the value of the field at the specified row and column index as a <code>long</code>. + * <p> + * The result is determined as follows: + * <ul> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is <code>0L</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the value obtained by parsing the string value with <code>strtoll</code>. + * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is the <code>long</code> value.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is the floating-point value converted to a <code>long</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.</li> + * </ul> + * </p> + * + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return The value of the field as a <code>long</code>. */ - public long getLong(int row, int col) { + public long getLong(int row, int column) { acquireReference(); try { - return getLong_native(row - mStartPos, col); + return nativeGetLong(mWindowPtr, row - mStartPos, column); } finally { releaseReference(); } } - - /** - * Returns the value at (<code>row</code>, <code>col</code>) as a <code>long</code>. - * - * <p>If the value is null, then <code>0L</code> is returned. If the - * type of column <code>col</code> is a string type, then the result - * is the <code>long</code> that is obtained by parsing the string value with - * <code>strtoll</code>. If the type of column <code>col</code> is - * floating-point, then the result is the floating-point value casted to a <code>long</code>. - * If the type of column <code>col</code> is a blob type, then an - * {@link SQLiteException} is thrown. - */ - private native long getLong_native(int row, int col); /** - * Returns a double for the given field. - * row is 0 based - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return a double value for the given field + * Gets the value of the field at the specified row and column index as a + * <code>double</code>. + * <p> + * The result is determined as follows: + * <ul> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is <code>0.0</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the value obtained by parsing the string value with <code>strtod</code>. + * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is the integer value converted to a <code>double</code>.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is the <code>double</code> value.</li> + * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.</li> + * </ul> + * </p> + * + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return The value of the field as a <code>double</code>. */ - public double getDouble(int row, int col) { + public double getDouble(int row, int column) { acquireReference(); try { - return getDouble_native(row - mStartPos, col); + return nativeGetDouble(mWindowPtr, row - mStartPos, column); } finally { releaseReference(); } } - + + /** + * Gets the value of the field at the specified row and column index as a + * <code>short</code>. + * <p> + * The result is determined by invoking {@link #getLong} and converting the + * result to <code>short</code>. + * </p> + * + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return The value of the field as a <code>short</code>. + */ + public short getShort(int row, int column) { + return (short) getLong(row, column); + } + /** - * Returns the value at (<code>row</code>, <code>col</code>) as a <code>double</code>. + * Gets the value of the field at the specified row and column index as an + * <code>int</code>. + * <p> + * The result is determined by invoking {@link #getLong} and converting the + * result to <code>int</code>. + * </p> * - * <p>If the value is null, then <code>0.0</code> is returned. If the - * type of column <code>col</code> is a string type, then the result - * is the <code>double</code> that is obtained by parsing the string value with - * <code>strtod</code>. If the type of column <code>col</code> is - * integral, then the result is the integer value casted to a <code>double</code>. - * If the type of column <code>col</code> is a blob type, then an - * {@link SQLiteException} is thrown. + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return The value of the field as an <code>int</code>. */ - private native double getDouble_native(int row, int col); + public int getInt(int row, int column) { + return (int) getLong(row, column); + } /** - * Returns a short for the given field. - * row is 0 based - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return a short value for the given field + * Gets the value of the field at the specified row and column index as a + * <code>float</code>. + * <p> + * The result is determined by invoking {@link #getDouble} and converting the + * result to <code>float</code>. + * </p> + * + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return The value of the field as an <code>float</code>. */ - public short getShort(int row, int col) { + public float getFloat(int row, int column) { + return (float) getDouble(row, column); + } + + /** + * Copies a byte array into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putBlob(byte[] value, int row, int column) { acquireReference(); try { - return (short) getLong_native(row - mStartPos, col); + return nativePutBlob(mWindowPtr, value, row - mStartPos, column); } finally { releaseReference(); } } /** - * Returns an int for the given field. - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return an int value for the given field + * Copies a string into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if successful. */ - public int getInt(int row, int col) { + public boolean putString(String value, int row, int column) { acquireReference(); try { - return (int) getLong_native(row - mStartPos, col); + return nativePutString(mWindowPtr, value, row - mStartPos, column); } finally { releaseReference(); } } - + /** - * Returns a float for the given field. - * row is 0 based - * - * @param row the row to read from, row - getStartPosition() being the actual row in the window - * @param col the column to read from - * @return a float value for the given field + * Puts a long integer into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if successful. */ - public float getFloat(int row, int col) { + public boolean putLong(long value, int row, int column) { acquireReference(); try { - return (float) getDouble_native(row - mStartPos, col); + return nativePutLong(mWindowPtr, value, row - mStartPos, column); } finally { releaseReference(); } - } - + } + /** - * Clears out the existing contents of the window, making it safe to reuse - * for new data. Note that the number of columns in the window may NOT - * change across a call to clear(). + * Puts a double-precision floating point value into the field at the + * specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if successful. */ - public void clear() { + public boolean putDouble(double value, int row, int column) { acquireReference(); try { - mStartPos = 0; - native_clear(); + return nativePutDouble(mWindowPtr, value, row - mStartPos, column); } finally { releaseReference(); } } - /** Clears out the native side of things */ - private native void native_clear(); - /** - * Cleans up the native resources associated with the window. + * Puts a null value into the field at the specified row and column index. + * + * @param row The zero-based row index, relative to the cursor window's + * start position ({@link #getStartPosition()}). + * @param column The zero-based column index. + * @return True if successful. */ - public void close() { - releaseReference(); - } - - private native void close_native(); - - @Override - protected void finalize() { - if (nWindow == 0) { - return; + public boolean putNull(int row, int column) { + acquireReference(); + try { + return nativePutNull(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); } - // due to bugs 3329504, 3502276, cursorwindow sometimes is closed in fialize() - // don't print any warning saying "don't release cursor in finzlize" - // because it is a bug in framework code - NOT an app bug. - recordClosingOfWindow(nWindow); - close_native(); } - + public static final Parcelable.Creator<CursorWindow> CREATOR = new Parcelable.Creator<CursorWindow>() { public CursorWindow createFromParcel(Parcel source) { @@ -591,30 +710,17 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { - dest.writeStrongBinder(native_getBinder()); dest.writeInt(mStartPos); - } + nativeWriteToParcel(mWindowPtr, dest); - private CursorWindow(Parcel source) { - IBinder nativeBinder = source.readStrongBinder(); - mStartPos = source.readInt(); - int rslt = native_init(nativeBinder); - printDebugMsgIfError(rslt); + if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { + releaseReference(); + } } - /** Get the binder for the native side of the window */ - private native IBinder native_getBinder(); - - /** Does the native side initialization for an empty window */ - private native int native_init(int cursorWindowSize, boolean localOnly); - - /** Does the native side initialization with an existing binder from another process */ - private native int native_init(IBinder nativeBinder); - @Override protected void onAllReferencesReleased() { - recordClosingOfWindow(nWindow); - close_native(); + dispose(); } private static final SparseIntArray sWindowToPidMap = new SparseIntArray(); @@ -637,6 +743,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { sWindowToPidMap.delete(window); } } + private String printStats() { StringBuilder buff = new StringBuilder(); int myPid = Process.myPid(); diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java index ba7df68..2e3227d 100644 --- a/core/java/android/database/CursorWindowAllocationException.java +++ b/core/java/android/database/CursorWindowAllocationException.java @@ -18,17 +18,12 @@ package android.database; /** * This exception is thrown when a CursorWindow couldn't be allocated, - * most probably due to memory not being available + * most probably due to memory not being available. + * + * @hide */ -class CursorWindowAllocationException extends java.lang.RuntimeException -{ - public CursorWindowAllocationException() - { - super(); - } - - public CursorWindowAllocationException(String description) - { +public class CursorWindowAllocationException extends RuntimeException { + public CursorWindowAllocationException(String description) { super(description); } } diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java index 3c3bd43..320733e 100644 --- a/core/java/android/database/CursorWrapper.java +++ b/core/java/android/database/CursorWrapper.java @@ -33,7 +33,9 @@ public class CursorWrapper implements Cursor { } /** - * @return the wrapped cursor + * Gets the underlying cursor that is wrapped by this instance. + * + * @return The wrapped cursor. */ public Cursor getWrappedCursor() { return mCursor; diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java index 244c88f..7c96797 100644 --- a/core/java/android/database/IBulkCursor.java +++ b/core/java/android/database/IBulkCursor.java @@ -56,7 +56,7 @@ public interface IBulkCursor extends IInterface { public void close() throws RemoteException; - public int requery(IContentObserver observer, CursorWindow window) throws RemoteException; + public int requery(IContentObserver observer) throws RemoteException; boolean getWantsAllOnMoveCalls() throws RemoteException; diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index ea9346d..a1c36e2 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -18,16 +18,11 @@ package android.database.sqlite; import android.database.AbstractWindowedCursor; import android.database.CursorWindow; -import android.database.DataSetObserver; -import android.os.Handler; -import android.os.Message; -import android.os.Process; import android.os.StrictMode; import android.util.Log; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; /** * A Cursor implementation that exposes results from a query on a @@ -60,140 +55,8 @@ public class SQLiteCursor extends AbstractWindowedCursor { /** Used to find out where a cursor was allocated in case it never got released. */ private final Throwable mStackTrace; - - /** - * mMaxRead is the max items that each cursor window reads - * default to a very high value - */ - private int mMaxRead = Integer.MAX_VALUE; - private int mInitialRead = Integer.MAX_VALUE; - private int mCursorState = 0; - private ReentrantLock mLock = null; - private boolean mPendingData = false; /** - * support for a cursor variant that doesn't always read all results - * initialRead is the initial number of items that cursor window reads - * if query contains more than this number of items, a thread will be - * created and handle the left over items so that caller can show - * results as soon as possible - * @param initialRead initial number of items that cursor read - * @param maxRead leftover items read at maxRead items per time - * @hide - */ - public void setLoadStyle(int initialRead, int maxRead) { - mMaxRead = maxRead; - mInitialRead = initialRead; - mLock = new ReentrantLock(true); - } - - private void queryThreadLock() { - if (mLock != null) { - mLock.lock(); - } - } - - private void queryThreadUnlock() { - if (mLock != null) { - mLock.unlock(); - } - } - - - /** - * @hide - */ - final private class QueryThread implements Runnable { - private final int mThreadState; - QueryThread(int version) { - mThreadState = version; - } - private void sendMessage() { - if (mNotificationHandler != null) { - mNotificationHandler.sendEmptyMessage(1); - mPendingData = false; - } else { - mPendingData = true; - } - - } - public void run() { - // use cached mWindow, to avoid get null mWindow - CursorWindow cw = mWindow; - Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND); - // the cursor's state doesn't change - while (true) { - mLock.lock(); - try { - if (mCursorState != mThreadState) { - break; - } - - int count = getQuery().fillWindow(cw, mMaxRead, mCount); - // return -1 means there is still more data to be retrieved from the resultset - if (count != 0) { - if (count == NO_COUNT){ - mCount += mMaxRead; - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "received -1 from native_fill_window. read " + - mCount + " rows so far"); - } - sendMessage(); - } else { - mCount += count; - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "received all data from native_fill_window. read " + - mCount + " rows."); - } - sendMessage(); - break; - } - } else { - break; - } - } catch (Exception e) { - // end the tread when the cursor is close - break; - } finally { - mLock.unlock(); - } - } - } - } - - /** - * @hide - */ - protected class MainThreadNotificationHandler extends Handler { - public void handleMessage(Message msg) { - notifyDataSetChange(); - } - } - - /** - * @hide - */ - protected MainThreadNotificationHandler mNotificationHandler; - - public void registerDataSetObserver(DataSetObserver observer) { - super.registerDataSetObserver(observer); - if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) && - mNotificationHandler == null) { - queryThreadLock(); - try { - mNotificationHandler = new MainThreadNotificationHandler(); - if (mPendingData) { - notifyDataSetChange(); - mPendingData = false; - } - } finally { - queryThreadUnlock(); - } - } - - } - - /** * Execute a query and provide access to its result set through a Cursor * interface. For a query such as: {@code SELECT name, birth, phone FROM * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, @@ -226,8 +89,6 @@ public class SQLiteCursor extends AbstractWindowedCursor { * @param query the {@link SQLiteQuery} object associated with this cursor object. */ public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { - // The AbstractCursor constructor needs to do some setup. - super(); if (query == null) { throw new IllegalArgumentException("query object cannot be null"); } @@ -293,36 +154,18 @@ public class SQLiteCursor extends AbstractWindowedCursor { return mCount; } - private void fillWindow (int startPos) { - if (mWindow == null) { - // If there isn't a window set already it will only be accessed locally - mWindow = new CursorWindow(true /* the window is local only */); - } else { - mCursorState++; - queryThreadLock(); - try { - mWindow.clear(); - } finally { - queryThreadUnlock(); - } - } + private void fillWindow(int startPos) { + clearOrCreateLocalWindow(getDatabase().getPath()); mWindow.setStartPosition(startPos); - int count = getQuery().fillWindow(mWindow, mInitialRead, 0); - // return -1 means there is still more data to be retrieved from the resultset - if (count == NO_COUNT){ - mCount = startPos + mInitialRead; - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "received -1 from native_fill_window. read " + mCount + " rows so far"); - } - Thread t = new Thread(new QueryThread(mCursorState), "query thread"); - t.start(); - } else if (startPos == 0) { // native_fill_window returns count(*) only for startPos = 0 + int count = getQuery().fillWindow(mWindow); + if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0 if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received count(*) from native_fill_window: " + count); } mCount = count; } else if (mCount <= 0) { - throw new IllegalStateException("count should never be non-zero negative number"); + throw new IllegalStateException("Row count should never be zero or negative " + + "when the start position is non-zero"); } } @@ -364,20 +207,9 @@ public class SQLiteCursor extends AbstractWindowedCursor { return mColumns; } - private void deactivateCommon() { - if (false) Log.v(TAG, "<<< Releasing cursor " + this); - mCursorState = 0; - if (mWindow != null) { - mWindow.close(); - mWindow = null; - } - if (false) Log.v("DatabaseWindow", "closing window in release()"); - } - @Override public void deactivate() { super.deactivate(); - deactivateCommon(); mDriver.cursorDeactivated(); } @@ -385,7 +217,6 @@ public class SQLiteCursor extends AbstractWindowedCursor { public void close() { super.close(); synchronized (this) { - deactivateCommon(); mQuery.close(); mDriver.cursorClosed(); } @@ -439,16 +270,12 @@ public class SQLiteCursor extends AbstractWindowedCursor { // This one will recreate the temp table, and get its count mDriver.cursorRequeried(this); mCount = NO_COUNT; - mCursorState++; - queryThreadLock(); try { mQuery.requery(); } catch (IllegalStateException e) { // for backwards compatibility, just return false Log.w(TAG, "requery() failed " + e.getMessage(), e); return false; - } finally { - queryThreadUnlock(); } } @@ -472,18 +299,9 @@ public class SQLiteCursor extends AbstractWindowedCursor { } @Override - public void setWindow(CursorWindow window) { - if (mWindow != null) { - mCursorState++; - queryThreadLock(); - try { - mWindow.close(); - } finally { - queryThreadUnlock(); - } - mCount = NO_COUNT; - } - mWindow = window; + public void setWindow(CursorWindow window) { + super.setWindow(window); + mCount = NO_COUNT; } /** @@ -521,11 +339,4 @@ public class SQLiteCursor extends AbstractWindowedCursor { super.finalize(); } } - - /** - * this is only for testing purposes. - */ - /* package */ int getMCount() { - return mCount; - } } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 93a6ad3..00d7ce8 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -66,6 +66,7 @@ import java.util.regex.Pattern; */ public class SQLiteDatabase extends SQLiteClosable { private static final String TAG = "SQLiteDatabase"; + private static final boolean ENABLE_DB_SAMPLE = false; // true to enable stats in event log private static final int EVENT_DB_OPERATION = 52000; private static final int EVENT_DB_CORRUPT = 75004; @@ -440,7 +441,9 @@ public class SQLiteDatabase extends SQLiteClosable { } } if (sql != null) { - logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX); + if (ENABLE_DB_SAMPLE) { + logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX); + } } } private static class DatabaseReentrantLock extends ReentrantLock { @@ -726,7 +729,9 @@ public class SQLiteDatabase extends SQLiteClosable { } } // log the transaction time to the Eventlog. - logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL); + if (ENABLE_DB_SAMPLE) { + logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL); + } } else { try { execSQL("ROLLBACK;"); @@ -1593,32 +1598,6 @@ public class SQLiteDatabase extends SQLiteClosable { } /** - * Runs the provided SQL and returns a cursor over the result set. - * The cursor will read an initial set of rows and the return to the caller. - * It will continue to read in batches and send data changed notifications - * when the later batches are ready. - * @param sql the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. The - * values will be bound as Strings. - * @param initialRead set the initial count of items to read from the cursor - * @param maxRead set the count of items to read on each iteration after the first - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * - * This work is incomplete and not fully tested or reviewed, so currently - * hidden. - * @hide - */ - public Cursor rawQuery(String sql, String[] selectionArgs, - int initialRead, int maxRead) { - SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory( - null, sql, selectionArgs, null); - c.setLoadStyle(initialRead, maxRead); - return c; - } - - /** * Convenience method for inserting a row into the database. * * @param table the table to insert the row into @@ -2036,7 +2015,9 @@ public class SQLiteDatabase extends SQLiteClosable { } /* package */ void logTimeStat(String sql, long beginMillis) { - logTimeStat(sql, beginMillis, null); + if (ENABLE_DB_SAMPLE) { + logTimeStat(sql, beginMillis, null); + } } private void logTimeStat(String sql, long beginMillis, String prefix) { diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index dc882d9..7db0914 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -30,6 +30,11 @@ import android.util.Log; public class SQLiteQuery extends SQLiteProgram { private static final String TAG = "SQLiteQuery"; + private static native int nativeFillWindow(int databasePtr, int statementPtr, int windowPtr, + int startPos, int offsetParam); + private static native int nativeColumnCount(int statementPtr); + private static native String nativeColumnName(int statementPtr, int columnIndex); + /** The index of the unbound OFFSET parameter */ private int mOffsetIndex = 0; @@ -68,19 +73,15 @@ public class SQLiteQuery extends SQLiteProgram { * @param window The window to fill into * @return number of total rows in the query */ - /* package */ int fillWindow(CursorWindow window, - int maxRead, int lastPos) { + /* package */ int fillWindow(CursorWindow window) { mDatabase.lock(mSql); long timeStart = SystemClock.uptimeMillis(); try { acquireReference(); try { window.acquireReference(); - // if the start pos is not equal to 0, then most likely window is - // too small for the data set, loading by another thread - // is not safe in this situation. the native code will ignore maxRead - int numRows = native_fill_window(window, window.getStartPosition(), - mOffsetIndex, maxRead, lastPos); + int numRows = nativeFillWindow(nHandle, nStatement, window.mWindowPtr, + window.getStartPosition(), mOffsetIndex); mDatabase.logTimeStat(mSql, timeStart); return numRows; } catch (IllegalStateException e){ @@ -111,7 +112,7 @@ public class SQLiteQuery extends SQLiteProgram { /* package */ int columnCountLocked() { acquireReference(); try { - return native_column_count(); + return nativeColumnCount(nStatement); } finally { releaseReference(); } @@ -127,17 +128,17 @@ public class SQLiteQuery extends SQLiteProgram { /* package */ String columnNameLocked(int columnIndex) { acquireReference(); try { - return native_column_name(columnIndex); + return nativeColumnName(nStatement, columnIndex); } finally { releaseReference(); } } - + @Override public String toString() { return "SQLiteQuery: " + mSql; } - + @Override public void close() { super.close(); @@ -153,11 +154,4 @@ public class SQLiteQuery extends SQLiteProgram { } compileAndbindAllArgs(); } - - private final native int native_fill_window(CursorWindow window, - int startPos, int offsetParam, int maxRead, int lastPos); - - private final native int native_column_count(); - - private final native String native_column_name(int columnIndex); } diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index e40de26..d338764 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -201,7 +201,7 @@ public class Camera { public static final int CAMERA_FACING_FRONT = 1; /** - * The direction that the camera faces to. It should be + * The direction that the camera faces. It should be * CAMERA_FACING_BACK or CAMERA_FACING_FRONT. */ public int facing; @@ -337,7 +337,7 @@ public class Camera { * Camera objects are locked by default unless {@link #unlock()} is * called. Normally {@link #reconnect()} is used instead. * - * <p>Since API level 13, camera is automatically locked for applications in + * <p>Since API level 14, camera is automatically locked for applications in * {@link android.media.MediaRecorder#start()}. Applications can use the * camera (ex: zoom) after recording starts. There is no need to call this * after recording starts or stops. @@ -356,7 +356,7 @@ public class Camera { * which will re-acquire the lock and allow you to continue using the * camera. * - * <p>Since API level 13, camera is automatically locked for applications in + * <p>Since API level 14, camera is automatically locked for applications in * {@link android.media.MediaRecorder#start()}. Applications can use the * camera (ex: zoom) after recording starts. There is no need to call this * after recording starts or stops. @@ -781,7 +781,7 @@ public class Camera { * @see android.hardware.Camera.Parameters#setAutoWhiteBalanceLock(boolean) */ void onAutoFocus(boolean success, Camera camera); - }; + } /** * Starts camera auto-focus and registers a callback function to run when @@ -804,11 +804,17 @@ public class Camera { * {@link android.hardware.Camera.Parameters#FLASH_MODE_OFF}, flash may be * fired during auto-focus, depending on the driver and camera hardware.<p> * - * Auto-exposure lock {@link android.hardware.Camera.Parameters#getAutoExposureLock()} + * <p>Auto-exposure lock {@link android.hardware.Camera.Parameters#getAutoExposureLock()} * and auto-white balance locks {@link android.hardware.Camera.Parameters#getAutoWhiteBalanceLock()} * do not change during and after autofocus. But auto-focus routine may stop * auto-exposure and auto-white balance transiently during focusing. * + * <p>Stopping preview with {@link #stopPreview()}, or triggering still + * image capture with {@link #takePicture(Camera.ShutterCallback, + * Camera.PictureCallback, Camera.PictureCallback)}, will not change the + * the focus position. Applications must call cancelAutoFocus to reset the + * focus.</p> + * * @param cb the callback to run * @see #cancelAutoFocus() * @see android.hardware.Camera.Parameters#setAutoExposureLock(boolean) @@ -1007,6 +1013,10 @@ public class Camera { * camera.setDisplayOrientation(result); * } * </pre> + * + * <p>Starting from API level 14, this method can be called when preview is + * active. + * * @param degrees the angle that the picture will be rotated clockwise. * Valid values are 0, 90, 180, and 270. The starting * position is 0 (landscape). @@ -1055,9 +1065,8 @@ public class Camera { /** * Notify the listener of the detected faces in the preview frame. * - * @param faces the detected faces. The list is sorted by the score. - * The highest score is the first element. - * @param camera the Camera service object + * @param faces The detected faces in a list + * @param camera The {@link Camera} service object */ void onFaceDetection(Face[] faces, Camera camera); } @@ -1105,7 +1114,7 @@ public class Camera { /** * Stops the face detection. * - * @see #startFaceDetection(int) + * @see #startFaceDetection() */ public final void stopFaceDetection() { _stopFaceDetection(); @@ -1116,8 +1125,12 @@ public class Camera { private native final void _stopFaceDetection(); /** - * The information of a face from camera face detection. + * Information about a face identified through camera face detection. + * + * <p>When face detection is used with a camera, the {@link FaceDetectionListener} returns a + * list of face objects for use in focusing and metering.</p> * + * @see FaceDetectionListener */ public static class Face { /** @@ -1132,21 +1145,23 @@ public class Camera { * the field of view. For example, suppose the size of the viewfinder UI * is 800x480. The rect passed from the driver is (-1000, -1000, 0, 0). * The corresponding viewfinder rect should be (0, 0, 400, 240). The - * width and height of the rect will not be 0 or negative. + * width and height of the rect will not be 0 or negative. The + * coordinates can be smaller than -1000 or bigger than 1000. But at + * least one vertex will be within (-1000, -1000) and (1000, 1000). * * <p>The direction is relative to the sensor orientation, that is, what * the sensor sees. The direction is not affected by the rotation or * mirroring of {@link #setDisplayOrientation(int)}.</p> * - * @see #startFaceDetection(int) + * @see #startFaceDetection() */ public Rect rect; /** - * The confidence level of the face. The range is 1 to 100. 100 is the + * The confidence level for the detection of the face. The range is 1 to 100. 100 is the * highest confidence. * - * @see #startFaceDetection(int) + * @see #startFaceDetection() */ public int score; @@ -1456,6 +1471,8 @@ public class Camera { private static final String KEY_MAX_NUM_DETECTED_FACES_SW = "max-num-detected-faces-sw"; private static final String KEY_RECORDING_HINT = "recording-hint"; private static final String KEY_VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported"; + private static final String KEY_VIDEO_STABILIZATION = "video-stabilization"; + private static final String KEY_VIDEO_STABILIZATION_SUPPORTED = "video-stabilization-supported"; // Parameter key suffix for supported values. private static final String SUPPORTED_VALUES_SUFFIX = "-values"; @@ -1643,9 +1660,18 @@ public class Camera { * call {@link #takePicture(Camera.ShutterCallback, * Camera.PictureCallback, Camera.PictureCallback)} in this mode but the * subject may not be in focus. Auto focus starts when the parameter is - * set. Applications should not call {@link - * #autoFocus(AutoFocusCallback)} in this mode. To stop continuous - * focus, applications should change the focus mode to other modes. + * set. + * + * <p>Since API level 14, applications can call {@link + * #autoFocus(AutoFocusCallback)} in this mode. The focus callback will + * immediately return with a boolean that indicates whether the focus is + * sharp or not. The focus position is locked after autoFocus call. If + * applications want to resume the continuous focus, cancelAutoFocus + * must be called. Restarting the preview will not resume the continuous + * autofocus. To stop continuous focus, applications should change the + * focus mode to other modes. + * + * @see #FOCUS_MODE_CONTINUOUS_PICTURE */ public static final String FOCUS_MODE_CONTINUOUS_VIDEO = "continuous-video"; @@ -1653,13 +1679,17 @@ public class Camera { * Continuous auto focus mode intended for taking pictures. The camera * continuously tries to focus. The speed of focus change is more * aggressive than {@link #FOCUS_MODE_CONTINUOUS_VIDEO}. Auto focus - * starts when the parameter is set. If applications call {@link - * #autoFocus(AutoFocusCallback)} in this mode, the focus callback will - * immediately return with a boolean that indicates whether the focus is - * sharp or not. The apps can then decide if they want to take a picture - * immediately or to change the focus mode to auto, and run a full - * autofocus cycle. To stop continuous focus, applications should change - * the focus mode to other modes. + * starts when the parameter is set. + * + * <p>If applications call {@link #autoFocus(AutoFocusCallback)} in this + * mode, the focus callback will immediately return with a boolean that + * indicates whether the focus is sharp or not. The apps can then decide + * if they want to take a picture immediately or to change the focus + * mode to auto, and run a full autofocus cycle. The focus position is + * locked after autoFocus call. If applications want to resume the + * continuous focus, cancelAutoFocus must be called. Restarting the + * preview will not resume the continuous autofocus. To stop continuous + * focus, applications should change the focus mode to other modes. * * @see #FOCUS_MODE_CONTINUOUS_VIDEO */ @@ -2435,7 +2465,7 @@ public class Camera { * * @param value new white balance. * @see #getWhiteBalance() - * @see #setAutoWhiteBalanceLock() + * @see #setAutoWhiteBalanceLock(boolean) */ public void setWhiteBalance(String value) { set(KEY_WHITE_BALANCE, value); @@ -3051,8 +3081,9 @@ public class Camera { * when using zoom.</p> * * <p>Focus area only has effect if the current focus mode is - * {@link #FOCUS_MODE_AUTO}, {@link #FOCUS_MODE_MACRO}, or - * {@link #FOCUS_MODE_CONTINUOUS_VIDEO}.</p> + * {@link #FOCUS_MODE_AUTO}, {@link #FOCUS_MODE_MACRO}, + * {@link #FOCUS_MODE_CONTINUOUS_VIDEO}, or + * {@link #FOCUS_MODE_CONTINUOUS_PICTURE}.</p> * * @return a list of current focus areas */ @@ -3144,7 +3175,7 @@ public class Camera { * supported. * * @return the maximum number of detected face supported by the camera. - * @see #startFaceDetection(int) + * @see #startFaceDetection() */ public int getMaxNumDetectedFaces() { return getInt(KEY_MAX_NUM_DETECTED_FACES_HW, 0); @@ -3200,6 +3231,59 @@ public class Camera { return TRUE.equals(str); } + /** + * <p>Enables and disables video stabilization. Use + * {@link #isVideoStabilizationSupported} to determine if calling this + * method is valid.</p> + * + * <p>Video stabilization reduces the shaking due to the motion of the + * camera in both the preview stream and in recorded videos, including + * data received from the preview callback. It does not reduce motion + * blur in images captured with + * {@link Camera#takePicture takePicture}.</p> + * + * <p>Video stabilization can be enabled and disabled while preview or + * recording is active, but toggling it may cause a jump in the video + * stream that may be undesirable in a recorded video.</p> + * + * @param toggle Set to true to enable video stabilization, and false to + * disable video stabilization. + * @see #isVideoStabilizationSupported() + * @see #getVideoStabilization() + * @hide + */ + public void setVideoStabilization(boolean toggle) { + set(KEY_VIDEO_STABILIZATION, toggle ? TRUE : FALSE); + } + + /** + * Get the current state of video stabilization. See + * {@link #setVideoStabilization} for details of video stabilization. + * + * @return true if video stabilization is enabled + * @see #isVideoStabilizationSupported() + * @see #setVideoStabilization(boolean) + * @hide + */ + public boolean getVideoStabilization() { + String str = get(KEY_VIDEO_STABILIZATION); + return TRUE.equals(str); + } + + /** + * Returns true if video stabilization is supported. See + * {@link #setVideoStabilization} for details of video stabilization. + * + * @return true if video stabilization is supported + * @see #setVideoStabilization(boolean) + * @see #getVideoStabilization() + * @hide + */ + public boolean isVideoStabilizationSupported() { + String str = get(KEY_VIDEO_STABILIZATION_SUPPORTED); + return TRUE.equals(str); + } + // Splits a comma delimited string to an ArrayList of String. // Return null if the passing string is null or the size is 0. private ArrayList<String> split(String str) { diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 1119c1e..5343e2a 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -248,6 +248,8 @@ public class KeyboardView extends View implements View.OnClickListener { private AccessibilityManager mAccessibilityManager; /** The audio manager for accessibility support */ private AudioManager mAudioManager; + /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */ + private boolean mHeadsetRequiredToHearPasswordsAnnounced; Handler mHandler = new Handler() { @Override @@ -852,13 +854,15 @@ public class KeyboardView extends View implements View.OnClickListener { Key oldKey = keys[oldKeyIndex]; oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY); invalidateKey(oldKeyIndex); - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT, oldKey.codes[0]); + sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT, + oldKey.codes[0]); } if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) { Key newKey = keys[mCurrentKeyIndex]; newKey.onPressed(); invalidateKey(mCurrentKeyIndex); - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, newKey.codes[0]); + sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, + newKey.codes[0]); } } // If key changed and preview is on ... @@ -958,13 +962,13 @@ public class KeyboardView extends View implements View.OnClickListener { mPreviewText.setVisibility(VISIBLE); } - private void sendAccessibilityEvent(int eventType, int code) { + private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) { if (mAccessibilityManager.isEnabled()) { AccessibilityEvent event = AccessibilityEvent.obtain(eventType); onInitializeAccessibilityEvent(event); + String text = null; // Add text only if headset is used to avoid leaking passwords. if (mAudioManager.isBluetoothA2dpOn() || mAudioManager.isWiredHeadsetOn()) { - String text = null; switch (code) { case Keyboard.KEYCODE_ALT: text = mContext.getString(R.string.keyboardview_keycode_alt); @@ -990,11 +994,17 @@ public class KeyboardView extends View implements View.OnClickListener { default: text = String.valueOf((char) code); } - event.getText().add(text); + } else if (!mHeadsetRequiredToHearPasswordsAnnounced) { + // We want the waring for required head set to be send with both the + // hover enter and hover exit event, so set the flag after the exit. + if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { + mHeadsetRequiredToHearPasswordsAnnounced = true; + } + text = mContext.getString(R.string.keyboard_headset_required_to_hear_password); } else { - event.getText().add(mContext.getString( - R.string.keyboard_headset_required_to_hear_password)); + text = mContext.getString(R.string.keyboard_password_character_no_headset); } + event.getText().add(text); mAccessibilityManager.sendAccessibilityEvent(event); } } @@ -1134,15 +1144,13 @@ public class KeyboardView extends View implements View.OnClickListener { } @Override - protected boolean dispatchHoverEvent(MotionEvent event) { + public boolean onHoverEvent(MotionEvent event) { // If touch exploring is enabled we ignore touch events and transform // the stream of hover events as touch events. This allows one consistent // event stream to drive the keyboard since during touch exploring the // first touch generates only hover events and tapping on the same // location generates hover and touch events. - if (mAccessibilityManager.isEnabled() - && mAccessibilityManager.isTouchExplorationEnabled() - && event.getPointerCount() == 1) { + if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) { final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_HOVER_ENTER: @@ -1156,9 +1164,9 @@ public class KeyboardView extends View implements View.OnClickListener { break; } onTouchEventInternal(event); - return true; + event.setAction(action); } - return super.dispatchHoverEvent(event); + return super.onHoverEvent(event); } @Override @@ -1168,8 +1176,7 @@ public class KeyboardView extends View implements View.OnClickListener { // event stream to drive the keyboard since during touch exploring the // first touch generates only hover events and tapping on the same // location generates hover and touch events. - if (mAccessibilityManager.isEnabled() - && mAccessibilityManager.isTouchExplorationEnabled()) { + if (mAccessibilityManager.isTouchExplorationEnabled()) { return true; } return onTouchEventInternal(event); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 530122c..0052dd0 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -515,7 +515,7 @@ public class ConnectivityManager { * All applications that have background services that use the network * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}. * <p> - * As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of + * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of * background data depends on several combined factors, and this method will * always return {@code true}. Instead, when background data is unavailable, * {@link #getActiveNetworkInfo()} will now appear disconnected. @@ -548,6 +548,8 @@ public class ConnectivityManager { * Return quota status for the current active network, or {@code null} if no * network is active. Quota status can change rapidly, so these values * shouldn't be cached. + * + * @hide */ public NetworkQuotaInfo getActiveNetworkQuotaInfo() { try { diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index 79c9395..fc6a44a 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -336,17 +336,17 @@ public class DhcpStateMachine extends StateMachine { DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal(); if (dhcpAction == DhcpAction.START) { - Log.d(TAG, "DHCP request on " + mInterfaceName); + if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName); success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal); mDhcpInfo = dhcpInfoInternal; } else if (dhcpAction == DhcpAction.RENEW) { - Log.d(TAG, "DHCP renewal on " + mInterfaceName); + if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName); success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal); dhcpInfoInternal.updateFromDhcpRequest(mDhcpInfo); } if (success) { - Log.d(TAG, "DHCP succeeded on " + mInterfaceName); + if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName); long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion //Sanity check for renewal @@ -366,7 +366,7 @@ public class DhcpStateMachine extends StateMachine { mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal) .sendToTarget(); } else { - Log.d(TAG, "DHCP failed on " + mInterfaceName + ": " + + Log.e(TAG, "DHCP failed on " + mInterfaceName + ": " + NetworkUtils.getDhcpError()); NetworkUtils.stopDhcp(mInterfaceName); mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0) diff --git a/core/java/android/net/DnsPinger.java b/core/java/android/net/DnsPinger.java index 3291e6b..11acabe 100644 --- a/core/java/android/net/DnsPinger.java +++ b/core/java/android/net/DnsPinger.java @@ -22,7 +22,7 @@ import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.provider.Settings; -import android.util.Slog; +import android.util.Log; import com.android.internal.util.Protocol; @@ -51,7 +51,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @hide */ public final class DnsPinger extends Handler { - private static final boolean V = true; + private static final boolean DBG = false; private static final int RECEIVE_POLL_INTERVAL_MS = 200; private static final int DNS_PORT = 53; @@ -154,7 +154,7 @@ public final class DnsPinger extends Handler { newActivePing.socket.setNetworkInterface(NetworkInterface.getByName( getCurrentLinkProperties().getInterfaceName())); } catch (Exception e) { - Slog.w(TAG,"sendDnsPing::Error binding to socket", e); + loge("sendDnsPing::Error binding to socket " + e); } newActivePing.packetId = (short) sRandom.nextInt(); @@ -165,8 +165,8 @@ public final class DnsPinger extends Handler { // Send the DNS query DatagramPacket packet = new DatagramPacket(buf, buf.length, dnsAddress, DNS_PORT); - if (V) { - Slog.v(TAG, "Sending a ping " + newActivePing.internalId + + if (DBG) { + log("Sending a ping " + newActivePing.internalId + " to " + dnsAddress.getHostAddress() + " with packetId " + newActivePing.packetId + "."); } @@ -196,15 +196,15 @@ public final class DnsPinger extends Handler { curPing.result = (int) (SystemClock.elapsedRealtime() - curPing.start); } else { - if (V) { - Slog.v(TAG, "response ID didn't match, ignoring packet"); + if (DBG) { + log("response ID didn't match, ignoring packet"); } } } catch (SocketTimeoutException e) { // A timeout here doesn't mean anything - squelsh this exception } catch (Exception e) { - if (V) { - Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e); + if (DBG) { + log("DnsPinger.pingDns got socket exception: " + e); } curPing.result = SOCKET_EXCEPTION; } @@ -244,13 +244,13 @@ public final class DnsPinger extends Handler { public List<InetAddress> getDnsList() { LinkProperties curLinkProps = getCurrentLinkProperties(); if (curLinkProps == null) { - Slog.e(TAG, "getCurLinkProperties:: LP for type" + mConnectionType + " is null!"); + loge("getCurLinkProperties:: LP for type" + mConnectionType + " is null!"); return mDefaultDns; } Collection<InetAddress> dnses = curLinkProps.getDnses(); if (dnses == null || dnses.size() == 0) { - Slog.v(TAG, "getDns::LinkProps has null dns - returning default"); + loge("getDns::LinkProps has null dns - returning default"); return mDefaultDns; } @@ -277,8 +277,8 @@ public final class DnsPinger extends Handler { } private void sendResponse(int internalId, int externalId, int responseVal) { - if(V) { - Slog.d(TAG, "Responding to packet " + internalId + + if(DBG) { + log("Responding to packet " + internalId + " externalId " + externalId + " and val " + responseVal); } @@ -304,7 +304,7 @@ public final class DnsPinger extends Handler { try { return NetworkUtils.numericToInetAddress(dns); } catch (IllegalArgumentException e) { - Slog.w(TAG, "getDefaultDns::malformed default dns address"); + loge("getDefaultDns::malformed default dns address"); return null; } } @@ -323,4 +323,12 @@ public final class DnsPinger extends Handler { 0, 1, // QTYPE, set to 1 = A (host address) 0, 1 // QCLASS, set to 1 = IN (internet) }; + + private void log(String s) { + Log.d(TAG, s); + } + + private void loge(String s) { + Log.e(TAG, s); + } } diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java index aaad8a1..1b24f0c 100644 --- a/core/java/android/net/NetworkPolicy.java +++ b/core/java/android/net/NetworkPolicy.java @@ -40,6 +40,8 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { public long limitBytes; public long lastSnooze; + private static final long DEFAULT_MTU = 1500; + public NetworkPolicy(NetworkTemplate template, int cycleDay, long warningBytes, long limitBytes, long lastSnooze) { this.template = checkNotNull(template, "missing NetworkTemplate"); @@ -71,6 +73,17 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { return 0; } + /** + * Test if given measurement is near enough to {@link #limitBytes} to be + * considered over-limit. + */ + public boolean isOverLimit(long totalBytes) { + // over-estimate, since kernel will trigger limit once first packet + // trips over limit. + totalBytes += 2 * DEFAULT_MTU; + return limitBytes != LIMIT_DISABLED && totalBytes >= limitBytes; + } + /** {@inheritDoc} */ public int compareTo(NetworkPolicy another) { if (another == null || another.limitBytes == LIMIT_DISABLED) { diff --git a/core/java/android/net/NetworkQuotaInfo.java b/core/java/android/net/NetworkQuotaInfo.java index b85f925..6535256 100644 --- a/core/java/android/net/NetworkQuotaInfo.java +++ b/core/java/android/net/NetworkQuotaInfo.java @@ -21,6 +21,8 @@ import android.os.Parcelable; /** * Information about quota status on a specific network. + * + * @hide */ public class NetworkQuotaInfo implements Parcelable { private final long mEstimatedBytes; diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index f3be39c..69ac1e7 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -229,6 +229,14 @@ public class NetworkStats implements Parcelable { return elapsedRealtime; } + /** + * Return age of this {@link NetworkStats} object with respect to + * {@link SystemClock#elapsedRealtime()}. + */ + public long getElapsedRealtimeAge() { + return SystemClock.elapsedRealtime() - elapsedRealtime; + } + public int size() { return size; } @@ -272,6 +280,17 @@ public class NetworkStats implements Parcelable { } /** + * Combine all values from another {@link NetworkStats} into this object. + */ + public void combineAllValues(NetworkStats another) { + NetworkStats.Entry entry = null; + for (int i = 0; i < another.size; i++) { + entry = another.getValues(i, entry); + combineValues(entry); + } + } + + /** * Find first stats index that matches the requested parameters. */ public int findIndex(String iface, int uid, int set, int tag) { @@ -343,26 +362,59 @@ public class NetworkStats implements Parcelable { * Return total of all fields represented by this snapshot object. */ public Entry getTotal(Entry recycle) { + return getTotal(recycle, null, UID_ALL); + } + + /** + * Return total of all fields represented by this snapshot object matching + * the requested {@link #uid}. + */ + public Entry getTotal(Entry recycle, int limitUid) { + return getTotal(recycle, null, limitUid); + } + + /** + * Return total of all fields represented by this snapshot object matching + * the requested {@link #iface}. + */ + public Entry getTotal(Entry recycle, HashSet<String> limitIface) { + return getTotal(recycle, limitIface, UID_ALL); + } + + /** + * Return total of all fields represented by this snapshot object matching + * the requested {@link #iface} and {@link #uid}. + * + * @param limitIface Set of {@link #iface} to include in total; or {@code + * null} to include all ifaces. + */ + private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) { final Entry entry = recycle != null ? recycle : new Entry(); entry.iface = IFACE_ALL; - entry.uid = UID_ALL; + entry.uid = limitUid; entry.set = SET_ALL; entry.tag = TAG_NONE; entry.rxBytes = 0; entry.rxPackets = 0; entry.txBytes = 0; entry.txPackets = 0; + entry.operations = 0; for (int i = 0; i < size; i++) { - // skip specific tags, since already counted in TAG_NONE - if (tag[i] != TAG_NONE) continue; - - entry.rxBytes += rxBytes[i]; - entry.rxPackets += rxPackets[i]; - entry.txBytes += txBytes[i]; - entry.txPackets += txPackets[i]; - entry.operations += operations[i]; + final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]); + final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i])); + + if (matchesUid && matchesIface) { + // skip specific tags, since already counted in TAG_NONE + if (tag[i] != TAG_NONE) continue; + + entry.rxBytes += rxBytes[i]; + entry.rxPackets += rxPackets[i]; + entry.txBytes += txBytes[i]; + entry.txPackets += txPackets[i]; + entry.operations += operations[i]; + } } return entry; } @@ -456,6 +508,62 @@ public class NetworkStats implements Parcelable { return result; } + /** + * Return total statistics grouped by {@link #iface}; doesn't mutate the + * original structure. + */ + public NetworkStats groupedByIface() { + final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); + + final Entry entry = new Entry(); + entry.uid = UID_ALL; + entry.set = SET_ALL; + entry.tag = TAG_NONE; + entry.operations = 0L; + + for (int i = 0; i < size; i++) { + // skip specific tags, since already counted in TAG_NONE + if (tag[i] != TAG_NONE) continue; + + entry.iface = iface[i]; + entry.rxBytes = rxBytes[i]; + entry.rxPackets = rxPackets[i]; + entry.txBytes = txBytes[i]; + entry.txPackets = txPackets[i]; + stats.combineValues(entry); + } + + return stats; + } + + /** + * Return total statistics grouped by {@link #uid}; doesn't mutate the + * original structure. + */ + public NetworkStats groupedByUid() { + final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); + + final Entry entry = new Entry(); + entry.iface = IFACE_ALL; + entry.set = SET_ALL; + entry.tag = TAG_NONE; + + for (int i = 0; i < size; i++) { + // skip specific tags, since already counted in TAG_NONE + if (tag[i] != TAG_NONE) continue; + + entry.uid = uid[i]; + entry.rxBytes = rxBytes[i]; + entry.rxPackets = rxPackets[i]; + entry.txBytes = txBytes[i]; + entry.txPackets = txPackets[i]; + entry.operations = operations[i]; + stats.combineValues(entry); + } + + return stats; + } + public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index a6635be..8c01331 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -478,17 +478,34 @@ public class NetworkStatsHistory implements Parcelable { * @deprecated only for temporary testing */ @Deprecated + public void generateRandom(long start, long end, long bytes) { + final Random r = new Random(); + + final float fractionRx = r.nextFloat(); + final long rxBytes = (long) (bytes * fractionRx); + final long txBytes = (long) (bytes * (1 - fractionRx)); + + final long rxPackets = rxBytes / 1024; + final long txPackets = txBytes / 1024; + final long operations = rxBytes / 2048; + + generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r); + } + + /** + * @deprecated only for temporary testing + */ + @Deprecated public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, - long txPackets, long operations) { + long txPackets, long operations, Random r) { ensureBuckets(start, end); final NetworkStats.Entry entry = new NetworkStats.Entry( IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); - final Random r = new Random(); while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 || operations > 32) { final long curStart = randomLong(r, start, end); - final long curEnd = randomLong(r, curStart, end); + final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2); entry.rxBytes = randomLong(r, 0, rxBytes); entry.rxPackets = randomLong(r, 0, rxPackets); @@ -506,7 +523,7 @@ public class NetworkStatsHistory implements Parcelable { } } - private static long randomLong(Random r, long start, long end) { + public static long randomLong(Random r, long start, long end) { return (long) (start + (r.nextFloat() * (end - start))); } diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java index 08c6692..863304c 100644 --- a/core/java/android/net/http/SslError.java +++ b/core/java/android/net/http/SslError.java @@ -19,7 +19,8 @@ package android.net.http; import java.security.cert.X509Certificate; /** - * One or more individual SSL errors and the associated SSL certificate + * This class represents a set of one or more SSL errors and the associated SSL + * certificate. */ public class SslError { @@ -48,16 +49,17 @@ public class SslError { */ public static final int SSL_DATE_INVALID = 4; /** - * The certificate is invalid + * A generic error occurred */ public static final int SSL_INVALID = 5; /** - * The number of different SSL errors (update if you add a new SSL error!!!) + * The number of different SSL errors. * @deprecated This constant is not necessary for using the SslError API and * can change from release to release. */ + // Update if you add a new SSL error!!! @Deprecated public static final int SSL_MAX_ERROR = 6; @@ -78,56 +80,56 @@ public class SslError { final String mUrl; /** - * Creates a new SSL error set object + * Creates a new SslError object using the supplied error and certificate. + * The URL will be set to the empty string. * @param error The SSL error * @param certificate The associated SSL certificate * @deprecated Use {@link #SslError(int, SslCertificate, String)} */ @Deprecated public SslError(int error, SslCertificate certificate) { - addError(error); - if (certificate == null) { - throw new NullPointerException("certificate is null."); - } - mCertificate = certificate; - mUrl = ""; + this(error, certificate, ""); } /** - * Creates a new SSL error set object + * Creates a new SslError object using the supplied error and certificate. + * The URL will be set to the empty string. * @param error The SSL error * @param certificate The associated SSL certificate * @deprecated Use {@link #SslError(int, X509Certificate, String)} */ @Deprecated public SslError(int error, X509Certificate certificate) { - addError(error); - if (certificate == null) { - throw new NullPointerException("certificate is null."); - } - mCertificate = new SslCertificate(certificate); - mUrl = ""; + this(error, certificate, ""); } /** - * Creates a new SSL error set object + * Creates a new SslError object using the supplied error, certificate and + * URL. * @param error The SSL error * @param certificate The associated SSL certificate - * @param url The associated URL. + * @param url The associated URL */ public SslError(int error, SslCertificate certificate, String url) { + assert certificate != null; + assert url != null; addError(error); - if (certificate == null) { - throw new NullPointerException("certificate is null."); - } mCertificate = certificate; - if (url == null) { - throw new NullPointerException("url is null."); - } mUrl = url; } /** + * Creates a new SslError object using the supplied error, certificate and + * URL. + * @param error The SSL error + * @param certificate The associated SSL certificate + * @param url The associated URL + */ + public SslError(int error, X509Certificate certificate, String url) { + this(error, new SslCertificate(certificate), url); + } + + /** * Creates an SslError object from a chromium error code. * @param error The chromium error code * @param certificate The associated SSL certificate @@ -138,56 +140,38 @@ public class SslError { int error, SslCertificate cert, String url) { // The chromium error codes are in: // external/chromium/net/base/net_error_list.h - if (error > -200 || error < -299) { - throw new NullPointerException("Not a valid chromium SSL error code."); - } + assert (error >= -299 && error <= -200); if (error == -200) return new SslError(SSL_IDMISMATCH, cert, url); if (error == -201) return new SslError(SSL_DATE_INVALID, cert, url); if (error == -202) return new SslError(SSL_UNTRUSTED, cert, url); - // Map all other errors to SSL_INVALID + // Map all other codes to SSL_INVALID. return new SslError(SSL_INVALID, cert, url); } /** - * Creates a new SSL error set object - * @param error The SSL error - * @param certificate The associated SSL certificate - * @param url The associated URL. - */ - public SslError(int error, X509Certificate certificate, String url) { - addError(error); - if (certificate == null) { - throw new NullPointerException("certificate is null."); - } - mCertificate = new SslCertificate(certificate); - if (url == null) { - throw new NullPointerException("url is null."); - } - mUrl = url; - } - - /** - * @return The SSL certificate associated with the error set, non-null. + * Gets the SSL certificate associated with this object. + * @return The SSL certificate, non-null. */ public SslCertificate getCertificate() { return mCertificate; } /** - * @return The URL associated with the error set, non-null. - * "" if one of the deprecated constructors is used. + * Gets the URL associated with this object. + * @return The URL, non-null. */ public String getUrl() { return mUrl; } /** - * Adds the SSL error to the error set + * Adds the supplied SSL error to the set. * @param error The SSL error to add - * @return True iff the error being added is a known SSL error + * @return True if the error being added is a known SSL error, otherwise + * false. */ public boolean addError(int error) { boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR); @@ -199,8 +183,9 @@ public class SslError { } /** - * @param error The SSL error to check - * @return True iff the set includes the error + * Determines whether this object includes the supplied error. + * @param error The SSL error to check for + * @return True if this object includes the error, otherwise false. */ public boolean hasError(int error) { boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR); @@ -212,7 +197,9 @@ public class SslError { } /** - * @return The primary, most severe, SSL error in the set + * Gets the most severe SSL error in this object's set of errors. + * Returns -1 if the set is empty. + * @return The most severe SSL error, or -1 if the set is empty. */ public int getPrimaryError() { if (mErrors != 0) { @@ -222,18 +209,20 @@ public class SslError { return error; } } + // mErrors should never be set to an invalid value. + assert false; } - return 0; + return -1; } /** - * @return A String representation of this SSL error object - * (used mostly for debugging). + * Returns a string representation of this object. + * @return A String representation of this object. */ public String toString() { return "primary error: " + getPrimaryError() + - " certificate: " + getCertificate() + - " on URL: " + getUrl(); + " certificate: " + getCertificate() + + " on URL: " + getUrl(); } } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index fe0106d..33310df 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -768,61 +768,6 @@ public final class NfcAdapter { } /** - * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated - * @deprecated use {@link CreateNdefMessageCallback} or {@link OnNdefPushCompleteCallback} - * @hide - */ - @Deprecated - public interface NdefPushCallback { - /** - * @deprecated use {@link CreateNdefMessageCallback} instead - */ - @Deprecated - NdefMessage createMessage(); - /** - * @deprecated use{@link OnNdefPushCompleteCallback} instead - */ - @Deprecated - void onMessagePushed(); - } - - /** - * TODO: Remove this - * Converts new callbacks to old callbacks. - */ - static final class LegacyCallbackWrapper implements CreateNdefMessageCallback, - OnNdefPushCompleteCallback { - final NdefPushCallback mLegacyCallback; - LegacyCallbackWrapper(NdefPushCallback legacyCallback) { - mLegacyCallback = legacyCallback; - } - @Override - public void onNdefPushComplete(NfcEvent event) { - mLegacyCallback.onMessagePushed(); - } - @Override - public NdefMessage createNdefMessage(NfcEvent event) { - return mLegacyCallback.createMessage(); - } - } - - /** - * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated - * @deprecated use {@link #setNdefPushMessageCallback} instead - * @hide - */ - @Deprecated - public void enableForegroundNdefPush(Activity activity, final NdefPushCallback callback) { - if (activity == null || callback == null) { - throw new NullPointerException(); - } - enforceResumed(activity); - LegacyCallbackWrapper callbackWrapper = new LegacyCallbackWrapper(callback); - mNfcActivityManager.setNdefPushMessageCallback(activity, callbackWrapper); - mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callbackWrapper); - } - - /** * Enable NDEF Push feature. * <p>This API is for the Settings application. * @hide diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 64bba54..9dea4c4 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -42,6 +42,13 @@ import java.util.concurrent.atomic.AtomicInteger; * and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>, * <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p> * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about using tasks and threads, read the + * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and + * Threads</a> developer guide.</p> + * </div> + * * <h2>Usage</h2> * <p>AsyncTask must be subclassed to be used. The subclass will override at least * one method ({@link #doInBackground}), and most often will override a diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 1d8ea12..5faab36 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -282,6 +282,10 @@ public class Build { * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> * <ul> + * <li> For devices without a dedicated menu key, the software compatibility + * menu key will not be shown even on phones. By targeting Ice Cream Sandwich + * or later, your UI must always have its own menu UI affordance if needed, + * on both tablets and phones. The ActionBar will take care of this for you. * <li> 2d drawing hardware acceleration is now turned on by default. * You can use * {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated} @@ -296,6 +300,12 @@ public class Build { * will not change character within the same platform version. Applications * that wish to blend in with the device should use a theme from the * {@link android.R.style#Theme_DeviceDefault} family. + * <li> Managed cursors can now throw an exception if you directly close + * the cursor yourself without stopping the management of it; previously failures + * would be silently ignored. + * <li> The fadingEdge attribute on views will be ignored (fading edges is no + * longer a standard part of the UI). A new requiresFadingEdge attribute allows + * applications to still force fading edges on for special cases. * </ul> */ public static final int ICE_CREAM_SANDWICH = 14; diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index c288f8a..28206b7 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -54,6 +54,7 @@ public final class Bundle implements Parcelable, Cloneable { private boolean mHasFds = false; private boolean mFdsKnown = true; + private boolean mAllowFds = true; /** * The ClassLoader used when unparcelling data from mParcelledData. @@ -186,7 +187,14 @@ public final class Bundle implements Parcelable, Cloneable { public ClassLoader getClassLoader() { return mClassLoader; } - + + /** @hide */ + public boolean setAllowFds(boolean allowFds) { + boolean orig = mAllowFds; + mAllowFds = allowFds; + return orig; + } + /** * Clones the current Bundle. The internal map is cloned, but the keys and * values to which it refers are copied by reference. @@ -1589,24 +1597,29 @@ public final class Bundle implements Parcelable, Cloneable { * @param parcel The parcel to copy this bundle to. */ public void writeToParcel(Parcel parcel, int flags) { - if (mParcelledData != null) { - int length = mParcelledData.dataSize(); - parcel.writeInt(length); - parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L' - parcel.appendFrom(mParcelledData, 0, length); - } else { - parcel.writeInt(-1); // dummy, will hold length - parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L' - - int oldPos = parcel.dataPosition(); - parcel.writeMapInternal(mMap); - int newPos = parcel.dataPosition(); - - // Backpatch length - parcel.setDataPosition(oldPos - 8); - int length = newPos - oldPos; - parcel.writeInt(length); - parcel.setDataPosition(newPos); + final boolean oldAllowFds = parcel.pushAllowFds(mAllowFds); + try { + if (mParcelledData != null) { + int length = mParcelledData.dataSize(); + parcel.writeInt(length); + parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L' + parcel.appendFrom(mParcelledData, 0, length); + } else { + parcel.writeInt(-1); // dummy, will hold length + parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L' + + int oldPos = parcel.dataPosition(); + parcel.writeMapInternal(mMap); + int newPos = parcel.dataPosition(); + + // Backpatch length + parcel.setDataPosition(oldPos - 8); + int length = newPos - oldPos; + parcel.writeInt(length); + parcel.setDataPosition(newPos); + } + } finally { + parcel.restoreAllowFds(oldAllowFds); } } diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index e9ed676..15e3af4 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -323,6 +323,12 @@ public final class Parcel { */ public final native void setDataCapacity(int size); + /** @hide */ + public final native boolean pushAllowFds(boolean allowFds); + + /** @hide */ + public final native void restoreAllowFds(boolean lastValue); + /** * Returns the raw bytes of the parcel. * diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 68385b4..cc2fa85 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -35,7 +35,6 @@ import dalvik.system.VMDebug; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -902,6 +901,7 @@ public final class StrictMode { return false; } + // Thread policy controls BlockGuard. int threadPolicyMask = StrictMode.DETECT_DISK_WRITE | StrictMode.DETECT_DISK_READ | StrictMode.DETECT_NETWORK; @@ -915,10 +915,16 @@ public final class StrictMode { StrictMode.setThreadPolicyMask(threadPolicyMask); + // VM Policy controls CloseGuard, detection of Activity leaks, + // and instance counting. if (IS_USER_BUILD) { setCloseGuardEnabled(false); } else { - setVmPolicy(new VmPolicy.Builder().detectAll().penaltyDropBox().build()); + VmPolicy.Builder policyBuilder = new VmPolicy.Builder().detectAll().penaltyDropBox(); + if (IS_ENG_BUILD) { + policyBuilder.penaltyLog(); + } + setVmPolicy(policyBuilder.build()); setCloseGuardEnabled(vmClosableObjectLeaksEnabled()); } return true; diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index 4b4d308..d7060c1 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -300,8 +300,25 @@ public final class CalendarContract { public static final String CALENDAR_COLOR = "calendar_color"; /** + * An index for looking up a color from the {@link Colors} table. NULL + * or an empty string are reserved for indicating that the calendar does + * not use an index for looking up the color. The provider will update + * {@link #CALENDAR_COLOR} automatically when a valid index is written + * to this column. @see Colors + * <P> + * Type: TEXT + * </P> + * TODO UNHIDE + * + * @hide + */ + public static final String CALENDAR_COLOR_INDEX = "calendar_color_index"; + + /** * The display name of the calendar. Column name. - * <P>Type: TEXT</P> + * <P> + * Type: TEXT + * </P> */ public static final String CALENDAR_DISPLAY_NAME = "calendar_displayName"; @@ -392,6 +409,34 @@ public final class CalendarContract { * <P>Type: TEXT</P> */ public static final String ALLOWED_REMINDERS = "allowedReminders"; + + /** + * A comma separated list of availability types supported for this + * calendar in the format "#,#,#". Valid types are + * {@link Events#AVAILABILITY_BUSY}, {@link Events#AVAILABILITY_FREE}, + * {@link Events#AVAILABILITY_TENTATIVE}. Setting this field to only + * {@link Events#AVAILABILITY_BUSY} should be used to indicate that + * changing the availability is not supported. + * + * TODO UNHIDE, Update Calendars doc + * + * @hide + */ + public static final String ALLOWED_AVAILABILITY = "allowedAvailability"; + + /** + * A comma separated list of attendee types supported for this calendar + * in the format "#,#,#". Valid types are {@link Attendees#TYPE_NONE}, + * {@link Attendees#TYPE_OPTIONAL}, {@link Attendees#TYPE_REQUIRED}, + * {@link Attendees#TYPE_RESOURCE}. Setting this field to only + * {@link Attendees#TYPE_NONE} should be used to indicate that changing + * the attendee type is not supported. + * + * TODO UNHIDE, Update Calendars doc + * + * @hide + */ + public static final String ALLOWED_ATTENDEE_TYPES = "allowedAttendeeTypes"; } /** @@ -688,13 +733,22 @@ public final class CalendarContract { /** * The type of attendee. Column name. - * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})</P> + * <P> + * Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL} + * </P> */ public static final String ATTENDEE_TYPE = "attendeeType"; public static final int TYPE_NONE = 0; public static final int TYPE_REQUIRED = 1; public static final int TYPE_OPTIONAL = 2; + /** + * This specifies that an attendee is a resource, such as a room, and + * not an actual person. TODO UNHIDE and add to ATTENDEE_TYPE comment + * + * @hide + */ + public static final int TYPE_RESOURCE = 3; /** * The attendance status of the attendee. Column name. @@ -787,13 +841,26 @@ public final class CalendarContract { public static final String EVENT_LOCATION = "eventLocation"; /** - * A secondary color for the individual event. Reserved for future use. - * Column name. + * A secondary color for the individual event. This should only be + * updated by the sync adapter for a given account. * <P>Type: INTEGER</P> */ public static final String EVENT_COLOR = "eventColor"; /** + * A secondary color index for the individual event. NULL or an empty + * string are reserved for indicating that the event does not use an + * index for looking up the color. The provider will update + * {@link #EVENT_COLOR} automatically when a valid index is written to + * this column. @see Colors + * <P>Type: TEXT</P> + * TODO UNHIDE + * + * @hide + */ + public static final String EVENT_COLOR_INDEX = "eventColor_index"; + + /** * The event status. Column name. * <P>Type: INTEGER (one of {@link #STATUS_TENTATIVE}...)</P> */ @@ -964,6 +1031,15 @@ public final class CalendarContract { * other events. */ public static final int AVAILABILITY_FREE = 1; + /** + * Indicates that the owner's availability may change, but should be + * considered busy time that will conflict. + * + * TODO UNHIDE + * + * @hide + */ + public static final int AVAILABILITY_TENTATIVE = 2; /** * Whether the event has an alarm or not. Column name. @@ -2224,6 +2300,91 @@ public final class CalendarContract { } } + /** + * @hide + * TODO UNHIDE + */ + protected interface ColorsColumns extends SyncStateContract.Columns { + + /** + * The type of color, which describes how it should be used. Valid types + * are {@link #TYPE_CALENDAR} and {@link #TYPE_EVENT}. Column name. + * <P> + * Type: INTEGER (NOT NULL) + * </P> + */ + public static final String COLOR_TYPE = "color_type"; + + /** + * This indicateds a color that can be used for calendars. + */ + public static final int TYPE_CALENDAR = 0; + /** + * This indicates a color that can be used for events. + */ + public static final int TYPE_EVENT = 1; + + /** + * The index used to reference this color. This can be any non-empty + * string, but must be unique for a given {@link #ACCOUNT_TYPE} and + * {@link #ACCOUNT_NAME} . Column name. + * <P> + * Type: TEXT + * </P> + */ + public static final String COLOR_INDEX = "color_index"; + + /** + * The version of this color that will work with dark text as an 8-bit + * ARGB integer value. Colors should specify alpha as fully opaque (eg + * 0xFF993322) as the alpha may be ignored or modified for display. + * Column name. + * <P> + * Type: INTEGER (NOT NULL) + * </P> + */ + public static final String COLOR_LIGHT = "color_light"; + + /** + * The version of this color that will work with light text as an 8-bit + * ARGB integer value. Colors should specify alpha as fully opaque (eg + * 0xFF993322) as the alpha may be ignored or modified for display. + * Column name. + * <P> + * Type: INTEGER (NOT NULL) + * </P> + */ + public static final String COLOR_DARK = "color_dark"; + + } + + /** + * Fields for accessing colors available for a given account. Colors are + * referenced by {@link #COLOR_INDEX} which must be unique for a given + * account name/type. These values should only be updated by the sync + * adapter. + * TODO UNHIDE + * + * @hide + */ + public static final class Colors implements ColorsColumns { + /** + * @hide + */ + public static final String TABLE_NAME = "Colors"; + /** + * The Uri for querying color information + */ + @SuppressWarnings("hiding") + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/colors"); + + /** + * This utility class cannot be instantiated + */ + private Colors() { + } + } + protected interface ExtendedPropertiesColumns { /** * The event the extended property belongs to. Column name. diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 886edaf..6d14dfc 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -238,6 +238,14 @@ public class CallLog { public static final String CACHED_PHOTO_ID = "photo_id"; /** + * The cached formatted phone number. + * This value is not guaranteed to be present. + * <P>Type: TEXT</P> + * @hide + */ + public static final String CACHED_FORMATTED_NUMBER = "formatted_number"; + + /** * Adds a call to the call log. * * @param ci the CallerInfo object to get the target contact from. Can be null diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index fb119b3..8483b4f 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1611,9 +1611,16 @@ public final class ContactsContract { } /** + * <p> * A sub-directory of a single contact that contains all of the constituent raw contact * {@link ContactsContract.StreamItems} rows. This directory can be used either * with a {@link #CONTENT_URI} or {@link #CONTENT_LOOKUP_URI}. + * </p> + * <p> + * Querying for social stream data requires android.permission.READ_SOCIAL_STREAM + * permission. + * </p> + * @hide */ public static final class StreamItems implements StreamItemsColumns { /** @@ -2669,6 +2676,14 @@ public final class ContactsContract { * {@link ContactsContract.StreamItems} for a stand-alone table containing the * same data. * </p> + * <p> + * Access to the social stream through this sub-directory requires additional permissions + * beyond the read/write contact permissions required by the provider. Querying for + * social stream data requires android.permission.READ_SOCIAL_STREAM permission, and + * inserting or updating social stream items requires android.permission.WRITE_SOCIAL_STREAM + * permission. + * </p> + * @hide */ public static final class StreamItems implements BaseColumns, StreamItemsColumns { /** @@ -2963,6 +2978,12 @@ public final class ContactsContract { * transaction correspondingly. Insertion of more items beyond the limit will * automatically lead to deletion of the oldest items, by {@link StreamItems#TIMESTAMP}. * </p> + * <p> + * Access to the social stream through these URIs requires additional permissions beyond the + * read/write contact permissions required by the provider. Querying for social stream data + * requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating social + * stream items requires android.permission.WRITE_SOCIAL_STREAM permission. + * </p> * <h3>Operations</h3> * <dl> * <dt><b>Insert</b></dt> @@ -3075,6 +3096,7 @@ public final class ContactsContract { * </pre> * </dd> * </dl> + * @hide */ public static final class StreamItems implements BaseColumns, StreamItemsColumns { /** @@ -3135,6 +3157,12 @@ public final class ContactsContract { * directory append {@link StreamItems.StreamItemPhotos#CONTENT_DIRECTORY} to * an individual stream item URI. * </p> + * <p> + * Access to social stream photos requires additional permissions beyond the read/write + * contact permissions required by the provider. Querying for social stream photos + * requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating + * social stream photos requires android.permission.WRITE_SOCIAL_STREAM permission. + * </p> */ public static final class StreamItemPhotos implements BaseColumns, StreamItemPhotosColumns { @@ -3166,6 +3194,7 @@ public final class ContactsContract { * Columns in the StreamItems table. * * @see ContactsContract.StreamItems + * @hide */ protected interface StreamItemsColumns { /** @@ -3312,6 +3341,12 @@ public final class ContactsContract { * Constants for the stream_item_photos table, which contains photos associated with * social stream updates. * </p> + * <p> + * Access to social stream photos requires additional permissions beyond the read/write + * contact permissions required by the provider. Querying for social stream photos + * requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating + * social stream photos requires android.permission.WRITE_SOCIAL_STREAM permission. + * </p> * <h3>Operations</h3> * <dl> * <dt><b>Insert</b></dt> @@ -3450,6 +3485,7 @@ public final class ContactsContract { * <pre> * </dd> * </dl> + * @hide */ public static final class StreamItemPhotos implements BaseColumns, StreamItemPhotosColumns { /** @@ -3477,6 +3513,7 @@ public final class ContactsContract { * Columns in the StreamItemPhotos table. * * @see ContactsContract.StreamItemPhotos + * @hide */ protected interface StreamItemPhotosColumns { /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bc05078..3d2a3ce 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4087,7 +4087,9 @@ public final class Settings { MOUNT_UMS_AUTOSTART, MOUNT_UMS_PROMPT, MOUNT_UMS_NOTIFY_ENABLED, - UI_NIGHT_MODE + UI_NIGHT_MODE, + LOCK_SCREEN_OWNER_INFO, + LOCK_SCREEN_OWNER_INFO_ENABLED }; /** diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 237a892..79995d0 100755 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -666,6 +666,7 @@ public final class Telephony { public static SmsMessage[] getMessagesFromIntent( Intent intent) { Object[] messages = (Object[]) intent.getSerializableExtra("pdus"); + String format = intent.getStringExtra("format"); byte[][] pduObjs = new byte[messages.length][]; for (int i = 0; i < messages.length; i++) { @@ -676,7 +677,7 @@ public final class Telephony { SmsMessage[] msgs = new SmsMessage[pduCount]; for (int i = 0; i < pduCount; i++) { pdus[i] = pduObjs[i]; - msgs[i] = SmsMessage.createFromPdu(pdus[i]); + msgs[i] = SmsMessage.createFromPdu(pdus[i], format); } return msgs; } diff --git a/core/java/android/server/BluetoothAdapterStateMachine.java b/core/java/android/server/BluetoothAdapterStateMachine.java index d26364e..da7c489 100644 --- a/core/java/android/server/BluetoothAdapterStateMachine.java +++ b/core/java/android/server/BluetoothAdapterStateMachine.java @@ -77,6 +77,12 @@ final class BluetoothAdapterStateMachine extends StateMachine { 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 @@ -94,23 +100,18 @@ final class BluetoothAdapterStateMachine extends StateMachine { // private internal messages // - // 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 - private static final int TURN_HOT = 101; // 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 = 102; + private static final int TURN_ON_CONTINUE = 101; // Unload firmware, turning off Bluetooth module power - private static final int TURN_COLD = 103; + private static final int TURN_COLD = 102; // Device disconnecting timeout happens - private static final int DEVICES_DISCONNECT_TIMEOUT = 104; + private static final int DEVICES_DISCONNECT_TIMEOUT = 103; // Prepare Bluetooth timeout happens - private static final int PREPARE_BLUETOOTH_TIMEOUT = 105; + private static final int PREPARE_BLUETOOTH_TIMEOUT = 104; // Bluetooth Powerdown timeout happens - private static final int POWER_DOWN_TIMEOUT = 106; + private static final int POWER_DOWN_TIMEOUT = 105; private Context mContext; private BluetoothService mBluetoothService; @@ -156,11 +157,6 @@ final class BluetoothAdapterStateMachine extends StateMachine { setInitialState(mPowerOff); mPublicState = BluetoothAdapter.STATE_OFF; - - if (mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - sendMessage(TURN_HOT); - } } /** diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java index 2961fd2..5e93b81 100644 --- a/core/java/android/server/BluetoothHealthProfileHandler.java +++ b/core/java/android/server/BluetoothHealthProfileHandler.java @@ -45,8 +45,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ final class BluetoothHealthProfileHandler { private static final String TAG = "BluetoothHealthProfileHandler"; - /*STOPSHIP*/ - private static final boolean DBG = true; + private static final boolean DBG = false; private static BluetoothHealthProfileHandler sInstance; private BluetoothService mBluetoothService; diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 63da926..9ca5847 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -313,6 +313,10 @@ public class BluetoothService extends IBluetooth.Stub { 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(); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 713bb91..ba94ab2 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -565,7 +565,7 @@ public abstract class WallpaperService extends Service { mLayout.windowAnimations = com.android.internal.R.style.Animation_Wallpaper; mInputChannel = new InputChannel(); - if (mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets, + if (mSession.add(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, mContentInsets, mInputChannel) < 0) { Log.w(TAG, "Failed to add window while updating wallpaper surface."); return; @@ -580,7 +580,7 @@ public abstract class WallpaperService extends Service { mDrawingAllowed = true; final int relayoutResult = mSession.relayout( - mWindow, mLayout, mWidth, mHeight, + mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, View.VISIBLE, false, mWinFrame, mContentInsets, mVisibleInsets, mConfiguration, mSurfaceHolder.mSurface); diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 5126e48..98ab310 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -440,6 +440,9 @@ public class TextToSpeech { private final Context mContext; private Connection mServiceConnection; private OnInitListener mInitListener; + // Written from an unspecified application thread, read from + // a binder thread. + private volatile OnUtteranceCompletedListener mUtteranceCompletedListener; private final Object mStartLock = new Object(); private String mRequestedEngine; @@ -1071,20 +1074,8 @@ public class TextToSpeech { * @return {@link #ERROR} or {@link #SUCCESS}. */ public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) { - return runAction(new Action<Integer>() { - @Override - public Integer run(ITextToSpeechService service) throws RemoteException { - ITextToSpeechCallback.Stub callback = new ITextToSpeechCallback.Stub() { - public void utteranceCompleted(String utteranceId) { - if (listener != null) { - listener.onUtteranceCompleted(utteranceId); - } - } - }; - service.setCallback(getPackageName(), callback); - return SUCCESS; - } - }, ERROR, "setOnUtteranceCompletedListener"); + mUtteranceCompletedListener = listener; + return TextToSpeech.SUCCESS; } /** @@ -1137,6 +1128,15 @@ public class TextToSpeech { private class Connection implements ServiceConnection { private ITextToSpeechService mService; + private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { + @Override + public void utteranceCompleted(String utteranceId) { + OnUtteranceCompletedListener listener = mUtteranceCompletedListener; + if (listener != null) { + listener.onUtteranceCompleted(utteranceId); + } + } + }; public void onServiceConnected(ComponentName name, IBinder service) { Log.i(TAG, "Connected to " + name); @@ -1147,7 +1147,13 @@ public class TextToSpeech { } mServiceConnection = this; mService = ITextToSpeechService.Stub.asInterface(service); - dispatchOnInit(SUCCESS); + try { + mService.setCallback(getPackageName(), mCallback); + dispatchOnInit(SUCCESS); + } catch (RemoteException re) { + Log.e(TAG, "Error connecting to service, setCallback() failed"); + dispatchOnInit(ERROR); + } } } diff --git a/core/java/android/text/CharSequenceIterator.java b/core/java/android/text/CharSequenceIterator.java deleted file mode 100644 index 4b8ac10..0000000 --- a/core/java/android/text/CharSequenceIterator.java +++ /dev/null @@ -1,100 +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.text; - -import java.text.CharacterIterator; - -/** {@hide} */ -public class CharSequenceIterator implements CharacterIterator { - private final CharSequence mValue; - - private final int mLength; - private int mIndex; - - public CharSequenceIterator(CharSequence value) { - mValue = value; - mLength = value.length(); - mIndex = 0; - } - - @Override - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - throw new AssertionError(e); - } - } - - /** {@inheritDoc} */ - public char current() { - if (mIndex == mLength) { - return DONE; - } - return mValue.charAt(mIndex); - } - - /** {@inheritDoc} */ - public int getBeginIndex() { - return 0; - } - - /** {@inheritDoc} */ - public int getEndIndex() { - return mLength; - } - - /** {@inheritDoc} */ - public int getIndex() { - return mIndex; - } - - /** {@inheritDoc} */ - public char first() { - return setIndex(0); - } - - /** {@inheritDoc} */ - public char last() { - return setIndex(mLength - 1); - } - - /** {@inheritDoc} */ - public char next() { - if (mIndex == mLength) { - return DONE; - } - return setIndex(mIndex + 1); - } - - /** {@inheritDoc} */ - public char previous() { - if (mIndex == 0) { - return DONE; - } - return setIndex(mIndex - 1); - } - - /** {@inheritDoc} */ - public char setIndex(int index) { - if ((index < 0) || (index > mLength)) { - throw new IllegalArgumentException("Valid range is [" + 0 + "..." + mLength + "]"); - } - mIndex = index; - return current(); - } -} diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index c3a2308..f82c9c4 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -76,7 +76,7 @@ extends Layout boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR, - spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE); + spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth); } /** @@ -93,7 +93,7 @@ extends Layout int width, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, - TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) { + TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { super((ellipsize == null) ? display : (display instanceof Spanned) @@ -135,8 +135,6 @@ extends Layout mEllipsize = true; } - mMaxLines = maxLines; - // Initial state is a single line with 0 characters (0 to 0), // with top at 0 and bottom at whatever is natural, and // undefined ellipsis. @@ -285,7 +283,7 @@ extends Layout reflowed.generate(text, where, where + after, getPaint(), getWidth(), getAlignment(), getTextDirectionHeuristic(), getSpacingMultiplier(), getSpacingAdd(), - false, true, mEllipsizedWidth, mEllipsizeAt, mMaxLines); + false, true, mEllipsizedWidth, mEllipsizeAt); int n = reflowed.getLineCount(); // If the new layout has a blank line at the end, but it is not @@ -490,8 +488,6 @@ extends Layout private int mTopPadding, mBottomPadding; - private int mMaxLines; - private static StaticLayout sStaticLayout = new StaticLayout(null); private static final Object[] sLock = new Object[0]; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 7c27396..583cbe6 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -139,7 +139,7 @@ public class StaticLayout extends Layout { generate(source, bufstart, bufend, paint, outerwidth, align, textDir, spacingmult, spacingadd, includepad, includepad, - ellipsizedWidth, ellipsize, mMaximumVisibleLineCount); + ellipsizedWidth, ellipsize); mMeasured = MeasuredText.recycle(mMeasured); mFontMetricsInt = null; @@ -160,7 +160,7 @@ public class StaticLayout extends Layout { Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, boolean trackpad, - float ellipsizedWidth, TextUtils.TruncateAt ellipsize, int maxLines) { + float ellipsizedWidth, TextUtils.TruncateAt ellipsize) { mLineCount = 0; int v = 0; @@ -477,13 +477,13 @@ public class StaticLayout extends Layout { width = restWidth; } } - if (mLineCount >= maxLines) { + if (mLineCount >= mMaximumVisibleLineCount) { break; } } } - if (paraEnd != here && mLineCount < maxLines) { + if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) { if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) { paint.getFontMetricsInt(fm); @@ -514,7 +514,7 @@ public class StaticLayout extends Layout { } if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && - mLineCount < maxLines) { + mLineCount < mMaximumVisibleLineCount) { // Log.e("text", "output last " + bufEnd); paint.getFontMetricsInt(fm); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index b8728ee..e93039b 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -35,11 +35,11 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0)); } - private int getCurrentLineTop(Spannable buffer, Layout layout) { + private static int getCurrentLineTop(Spannable buffer, Layout layout) { return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer))); } - private int getPageHeight(TextView widget) { + private static int getPageHeight(TextView widget) { // This calculation does not take into account the view transformations that // may have been applied to the child or its containers. In case of scaling or // rotation, the calculated page height may be incorrect. @@ -196,14 +196,16 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme /** {@hide} */ @Override protected boolean leftWord(TextView widget, Spannable buffer) { - mWordIterator.setCharSequence(buffer); + final int selectionEnd = widget.getSelectionEnd(); + mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd); return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer)); } /** {@hide} */ @Override protected boolean rightWord(TextView widget, Spannable buffer) { - mWordIterator.setCharSequence(buffer); + final int selectionEnd = widget.getSelectionEnd(); + mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd); return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer)); } diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java index af524ee..239d9e8 100644 --- a/core/java/android/text/method/WordIterator.java +++ b/core/java/android/text/method/WordIterator.java @@ -17,14 +17,9 @@ package android.text.method; -import android.text.CharSequenceIterator; -import android.text.Editable; import android.text.Selection; -import android.text.Spanned; -import android.text.TextWatcher; import java.text.BreakIterator; -import java.text.CharacterIterator; import java.util.Locale; /** @@ -36,8 +31,11 @@ import java.util.Locale; * {@hide} */ public class WordIterator implements Selection.PositionIterator { - private CharSequence mCurrent; - private boolean mCurrentDirty = false; + // Size of the window for the word iterator, should be greater than the longest word's length + private static final int WINDOW_WIDTH = 50; + + private String mString; + private int mOffsetShift; private BreakIterator mIterator; @@ -56,70 +54,40 @@ public class WordIterator implements Selection.PositionIterator { mIterator = BreakIterator.getWordInstance(locale); } - private final TextWatcher mWatcher = new TextWatcher() { - /** {@inheritDoc} */ - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // ignored - } - - /** {@inheritDoc} */ - public void onTextChanged(CharSequence s, int start, int before, int count) { - mCurrentDirty = true; - } - - /** {@inheritDoc} */ - public void afterTextChanged(Editable s) { - // ignored - } - }; - - public void setCharSequence(CharSequence incoming) { - // When incoming is different object, move listeners to new sequence - // and mark as dirty so we reload contents. - if (mCurrent != incoming) { - if (mCurrent instanceof Editable) { - ((Editable) mCurrent).removeSpan(mWatcher); - } + public void setCharSequence(CharSequence charSequence, int start, int end) { + mOffsetShift = Math.max(0, start - WINDOW_WIDTH); + final int windowEnd = Math.min(charSequence.length(), end + WINDOW_WIDTH); - if (incoming instanceof Editable) { - ((Editable) incoming).setSpan( - mWatcher, 0, incoming.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - - mCurrent = incoming; - mCurrentDirty = true; - } - - if (mCurrentDirty) { - final CharacterIterator charIterator = new CharSequenceIterator(mCurrent); - mIterator.setText(charIterator); - - mCurrentDirty = false; - } + mString = charSequence.toString().substring(mOffsetShift, windowEnd); + mIterator.setText(mString); } /** {@inheritDoc} */ public int preceding(int offset) { + int shiftedOffset = offset - mOffsetShift; do { - offset = mIterator.preceding(offset); - if (offset == BreakIterator.DONE || isOnLetterOrDigit(offset)) { - break; + shiftedOffset = mIterator.preceding(shiftedOffset); + if (shiftedOffset == BreakIterator.DONE) { + return BreakIterator.DONE; + } + if (isOnLetterOrDigit(shiftedOffset)) { + return shiftedOffset + mOffsetShift; } } while (true); - - return offset; } /** {@inheritDoc} */ public int following(int offset) { + int shiftedOffset = offset - mOffsetShift; do { - offset = mIterator.following(offset); - if (offset == BreakIterator.DONE || isAfterLetterOrDigit(offset)) { - break; + shiftedOffset = mIterator.following(shiftedOffset); + if (shiftedOffset == BreakIterator.DONE) { + return BreakIterator.DONE; + } + if (isAfterLetterOrDigit(shiftedOffset)) { + return shiftedOffset + mOffsetShift; } } while (true); - - return offset; } /** If <code>offset</code> is within a word, returns the index of the first character of that @@ -135,17 +103,18 @@ public class WordIterator implements Selection.PositionIterator { * @throws IllegalArgumentException is offset is not valid. */ public int getBeginning(int offset) { - checkOffsetIsValid(offset); + final int shiftedOffset = offset - mOffsetShift; + checkOffsetIsValid(shiftedOffset); - if (isOnLetterOrDigit(offset)) { - if (mIterator.isBoundary(offset)) { - return offset; + if (isOnLetterOrDigit(shiftedOffset)) { + if (mIterator.isBoundary(shiftedOffset)) { + return shiftedOffset + mOffsetShift; } else { - return mIterator.preceding(offset); + return mIterator.preceding(shiftedOffset) + mOffsetShift; } } else { - if (isAfterLetterOrDigit(offset)) { - return mIterator.preceding(offset); + if (isAfterLetterOrDigit(shiftedOffset)) { + return mIterator.preceding(shiftedOffset) + mOffsetShift; } } return BreakIterator.DONE; @@ -164,58 +133,44 @@ public class WordIterator implements Selection.PositionIterator { * @throws IllegalArgumentException is offset is not valid. */ public int getEnd(int offset) { - checkOffsetIsValid(offset); + final int shiftedOffset = offset - mOffsetShift; + checkOffsetIsValid(shiftedOffset); - if (isAfterLetterOrDigit(offset)) { - if (mIterator.isBoundary(offset)) { - return offset; + if (isAfterLetterOrDigit(shiftedOffset)) { + if (mIterator.isBoundary(shiftedOffset)) { + return shiftedOffset + mOffsetShift; } else { - return mIterator.following(offset); + return mIterator.following(shiftedOffset) + mOffsetShift; } } else { - if (isOnLetterOrDigit(offset)) { - return mIterator.following(offset); + if (isOnLetterOrDigit(shiftedOffset)) { + return mIterator.following(shiftedOffset) + mOffsetShift; } } return BreakIterator.DONE; } - private boolean isAfterLetterOrDigit(int offset) { - if (offset - 1 >= 0) { - final char previousChar = mCurrent.charAt(offset - 1); - if (Character.isLetterOrDigit(previousChar)) return true; - if (offset - 2 >= 0) { - final char previousPreviousChar = mCurrent.charAt(offset - 2); - if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { - final int codePoint = Character.toCodePoint(previousPreviousChar, previousChar); - return Character.isLetterOrDigit(codePoint); - } - } + private boolean isAfterLetterOrDigit(int shiftedOffset) { + if (shiftedOffset >= 1 && shiftedOffset <= mString.length()) { + final int codePoint = mString.codePointBefore(shiftedOffset); + if (Character.isLetterOrDigit(codePoint)) return true; } return false; } - private boolean isOnLetterOrDigit(int offset) { - final int length = mCurrent.length(); - if (offset < length) { - final char currentChar = mCurrent.charAt(offset); - if (Character.isLetterOrDigit(currentChar)) return true; - if (offset + 1 < length) { - final char nextChar = mCurrent.charAt(offset + 1); - if (Character.isSurrogatePair(currentChar, nextChar)) { - final int codePoint = Character.toCodePoint(currentChar, nextChar); - return Character.isLetterOrDigit(codePoint); - } - } + private boolean isOnLetterOrDigit(int shiftedOffset) { + if (shiftedOffset >= 0 && shiftedOffset < mString.length()) { + final int codePoint = mString.codePointAt(shiftedOffset); + if (Character.isLetterOrDigit(codePoint)) return true; } return false; } - private void checkOffsetIsValid(int offset) { - if (offset < 0 || offset > mCurrent.length()) { - final String message = "Invalid offset: " + offset + - ". Valid range is [0, " + mCurrent.length() + "]"; - throw new IllegalArgumentException(message); + private void checkOffsetIsValid(int shiftedOffset) { + if (shiftedOffset < 0 || shiftedOffset > mString.length()) { + throw new IllegalArgumentException("Invalid offset: " + (shiftedOffset + mOffsetShift) + + ". Valid range is [" + mOffsetShift + ", " + (mString.length() + mOffsetShift) + + "]"); } } } diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 1379dd2..6e95016 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -56,6 +56,14 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { */ public static final int FLAG_MISSPELLED = 0x0002; + /** + * Sets this flag if the auto correction is about to be applied to a word/text + * that the user is typing/composing. This type of suggestion is rendered differently + * to indicate the auto correction is happening. + * @hide + */ + public static final int FLAG_AUTO_CORRECTION = 0x0004; + public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED"; public static final String SUGGESTION_SPAN_PICKED_AFTER = "after"; public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before"; @@ -82,6 +90,9 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { private float mMisspelledUnderlineThickness; private int mMisspelledUnderlineColor; + private float mAutoCorrectionUnderlineThickness; + private int mAutoCorrectionUnderlineColor; + /* * TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo * and InputMethodSubtype. @@ -145,14 +156,21 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion; - typedArray = context.obtainStyledAttributes( null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); - mEasyCorrectUnderlineThickness = typedArray.getDimension( com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); mEasyCorrectUnderlineColor = typedArray.getColor( com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); + + defStyle = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion; + typedArray = context.obtainStyledAttributes( + null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0); + mAutoCorrectionUnderlineThickness = typedArray.getDimension( + com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); + mAutoCorrectionUnderlineColor = typedArray.getColor( + com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); + } public SuggestionSpan(Parcel src) { @@ -165,6 +183,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { mEasyCorrectUnderlineThickness = src.readFloat(); mMisspelledUnderlineColor = src.readInt(); mMisspelledUnderlineThickness = src.readFloat(); + mAutoCorrectionUnderlineColor = src.readInt(); + mAutoCorrectionUnderlineThickness = src.readFloat(); } /** @@ -218,6 +238,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { dest.writeFloat(mEasyCorrectUnderlineThickness); dest.writeInt(mMisspelledUnderlineColor); dest.writeFloat(mMisspelledUnderlineThickness); + dest.writeInt(mAutoCorrectionUnderlineColor); + dest.writeFloat(mAutoCorrectionUnderlineThickness); } @Override @@ -261,6 +283,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { public void updateDrawState(TextPaint tp) { final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0; + final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0; if (easy) { if (!misspelled) { tp.setUnderlineText(mEasyCorrectUnderlineColor, mEasyCorrectUnderlineThickness); @@ -269,6 +292,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { // than just easy, do not apply misspelled if an easy (or a mispelled) has been set tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness); } + } else if (autoCorrection) { + tp.setUnderlineText(mAutoCorrectionUnderlineColor, mAutoCorrectionUnderlineThickness); } } @@ -281,12 +306,15 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { // The order here should match what is used in updateDrawState final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0; + final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0; if (easy) { if (!misspelled) { return mEasyCorrectUnderlineColor; } else { return mMisspelledUnderlineColor; } + } else if (autoCorrection) { + return mAutoCorrectionUnderlineColor; } return 0; } diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java index e954983..34e7d4d 100644 --- a/core/java/android/view/ActionMode.java +++ b/core/java/android/view/ActionMode.java @@ -153,6 +153,18 @@ public abstract class ActionMode { public abstract MenuInflater getMenuInflater(); /** + * Returns whether the UI presenting this action mode can take focus or not. + * This is used by internal components within the framework that would otherwise + * present an action mode UI that requires focus, such as an EditText as a custom view. + * + * @return true if the UI used to show this action mode can take focus + * @hide Internal use only + */ + public boolean isUiFocusable() { + return true; + } + + /** * Callback interface for action modes. Supplied to * {@link View#startActionMode(Callback)}, a Callback * configures and handles events raised by a user's interaction with an action mode. diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 3bd0f76..ad2283e 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -117,6 +117,11 @@ public class Display { 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) { diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 23d1b0f..b86d21d 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -184,6 +184,22 @@ public abstract class HardwareRenderer { abstract void setup(int width, int height); /** + * Gets the current width of the surface. This is the width that the surface + * was last set to in a call to {@link #setup(int, int)}. + * + * @return the current width of the surface + */ + abstract int getWidth(); + + /** + * Gets the current height of the surface. This is the height that the surface + * was last set to in a call to {@link #setup(int, int)}. + * + * @return the current width of the surface + */ + abstract int getHeight(); + + /** * Interface used to receive callbacks whenever a view is drawn by * a hardware renderer instance. */ @@ -362,6 +378,7 @@ public abstract class HardwareRenderer { static EGLDisplay sEglDisplay; static EGLConfig sEglConfig; static final Object[] sEglLock = new Object[0]; + int mWidth = -1, mHeight = -1; static final ThreadLocal<EGLContext> sEglContextStorage = new ThreadLocal<EGLContext>(); @@ -714,9 +731,21 @@ public abstract class HardwareRenderer { void setup(int width, int height) { if (validate()) { mCanvas.setViewport(width, height); + mWidth = width; + mHeight = height; } } + @Override + int getWidth() { + return mWidth; + } + + @Override + int getHeight() { + return mHeight; + } + boolean canDraw() { return mGl != null && mCanvas != null; } diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 0e482d6..715fa7b 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -74,5 +74,6 @@ oneway interface IWindow { /** * System chrome visibility changes */ - void dispatchSystemUiVisibilityChanged(int visibility); + void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, + int localValue, int localChanges); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 45f9da2..fe32a5f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -192,11 +192,12 @@ interface IWindowManager int getPreferredOptionsPanelGravity(); /** - * Lock the device orientation to the current rotation. Sensor input will - * be ignored until thawRotation() is called. + * Lock the device orientation to the specified rotation, or to the + * current rotation if -1. Sensor input will be ignored until + * thawRotation() is called. * @hide */ - void freezeRotation(); + void freezeRotation(int rotation); /** * Release the orientation lock imposed by freezeRotation(). diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 990af08..282d7be 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -34,10 +34,10 @@ import android.view.Surface; * {@hide} */ interface IWindowSession { - int add(IWindow window, in WindowManager.LayoutParams attrs, + int add(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, out Rect outContentInsets, out InputChannel outInputChannel); - int addWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs, + int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, out Rect outContentInsets); void remove(IWindow window); @@ -49,6 +49,7 @@ interface IWindowSession { * to draw the window's contents. * * @param window The window being modified. + * @param seq Ordering sequence number. * @param attrs If non-null, new attributes to apply to the window. * @param requestedWidth The width the window wants to be. * @param requestedHeight The height the window wants to be. @@ -77,7 +78,7 @@ interface IWindowSession { * @return int Result flags: {@link WindowManagerImpl#RELAYOUT_SHOW_FOCUS}, * {@link WindowManagerImpl#RELAYOUT_FIRST_TIME}. */ - int relayout(IWindow window, in WindowManager.LayoutParams attrs, + int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, boolean insetsPending, out Rect outFrame, out Rect outContentInsets, out Rect outVisibleInsets, out Configuration outConfig, diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index cbdb38e..9a57ea0 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -266,7 +266,7 @@ public class SurfaceView extends View { try { DisplayMetrics metrics = getResources().getDisplayMetrics(); mLayout.x = metrics.widthPixels * 3; - mSession.relayout(mWindow, mLayout, mWidth, mHeight, VISIBLE, false, + mSession.relayout(mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, VISIBLE, false, mWinFrame, mContentInsets, mVisibleInsets, mConfiguration, mSurface); } catch (RemoteException e) { // Ignore @@ -492,7 +492,7 @@ public class SurfaceView extends View { mWindow = new MyWindow(this); mLayout.type = mWindowType; mLayout.gravity = Gravity.LEFT|Gravity.TOP; - mSession.addWithoutInputChannel(mWindow, mLayout, + mSession.addWithoutInputChannel(mWindow, mWindow.mSeq, mLayout, mVisible ? VISIBLE : GONE, mContentInsets); } @@ -513,7 +513,7 @@ public class SurfaceView extends View { mDrawingStopped = !visible; final int relayoutResult = mSession.relayout( - mWindow, mLayout, mWidth, mHeight, + mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets, mVisibleInsets, mConfiguration, mSurface); if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index d57132a..1697382 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -502,9 +502,23 @@ public class TextureView extends View { * @see #isAvailable() * @see #getBitmap(int, int) * @see #getBitmap() + * + * @throws IllegalStateException if the hardware rendering context cannot be + * acquired to capture the bitmap */ public Bitmap getBitmap(Bitmap bitmap) { if (bitmap != null && isAvailable()) { + AttachInfo info = mAttachInfo; + if (info != null && info.mHardwareRenderer != null && + info.mHardwareRenderer.isEnabled()) { + if (!info.mHardwareRenderer.validate()) { + throw new IllegalStateException("Could not acquire hardware rendering context"); + } + } + + applyUpdate(); + applyTransformMatrix(); + mLayer.copyInto(bitmap); } return bitmap; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d193d6e..86be28a 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -94,20 +94,10 @@ import java.util.concurrent.CopyOnWriteArrayList; * their layout properties. * </p> * - * <div class="special"> - * <p>For an introduction to using this class to develop your - * application's user interface, read the Developer Guide documentation on - * <strong><a href="{@docRoot}guide/topics/ui/index.html">User Interface</a></strong>. Special topics - * include: - * <br/><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Declaring Layout</a> - * <br/><a href="{@docRoot}guide/topics/ui/menus.html">Creating Menus</a> - * <br/><a href="{@docRoot}guide/topics/ui/layout-objects.html">Common Layout Objects</a> - * <br/><a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a> - * <br/><a href="{@docRoot}guide/topics/ui/ui-events.html">Handling UI Events</a> - * <br/><a href="{@docRoot}guide/topics/ui/themes.html">Applying Styles and Themes</a> - * <br/><a href="{@docRoot}guide/topics/ui/custom-components.html">Building Custom Components</a> - * <br/><a href="{@docRoot}guide/topics/ui/how-android-draws.html">How Android Draws Views</a>. - * </p> + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For information about using this class to develop your application's user interface, + * read the <a href="{@docRoot}guide/topics/ui/index.html">User Interface</a> developer guide. * </div> * * <a name="Using"></a> @@ -1487,6 +1477,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Accessibility event types that are dispatched for text population. + */ + private static final int POPULATING_ACCESSIBILITY_EVENT_TYPES = + AccessibilityEvent.TYPE_VIEW_CLICKED + | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED + | AccessibilityEvent.TYPE_VIEW_SELECTED + | AccessibilityEvent.TYPE_VIEW_FOCUSED + | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER + | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT + | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED; + + /** * Temporary Rect currently for use in setBackground(). This will probably * be extended in the future to hold our own class with more than just * a Rect. :) @@ -1923,6 +1926,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF; /** + * These are the system UI flags that can be cleared by events outside + * of an application. Currently this is just the ability to tap on the + * screen while hiding the navigation bar to have it return. + * @hide + */ + public static final int SYSTEM_UI_CLEARABLE_FLAGS = + SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_HIDE_NAVIGATION; + + /** * Find views that render the specified text. * * @see #findViewsWithText(ArrayList, CharSequence, int) @@ -3333,7 +3345,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (mOnLayoutChangeListeners == null) { mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>(); } - mOnLayoutChangeListeners.add(listener); + if (!mOnLayoutChangeListeners.contains(listener)) { + mOnLayoutChangeListeners.add(listener); + } } /** @@ -3855,7 +3869,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return; } onInitializeAccessibilityEvent(event); - dispatchPopulateAccessibilityEvent(event); + // Only a subset of accessibility events populates text content. + if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) { + dispatchPopulateAccessibilityEvent(event); + } // In the beginning we called #isShown(), so we know that getParent() is not null. getParent().requestSendAccessibilityEvent(this, event); } @@ -3876,6 +3893,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link AccessibilityDelegate#dispatchPopulateAccessibilityEvent(View, AccessibilityEvent)} * is responsible for handling this call. * </p> + * <p> + * <em>Note:</em> Accessibility events of certain types are not dispatched for + * populating the event text via this method. For details refer to {@link AccessibilityEvent}. + * </p> * * @param event The event. * @@ -3895,12 +3916,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Note: Called from the default {@link AccessibilityDelegate}. */ boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { - // Do not populate text to scroll events. They describe position change - // and usually come from container with a lot of text which is not very - // informative for accessibility purposes. Also they are fired frequently. - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - return true; - } onPopulateAccessibilityEvent(event); return false; } @@ -3998,23 +4013,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal event.setEnabled(isEnabled()); event.setContentDescription(mContentDescription); - final int eventType = event.getEventType(); - switch (eventType) { - case AccessibilityEvent.TYPE_VIEW_FOCUSED: { - if (mAttachInfo != null) { - ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList; - getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, - FOCUSABLES_ALL); - event.setItemCount(focusablesTempList.size()); - event.setCurrentItemIndex(focusablesTempList.indexOf(this)); - focusablesTempList.clear(); - } - } break; - case AccessibilityEvent.TYPE_VIEW_SCROLLED: { - event.setScrollX(mScrollX); - event.setScrollY(mScrollY); - event.setItemCount(getHeight()); - } break; + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) { + ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList; + getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, + FOCUSABLES_ALL); + event.setItemCount(focusablesTempList.size()); + event.setCurrentItemIndex(focusablesTempList.indexOf(this)); + focusablesTempList.clear(); } } @@ -10121,6 +10126,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return mHardwareLayer; } + /** + * Destroys this View's hardware layer if possible. + * + * @return True if the layer was destroyed, false otherwise. + * + * @see #setLayerType(int, android.graphics.Paint) + * @see #LAYER_TYPE_HARDWARE + */ boolean destroyLayer() { if (mHardwareLayer != null) { mHardwareLayer.destroy(); @@ -12093,6 +12106,39 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Finds a view by its unuque and stable accessibility id. + * + * @param accessibilityId The searched accessibility id. + * @return The found view. + */ + final View findViewByAccessibilityId(int accessibilityId) { + if (accessibilityId < 0) { + return null; + } + return findViewByAccessibilityIdTraversal(accessibilityId); + } + + /** + * Performs the traversal to find a view by its unuque and stable accessibility id. + * + * <strong>Note:</strong>This method does not stop at the root namespace + * boundary since the user can touch the screen at an arbitrary location + * potentially crossing the root namespace bounday which will send an + * accessibility event to accessibility services and they should be able + * to obtain the event source. Also accessibility ids are guaranteed to be + * unique in the window. + * + * @param accessibilityId The accessibility id. + * @return The found view. + */ + View findViewByAccessibilityIdTraversal(int accessibilityId) { + if (getAccessibilityViewId() == accessibilityId) { + return this; + } + return null; + } + + /** * Look for a child view with the given tag. If this view has the given * tag, return this view. * @@ -12991,6 +13037,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Dispatch callbacks to {@link #setOnSystemUiVisibilityChangeListener} down + * the view hierarchy. */ public void dispatchSystemUiVisibilityChanged(int visibility) { if (mOnSystemUiVisibilityChangeListener != null) { @@ -12999,6 +13047,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } + void updateLocalSystemUiVisibility(int localValue, int localChanges) { + int val = (mSystemUiVisibility&~localChanges) | (localValue&localChanges); + if (val != mSystemUiVisibility) { + setSystemUiVisibility(val); + } + } + /** * Creates an image that the system displays during the drag and drop * operation. This is called a "drag shadow". The default implementation @@ -14072,7 +14127,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Interface definition for a callback to be invoked when the status bar changes - * visibility. + * visibility. This reports <strong>global</strong> changes to the system UI + * state, not just what the application is requesting. * * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) */ @@ -14082,7 +14138,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link View#setSystemUiVisibility(int)}. * * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or - * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. + * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. This tells you the + * <strong>global</strong> state of the UI visibility flags, not what your + * app is currently applying. */ public void onSystemUiVisibilityChange(int visibility); } @@ -14340,6 +14398,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal boolean mRecomputeGlobalAttributes; /** + * Always report new attributes at next traversal. + */ + boolean mForceReportNewAttributes; + + /** * Set during a traveral if any views want to keep the screen on. */ boolean mKeepScreenOn; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index fb0d80a..9266ae2 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -821,6 +821,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + View findViewByAccessibilityIdTraversal(int accessibilityId) { + View foundView = super.findViewByAccessibilityIdTraversal(accessibilityId); + if (foundView != null) { + return foundView; + } + final int childrenCount = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < childrenCount; i++) { + View child = children[i]; + foundView = child.findViewByAccessibilityIdTraversal(accessibilityId); + if (foundView != null) { + return foundView; + } + } + return null; + } + /** * {@inheritDoc} */ @@ -1182,6 +1200,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + void updateLocalSystemUiVisibility(int localValue, int localChanges) { + super.updateLocalSystemUiVisibility(localValue, localChanges); + + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i=0; i <count; i++) { + final View child = children[i]; + child.updateLocalSystemUiVisibility(localValue, localChanges); + } + } + /** * {@inheritDoc} */ diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9cb4e5e..7eae739 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -74,7 +74,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import com.android.internal.policy.PolicyManager; -import com.android.internal.util.Predicate; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.IInputMethodCallback; import com.android.internal.view.IInputMethodSession; @@ -166,6 +165,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, final int mTargetSdkVersion; + int mSeq; + View mView; View mFocusedView; View mRealFocusedView; // this is not set to null in touch mode @@ -309,6 +310,13 @@ public final class ViewRootImpl extends Handler implements ViewParent, return sWindowSession; } } + + static final class SystemUiVisibilityInfo { + int seq; + int globalVisibility; + int localValue; + int localChanges; + } public ViewRootImpl(Context context) { super(); @@ -466,7 +474,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, } try { mOrigWindowType = mWindowAttributes.type; - res = sWindowSession.add(mWindow, mWindowAttributes, + res = sWindowSession.add(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets, mInputChannel); } catch (RemoteException e) { @@ -861,7 +869,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get(); if (compatibilityInfo.supportsScreen() == mLastInCompatMode) { params = lp; - windowAttributesChanges |= WindowManager.LayoutParams.BUFFER_CHANGED; fullRedrawNeeded = true; mLayoutRequested = true; if (mLastInCompatMode) { @@ -1046,16 +1053,21 @@ public final class ViewRootImpl extends Handler implements ViewParent, attachInfo.mRecomputeGlobalAttributes = false; boolean oldScreenOn = attachInfo.mKeepScreenOn; int oldVis = attachInfo.mSystemUiVisibility; + boolean oldHasSystemUiListeners = attachInfo.mHasSystemUiListeners; attachInfo.mKeepScreenOn = false; attachInfo.mSystemUiVisibility = 0; attachInfo.mHasSystemUiListeners = false; host.dispatchCollectViewAttributes(0); if (attachInfo.mKeepScreenOn != oldScreenOn || attachInfo.mSystemUiVisibility != oldVis - || attachInfo.mHasSystemUiListeners) { + || attachInfo.mHasSystemUiListeners != oldHasSystemUiListeners) { params = lp; } } + if (attachInfo.mForceReportNewAttributes) { + attachInfo.mForceReportNewAttributes = false; + params = lp; + } if (mFirst || attachInfo.mViewVisibilityChanged) { attachInfo.mViewVisibilityChanged = false; @@ -1079,7 +1091,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) | resizeMode; params = lp; - windowAttributesChanges |= WindowManager.LayoutParams.BUFFER_CHANGED; } } } @@ -1137,9 +1148,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; } params.subtreeSystemUiVisibility = attachInfo.mSystemUiVisibility; - params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners - || params.subtreeSystemUiVisibility != 0 - || params.systemUiVisibility != 0; + params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners; } if (DEBUG_LAYOUT) { Log.i(TAG, "host=w:" + host.getMeasuredWidth() + ", h:" + @@ -1376,13 +1385,15 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - if (hwInitialized || ((windowShouldResize || (params != null && - (windowAttributesChanges & WindowManager.LayoutParams.BUFFER_CHANGED) != 0)) && - mAttachInfo.mHardwareRenderer != null && - mAttachInfo.mHardwareRenderer.isEnabled())) { - mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight); - if (!hwInitialized && mAttachInfo.mHardwareRenderer.isEnabled()) { - mAttachInfo.mHardwareRenderer.invalidate(mHolder); + if (mAttachInfo.mHardwareRenderer != null && + mAttachInfo.mHardwareRenderer.isEnabled()) { + if (hwInitialized || windowShouldResize || + mWidth != mAttachInfo.mHardwareRenderer.getWidth() || + mHeight != mAttachInfo.mHardwareRenderer.getHeight()) { + mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight); + if (!hwInitialized) { + mAttachInfo.mHardwareRenderer.invalidate(mHolder); + } } } @@ -2546,7 +2557,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, handleDragEvent(event); } break; case DISPATCH_SYSTEM_UI_VISIBILITY: { - handleDispatchSystemUiVisibilityChanged(msg.arg1); + handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo)msg.obj); } break; case UPDATE_CONFIGURATION: { Configuration config = (Configuration)msg.obj; @@ -3430,12 +3441,27 @@ public final class ViewRootImpl extends Handler implements ViewParent, event.recycle(); } - public void handleDispatchSystemUiVisibilityChanged(int visibility) { + public void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) { + if (mSeq != args.seq) { + // The sequence has changed, so we need to update our value and make + // sure to do a traversal afterward so the window manager is given our + // most recent data. + mSeq = args.seq; + mAttachInfo.mForceReportNewAttributes = true; + scheduleTraversals(); + } if (mView == null) return; - if (mAttachInfo != null) { - mAttachInfo.mSystemUiVisibility = visibility; + if (args.localChanges != 0) { + if (mAttachInfo != null) { + mAttachInfo.mSystemUiVisibility = + (mAttachInfo.mSystemUiVisibility&~args.localChanges) + | (args.localValue&args.localChanges); + } + mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges); + mAttachInfo.mRecomputeGlobalAttributes = true; + scheduleTraversals(); } - mView.dispatchSystemUiVisibilityChanged(visibility); + mView.dispatchSystemUiVisibilityChanged(args.globalVisibility); } public void getLastTouchPoint(Point outLocation) { @@ -3494,7 +3520,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } int relayoutResult = sWindowSession.relayout( - mWindow, params, + mWindow, mSeq, params, (int) (mView.getMeasuredWidth() * appScale + 0.5f), (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending, mWinFrame, @@ -3797,8 +3823,14 @@ public final class ViewRootImpl extends Handler implements ViewParent, sendMessage(msg); } - public void dispatchSystemUiVisibilityChanged(int visibility) { - sendMessage(obtainMessage(DISPATCH_SYSTEM_UI_VISIBILITY, visibility, 0)); + public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, + int localValue, int localChanges) { + SystemUiVisibilityInfo args = new SystemUiVisibilityInfo(); + args.seq = seq; + args.globalVisibility = globalVisibility; + args.localValue = localValue; + args.localChanges = localChanges; + sendMessage(obtainMessage(DISPATCH_SYSTEM_UI_VISIBILITY, args)); } /** @@ -4053,10 +4085,12 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - public void dispatchSystemUiVisibilityChanged(int visibility) { + public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, + int localValue, int localChanges) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { - viewAncestor.dispatchSystemUiVisibilityChanged(visibility); + viewAncestor.dispatchSystemUiVisibilityChanged(seq, globalVisibility, + localValue, localChanges); } } } @@ -4462,9 +4496,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, final class AccessibilityInteractionController { private static final int POOL_SIZE = 5; - private FindByAccessibilitytIdPredicate mFindByAccessibilityIdPredicate = - new FindByAccessibilitytIdPredicate(); - private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = new ArrayList<AccessibilityNodeInfo>(); @@ -4551,11 +4582,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, AccessibilityNodeInfo info = null; try { - FindByAccessibilitytIdPredicate predicate = mFindByAccessibilityIdPredicate; - predicate.init(accessibilityId); - View root = ViewRootImpl.this.mView; - View target = root.findViewByPredicate(predicate); - if (target != null && target.getVisibility() == View.VISIBLE) { + View target = findViewByAccessibilityId(accessibilityId); + if (target != null) { info = target.createAccessibilityNodeInfo(); } } finally { @@ -4794,25 +4822,12 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (root == null) { return null; } - mFindByAccessibilityIdPredicate.init(accessibilityId); - View foundView = root.findViewByPredicate(mFindByAccessibilityIdPredicate); - if (foundView == null || foundView.getVisibility() != View.VISIBLE) { + View foundView = root.findViewByAccessibilityId(accessibilityId); + if (foundView != null && foundView.getVisibility() != View.VISIBLE) { return null; } return foundView; } - - private final class FindByAccessibilitytIdPredicate implements Predicate<View> { - public int mSearchedId; - - public void init(int searchedId) { - mSearchedId = searchedId; - } - - public boolean apply(View view) { - return (view.getAccessibilityViewId() == mSearchedId); - } - } } private class SendWindowContentChangedAccessibilityEvent implements Runnable { @@ -4820,18 +4835,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void run() { if (mView != null) { - // Check again for accessibility state since this is executed delayed. - AccessibilityManager accessibilityManager = - AccessibilityManager.getInstance(mView.mContext); - if (accessibilityManager.isEnabled()) { - // Send the event directly since we do not want to append the - // source text because this is the text for the entire window - // and we just want to notify that the content has changed. - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - mView.onInitializeAccessibilityEvent(event); - accessibilityManager.sendAccessibilityEvent(event); - } + mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); mIsPending = false; } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 17a516c..e8ab227 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -416,6 +416,13 @@ public interface WindowManager extends ViewManager { public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21; /** + * Window type: Fake window to consume touch events when the navigation + * bar is hidden. + * @hide + */ + public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22; + + /** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW = 2999; @@ -1260,8 +1267,6 @@ public interface WindowManager extends ViewManager { /** {@hide} */ public static final int PRIVATE_FLAGS_CHANGED = 1<<16; /** {@hide} */ - public static final int BUFFER_CHANGED = 1<<17; - /** {@hide} */ public static final int EVERYTHING_CHANGED = 0xffffffff; // internal buffer to backup/restore parameters under compatibility mode. @@ -1272,11 +1277,11 @@ public interface WindowManager extends ViewManager { if (width != o.width) { width = o.width; - changes |= LAYOUT_CHANGED | BUFFER_CHANGED; + changes |= LAYOUT_CHANGED; } if (height != o.height) { height = o.height; - changes |= LAYOUT_CHANGED | BUFFER_CHANGED; + changes |= LAYOUT_CHANGED; } if (x != o.x) { x = o.x; @@ -1288,19 +1293,19 @@ public interface WindowManager extends ViewManager { } if (horizontalWeight != o.horizontalWeight) { horizontalWeight = o.horizontalWeight; - changes |= LAYOUT_CHANGED | BUFFER_CHANGED; + changes |= LAYOUT_CHANGED; } if (verticalWeight != o.verticalWeight) { verticalWeight = o.verticalWeight; - changes |= LAYOUT_CHANGED | BUFFER_CHANGED; + changes |= LAYOUT_CHANGED; } if (horizontalMargin != o.horizontalMargin) { horizontalMargin = o.horizontalMargin; - changes |= LAYOUT_CHANGED | BUFFER_CHANGED; + changes |= LAYOUT_CHANGED; } if (verticalMargin != o.verticalMargin) { verticalMargin = o.verticalMargin; - changes |= LAYOUT_CHANGED | BUFFER_CHANGED; + changes |= LAYOUT_CHANGED; } if (type != o.type) { type = o.type; @@ -1308,7 +1313,7 @@ public interface WindowManager extends ViewManager { } if (flags != o.flags) { flags = o.flags; - changes |= FLAGS_CHANGED | BUFFER_CHANGED; + changes |= FLAGS_CHANGED; } if (privateFlags != o.privateFlags) { privateFlags = o.privateFlags; @@ -1320,11 +1325,11 @@ public interface WindowManager extends ViewManager { } if (gravity != o.gravity) { gravity = o.gravity; - changes |= LAYOUT_CHANGED | BUFFER_CHANGED; + changes |= LAYOUT_CHANGED; } if (format != o.format) { format = o.format; - changes |= FORMAT_CHANGED | BUFFER_CHANGED; + changes |= FORMAT_CHANGED; } if (windowAnimations != o.windowAnimations) { windowAnimations = o.windowAnimations; @@ -1363,7 +1368,7 @@ public interface WindowManager extends ViewManager { if (screenOrientation != o.screenOrientation) { screenOrientation = o.screenOrientation; - changes |= SCREEN_ORIENTATION_CHANGED | BUFFER_CHANGED; + changes |= SCREEN_ORIENTATION_CHANGED; } if (systemUiVisibility != o.systemUiVisibility diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 4f67675..17bdff2 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -23,6 +23,7 @@ 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; import java.io.FileDescriptor; @@ -103,23 +104,23 @@ public interface WindowManagerPolicy { */ public final static String EXTRA_HDMI_PLUGGED_STATE = "state"; - // flags for interceptKeyTq /** - * Pass this event to the user / app. To be returned from {@link #interceptKeyTq}. + * Pass this event to the user / app. To be returned from + * {@link #interceptKeyBeforeQueueing}. */ public final static int ACTION_PASS_TO_USER = 0x00000001; /** * This key event should extend the user activity timeout and turn the lights on. - * To be returned from {@link #interceptKeyTq}. Do not return this and - * {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}. + * 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; /** * This key event should put the device to sleep (and engage keyguard if necessary) - * To be returned from {@link #interceptKeyTq}. Do not return this and - * {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}. + * To be returned from {@link #interceptKeyBeforeQueueing}. + * Do not return this and {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}. */ public final static int ACTION_GO_TO_SLEEP = 0x00000004; @@ -237,6 +238,12 @@ public interface WindowManagerPolicy { public WindowManager.LayoutParams getAttrs(); /** + * Retrieve the current system UI visibility flags associated with + * this window. + */ + public int getSystemUiVisibility(); + + /** * Get the layer at which this window's surface will be Z-ordered. */ public int getSurfaceLayer(); @@ -309,6 +316,36 @@ public interface WindowManagerPolicy { } /** + * Representation of a "fake window" that the policy has added to the + * window manager to consume events. + */ + public interface FakeWindow { + /** + * Remove the fake window from the window manager. + */ + void dismiss(); + } + + /** + * Interface for calling back in to the window manager that is private + * between it and the policy. + */ + public interface WindowManagerFuncs { + /** + * Ask the window manager to re-evaluate the system UI flags. + */ + public void reevaluateStatusBarVisibility(); + + /** + * Add a fake window to the window manager. This window sits + * at the top of the other windows and consumes events. + */ + public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler, + String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, + boolean hasFocus, boolean touchFullscreen); + } + + /** * Bit mask that is set for all enter transition. */ public final int TRANSIT_ENTER_MASK = 0x1000; @@ -389,6 +426,7 @@ public interface WindowManagerPolicy { * @param powerManager */ public void init(Context context, IWindowManager windowManager, + WindowManagerFuncs windowManagerFuncs, LocalPowerManager powerManager); /** @@ -639,10 +677,12 @@ public interface WindowManagerPolicy { * event will normally go. * @param event The key event. * @param policyFlags The policy flags associated with the key. - * @return Returns true if the policy consumed the event and it should - * not be further dispatched. + * @return 0 if the key should be dispatched immediately, -1 if the key should + * not be dispatched ever, or a positive value indicating the number of + * milliseconds by which the key dispatch should be delayed before trying + * again. */ - public boolean interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags); + public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags); /** * Called from the input dispatcher thread when an application did not handle @@ -756,7 +796,7 @@ public interface WindowManagerPolicy { /** * A new window has been focused. */ - public void focusChanged(WindowState lastFocus, WindowState newFocus); + public int focusChangedLw(WindowState lastFocus, WindowState newFocus); /** * Called after the screen turns off. @@ -962,6 +1002,14 @@ public interface WindowManagerPolicy { public void setUserRotationMode(int mode, int rotation); /** + * Called when a new system UI visibility is being reported, allowing + * the policy to adjust what is actually reported. + * @param visibility The raw visiblity reported by the status bar. + * @return The new desired visibility. + */ + public int adjustSystemUiVisibilityLw(int visibility); + + /** * Print the WindowManagerPolicy's state into the given stream. * * @param prefix Text to print at the front of each line. diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index c93b564..86dd9df 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -69,14 +69,26 @@ import java.util.List; * <em>Type:</em>{@link #TYPE_VIEW_CLICKED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isChecked()} - Whether the source is checked.</li> + * <li>{@link #getContentDescription()} - The content description of the source.</li> + * <li>{@link #getScrollX()} - The offset of the source left edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getScrollY()} - The offset of the source top edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getItemCount()} - The total items of the source + * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -85,14 +97,26 @@ import java.util.List; * <em>Type:</em>{@link #TYPE_VIEW_LONG_CLICKED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isChecked()} - Whether the source is checked.</li> + * <li>{@link #getContentDescription()} - The content description of the source.</li> + * <li>{@link #getScrollX()} - The offset of the source left edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getScrollY()} - The offset of the source top edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getItemCount()} - The total items of the source + * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -101,16 +125,28 @@ import java.util.List; * <em>Type:</em> {@link #TYPE_VIEW_SELECTED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isChecked()} - Whether the source is checked.</li> * <li>{@link #getItemCount()} - The number of selectable items of the source.</li> * <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li> + * <li>{@link #getContentDescription()} - The content description of the source.</li> + * <li>{@link #getScrollX()} - The offset of the source left edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getScrollY()} - The offset of the source top edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getItemCount()} - The total items of the source + * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -119,16 +155,28 @@ import java.util.List; * <em>Type:</em> {@link #TYPE_VIEW_FOCUSED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isChecked()} - Whether the source is checked.</li> * <li>{@link #getItemCount()} - The number of focusable items on the screen.</li> * <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li> + * <li>{@link #getContentDescription()} - The content description of the source.</li> + * <li>{@link #getScrollX()} - The offset of the source left edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getScrollY()} - The offset of the source top edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getItemCount()} - The total items of the source + * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -137,6 +185,7 @@ import java.util.List; * <em>Type:</em> {@link #TYPE_VIEW_TEXT_CHANGED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> @@ -149,6 +198,7 @@ import java.util.List; * <li>{@link #getAddedCount()} - The number of added characters.</li> * <li>{@link #getRemovedCount()} - The number of removed characters.</li> * <li>{@link #getBeforeText()} - The text of the source before the change.</li> + * <li>{@link #getContentDescription()} - The content description of the source.</li> * </ul> * </p> * <p> @@ -157,96 +207,236 @@ import java.util.List; * <em>Type:</em> {@link #TYPE_VIEW_TEXT_SELECTION_CHANGED} </br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> * <li>{@link #getText()} - The text of the source.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #getFromIndex()} - The selection start index.</li> * <li>{@link #getToIndex()} - The selection end index.</li> * <li>{@link #getItemCount()} - The length of the source text.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #getContentDescription()} - The content description of the source.</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> * </p> * <p> * <b>View scrolled</b> - represents the event of scrolling a view. If * the source is a descendant of {@link android.widget.AdapterView} the * scroll is reported in terms of visible items - the first visible item, * the last visible item, and the total items - because the the source - * is unaware if its pixel size since its adapter is responsible for + * is unaware of its pixel size since its adapter is responsible for * creating views. In all other cases the scroll is reported as the current * scroll on the X and Y axis respectively plus the height of the source in * pixels.</br> * <em>Type:</em> {@link #TYPE_VIEW_SCROLLED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #getScrollX()} - The horizontal offset of the source - * (without descendants of AdapterView)).</li> - * <li>{@link #getScrollY()} - The vertical offset of the source - * (without descendants of AdapterView)).</li> - * <li>{@link #getFromIndex()} - The index of the first visible item of the source - * (for descendants of AdapterView).</li> - * <li>{@link #getToIndex()} - The index of the last visible item of the source - * (for descendants of AdapterView).</li> - * <li>{@link #getItemCount()} - The total items of the source (for descendants of AdapterView) - * or the height of the source in pixels (all other cases).</li> + * <li>{@link #getContentDescription()} - The content description of the source.</li> + * <li>{@link #getScrollX()} - The offset of the source left edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getScrollY()} - The offset of the source top edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getItemCount()} - The total items of the source + * (for descendants of AdapterView).</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> * </p> * <p> * <b>TRANSITION TYPES</b></br> * </p> + * <p> * <b>Window state changed</b> - represents the event of opening a * {@link android.widget.PopupWindow}, {@link android.view.Menu}, * {@link android.app.Dialog}, etc.</br> * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * </ul> * </p> * <p> * <b>Window content changed</b> - represents the event of change in the * content of a window. This change can be adding/removing view, changing * a view size, etc.</br> + * </p> * <p> * <strong>Note:</strong> This event is fired only for the window source of the - * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED}) + * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED} * and its purpose is to notify clients that the content of the user interaction - * window has changed. - * </p> + * window has changed.</br> * <em>Type:</em> {@link #TYPE_WINDOW_CONTENT_CHANGED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> * </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> + * </p> * <p> * <b>NOTIFICATION TYPES</b></br> + * </p> * <p> * <b>Notification state changed</b> - represents the event showing - * {@link android.app.Notification}. + * {@link android.app.Notification}.</br> * <em>Type:</em> {@link #TYPE_NOTIFICATION_STATE_CHANGED}</br> * <em>Properties:</em></br> * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> * <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}.</li> + * <li>{@link #getText()} - Text for providing more context.</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> + * </p> + * <p> + * <b>EXPLORATION TYPES</b></br> + * </p> + * <p> + * <b>View hover enter</b> - represents the event of beginning to hover + * over a {@link android.view.View}. The hover may be generated via + * exploring the screen by touch or via a pointing device.</br> + * <em>Type:</em> {@link #TYPE_VIEW_HOVER_ENTER}</br> + * <em>Properties:</em></br> + * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> + * <li>{@link #getSource()} - The source info (for registered clients).</li> + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #getContentDescription()} - The content description of the source.</li> + * <li>{@link #getScrollX()} - The offset of the source left edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getScrollY()} - The offset of the source top edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getItemCount()} - The total items of the source + * (for descendants of AdapterView).</li> + * </ul> + * </p> + * <b>View hover exit</b> - represents the event of stopping to hover + * over a {@link android.view.View}. The hover may be generated via + * exploring the screen by touch or via a pointing device.</br> + * <em>Type:</em> {@link #TYPE_VIEW_HOVER_EXIT}</br> + * <em>Properties:</em></br> + * <ul> + * <li>{@link #getEventType()} - The type of the event.</li> + * <li>{@link #getSource()} - The source info (for registered clients).</li> + * <li>{@link #getClassName()} - The class name of the source.</li> + * <li>{@link #getPackageName()} - The package name of the source.</li> + * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> + * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #getContentDescription()} - The content description of the source.</li> + * <li>{@link #getScrollX()} - The offset of the source left edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getScrollY()} - The offset of the source top edge in pixels + * (without descendants of AdapterView).</li> + * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, + * inclusive (for descendants of AdapterView).</li> + * <li>{@link #getItemCount()} - The total items of the source + * (for descendants of AdapterView).</li> + * </ul> + * </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> + * <em>Properties:</em></br> + * <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> + * </p> + * <p> + * <b>Touch exploration gesture end</b> - represents the event of ending a touch + * exploring gesture.</br> + * <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_END}</br> + * <em>Properties:</em></br> + * <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> * </p> * <p> * <b>Security note</b> @@ -254,6 +444,7 @@ import java.util.List; * Since an event contains the text of its source privacy can be compromised by leaking * sensitive information such as passwords. To address this issue any event fired in response * to manipulation of a PASSWORD field does NOT CONTAIN the text of the password. + * </p> * * @see android.view.accessibility.AccessibilityManager * @see android.accessibilityservice.AccessibilityService @@ -675,6 +866,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par record.mToIndex = parcel.readInt(); record.mScrollX = parcel.readInt(); record.mScrollY = parcel.readInt(); + record.mMaxScrollX = parcel.readInt(); + record.mMaxScrollY = parcel.readInt(); record.mAddedCount = parcel.readInt(); record.mRemovedCount = parcel.readInt(); record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); @@ -727,6 +920,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par parcel.writeInt(record.mToIndex); parcel.writeInt(record.mScrollX); parcel.writeInt(record.mScrollY); + parcel.writeInt(record.mMaxScrollX); + parcel.writeInt(record.mMaxScrollY); parcel.writeInt(record.mAddedCount); parcel.writeInt(record.mRemovedCount); TextUtils.writeToParcel(record.mClassName, parcel, flags); diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index afd7473..a4e0688 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -73,6 +73,8 @@ public class AccessibilityRecord { int mToIndex = UNDEFINED; int mScrollX = UNDEFINED; int mScrollY = UNDEFINED; + int mMaxScrollX = UNDEFINED; + int mMaxScrollY = UNDEFINED; int mAddedCount= UNDEFINED; int mRemovedCount = UNDEFINED; @@ -348,18 +350,18 @@ public class AccessibilityRecord { } /** - * Gets the scroll position of the source along the X axis. + * Gets the scroll offset of the source left edge in pixels. * - * @return The scroll along the X axis. + * @return The scroll. */ public int getScrollX() { return mScrollX; } /** - * Sets the scroll position of the source along the X axis. + * Sets the scroll offset of the source left edge in pixels. * - * @param scrollX The scroll along the X axis. + * @param scrollX The scroll. */ public void setScrollX(int scrollX) { enforceNotSealed(); @@ -367,18 +369,18 @@ public class AccessibilityRecord { } /** - * Gets the scroll position of the source along the Y axis. + * Gets the scroll offset of the source top edge in pixels. * - * @return The scroll along the Y axis. + * @return The scroll. */ public int getScrollY() { return mScrollY; } /** - * Sets the scroll position of the source along the Y axis. + * Sets the scroll offset of the source top edge in pixels. * - * @param scrollY The scroll along the Y axis. + * @param scrollY The scroll. */ public void setScrollY(int scrollY) { enforceNotSealed(); @@ -386,6 +388,43 @@ public class AccessibilityRecord { } /** + * Gets the max scroll offset of the source left edge in pixels. + * + * @return The max scroll. + */ + public int getMaxScrollX() { + return mMaxScrollX; + } + /** + * Sets the max scroll offset of the source left edge in pixels. + * + * @param maxScrollX The max scroll. + */ + public void setMaxScrollX(int maxScrollX) { + enforceNotSealed(); + mMaxScrollX = maxScrollX; + } + + /** + * Gets the max scroll offset of the source top edge in pixels. + * + * @return The max scroll. + */ + public int getMaxScrollY() { + return mMaxScrollY; + } + + /** + * Sets the max scroll offset of the source top edge in pixels. + * + * @param maxScrollY The max scroll. + */ + public void setMaxScrollY(int maxScrollY) { + enforceNotSealed(); + mMaxScrollY = maxScrollY; + } + + /** * Gets the number of added characters. * * @return The number of added characters. @@ -658,6 +697,8 @@ public class AccessibilityRecord { mToIndex = record.mToIndex; mScrollX = record.mScrollX; mScrollY = record.mScrollY; + mMaxScrollX = record.mMaxScrollX; + mMaxScrollY = record.mMaxScrollY; mAddedCount = record.mAddedCount; mRemovedCount = record.mRemovedCount; mClassName = record.mClassName; @@ -682,6 +723,8 @@ public class AccessibilityRecord { mToIndex = UNDEFINED; mScrollX = UNDEFINED; mScrollY = UNDEFINED; + mMaxScrollX = UNDEFINED; + mMaxScrollY = UNDEFINED; mAddedCount = UNDEFINED; mRemovedCount = UNDEFINED; mClassName = null; @@ -711,6 +754,8 @@ public class AccessibilityRecord { builder.append("; ToIndex: " + mToIndex); builder.append("; ScrollX: " + mScrollX); builder.append("; ScrollY: " + mScrollY); + builder.append("; MaxScrollX: " + mMaxScrollX); + builder.append("; MaxScrollY: " + mMaxScrollY); builder.append("; AddedCount: " + mAddedCount); builder.append("; RemovedCount: " + mRemovedCount); builder.append("; ParcelableData: " + mParcelableData); diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 28f54aa..2cc928f 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -43,7 +43,6 @@ import junit.framework.Assert; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; -import java.net.URL; import java.net.URLEncoder; import java.nio.charset.Charsets; import java.security.PrivateKey; @@ -472,8 +471,7 @@ class BrowserFrame extends Handler { /** * We have received an SSL certificate for the main top-level page. - * - * !!!Called from the network thread!!! + * Used by the Android HTTP stack only. */ void certificate(SslCertificate certificate) { if (mIsMainFrame) { @@ -1171,12 +1169,7 @@ class BrowserFrame extends Handler { try { X509Certificate cert = new X509CertImpl(certDER); SslCertificate sslCert = new SslCertificate(cert); - if (JniUtil.useChromiumHttpStack()) { - sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert, - new URL(url).getHost()); - } else { - sslError = new SslError(certError, cert, url); - } + sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert, url); } catch (IOException e) { // Can't get the certificate, not much to do. Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling"); @@ -1186,18 +1179,18 @@ class BrowserFrame extends Handler { if (SslCertLookupTable.getInstance().isAllowed(sslError)) { nativeSslCertErrorProceed(handle); + mCallbackProxy.onProceededAfterSslError(sslError); return; } SslErrorHandler handler = new SslErrorHandler() { @Override public void proceed() { - SslCertLookupTable.getInstance().setIsAllowed(sslError, true); + SslCertLookupTable.getInstance().setIsAllowed(sslError); nativeSslCertErrorProceed(handle); } @Override public void cancel() { - SslCertLookupTable.getInstance().setIsAllowed(sslError, false); nativeSslCertErrorCancel(handle, certError); } }; @@ -1211,22 +1204,20 @@ class BrowserFrame extends Handler { * We delegate the request to CallbackProxy, and route its response to * {@link #nativeSslClientCert(int, X509Certificate)}. */ - private void requestClientCert(int handle, byte[] host_and_port_bytes) { - String host_and_port = new String(host_and_port_bytes, Charsets.UTF_8); + private void requestClientCert(int handle, String hostAndPort) { SslClientCertLookupTable table = SslClientCertLookupTable.getInstance(); - if (table.IsAllowed(host_and_port)) { + if (table.IsAllowed(hostAndPort)) { // previously allowed nativeSslClientCert(handle, - table.PrivateKey(host_and_port), - table.CertificateChain(host_and_port)); - } else if (table.IsDenied(host_and_port)) { + table.PrivateKey(hostAndPort), + table.CertificateChain(hostAndPort)); + } else if (table.IsDenied(hostAndPort)) { // previously denied nativeSslClientCert(handle, null, null); } else { // previously ignored or new mCallbackProxy.onReceivedClientCertRequest( - new ClientCertRequestHandler(this, handle, host_and_port, table), - host_and_port); + new ClientCertRequestHandler(this, handle, hostAndPort, table), hostAndPort); } } @@ -1276,7 +1267,8 @@ class BrowserFrame extends Handler { } /** - * Called by JNI when we load a page over SSL. + * Called by JNI when we recieve a certificate for the page's main resource. + * Used by the Chromium HTTP stack only. */ private void setCertificate(byte cert_der[]) { try { diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 5ee90a4..75ee338 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -77,53 +77,54 @@ class CallbackProxy extends Handler { // Used to call startActivity during url override. private final Context mContext; - // Message Ids - private static final int PAGE_STARTED = 100; - private static final int RECEIVED_ICON = 101; - private static final int RECEIVED_TITLE = 102; - private static final int OVERRIDE_URL = 103; - private static final int AUTH_REQUEST = 104; - private static final int SSL_ERROR = 105; - private static final int PROGRESS = 106; - private static final int UPDATE_VISITED = 107; - private static final int LOAD_RESOURCE = 108; - private static final int CREATE_WINDOW = 109; - private static final int CLOSE_WINDOW = 110; - private static final int SAVE_PASSWORD = 111; - private static final int JS_ALERT = 112; - private static final int JS_CONFIRM = 113; - private static final int JS_PROMPT = 114; - private static final int JS_UNLOAD = 115; - private static final int ASYNC_KEYEVENTS = 116; - private static final int DOWNLOAD_FILE = 118; - private static final int REPORT_ERROR = 119; - private static final int RESEND_POST_DATA = 120; - private static final int PAGE_FINISHED = 121; - private static final int REQUEST_FOCUS = 122; - private static final int SCALE_CHANGED = 123; - private static final int RECEIVED_CERTIFICATE = 124; - private static final int SWITCH_OUT_HISTORY = 125; - private static final int EXCEEDED_DATABASE_QUOTA = 126; - private static final int REACHED_APPCACHE_MAXSIZE = 127; - private static final int JS_TIMEOUT = 128; - private static final int ADD_MESSAGE_TO_CONSOLE = 129; - private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT = 130; - private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131; - private static final int RECEIVED_TOUCH_ICON_URL = 132; - private static final int GET_VISITED_HISTORY = 133; - private static final int OPEN_FILE_CHOOSER = 134; - 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; + // Message IDs + private static final int PAGE_STARTED = 100; + private static final int RECEIVED_ICON = 101; + private static final int RECEIVED_TITLE = 102; + private static final int OVERRIDE_URL = 103; + private static final int AUTH_REQUEST = 104; + private static final int SSL_ERROR = 105; + private static final int PROGRESS = 106; + private static final int UPDATE_VISITED = 107; + private static final int LOAD_RESOURCE = 108; + private static final int CREATE_WINDOW = 109; + private static final int CLOSE_WINDOW = 110; + private static final int SAVE_PASSWORD = 111; + private static final int JS_ALERT = 112; + private static final int JS_CONFIRM = 113; + private static final int JS_PROMPT = 114; + private static final int JS_UNLOAD = 115; + private static final int ASYNC_KEYEVENTS = 116; + private static final int DOWNLOAD_FILE = 118; + private static final int REPORT_ERROR = 119; + private static final int RESEND_POST_DATA = 120; + private static final int PAGE_FINISHED = 121; + private static final int REQUEST_FOCUS = 122; + private static final int SCALE_CHANGED = 123; + private static final int RECEIVED_CERTIFICATE = 124; + private static final int SWITCH_OUT_HISTORY = 125; + private static final int EXCEEDED_DATABASE_QUOTA = 126; + private static final int REACHED_APPCACHE_MAXSIZE = 127; + private static final int JS_TIMEOUT = 128; + private static final int ADD_MESSAGE_TO_CONSOLE = 129; + private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT = 130; + private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131; + private static final int RECEIVED_TOUCH_ICON_URL = 132; + private static final int GET_VISITED_HISTORY = 133; + private static final int OPEN_FILE_CHOOSER = 134; + 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 - private static final int NOTIFY = 200; + private static final int NOTIFY = 200; // Result transportation object for returning results across thread // boundaries. @@ -349,6 +350,13 @@ class CallbackProxy extends Handler { } break; + case PROCEEDED_AFTER_SSL_ERROR: + if (mWebViewClient != null) { + mWebViewClient.onProceededAfterSslError(mWebView, + (SslError) msg.obj); + } + break; + case CLIENT_CERT_REQUEST: if (mWebViewClient != null) { HashMap<String, Object> map = @@ -1024,6 +1032,15 @@ class CallbackProxy extends Handler { sendMessage(msg); } + public void onProceededAfterSslError(SslError error) { + if (mWebViewClient == null) { + return; + } + Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR); + msg.obj = error; + sendMessage(msg); + } + public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) { // Do an unsynchronized quick check to avoid posting if no callback has // been set. diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java index a322fa3..b85fd17 100644 --- a/core/java/android/webkit/FindActionModeCallback.java +++ b/core/java/android/webkit/FindActionModeCallback.java @@ -22,16 +22,14 @@ import android.text.Editable; import android.text.Selection; import android.text.Spannable; import android.text.TextWatcher; -import android.webkit.WebView; -import android.widget.EditText; -import android.widget.TextView; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; class FindActionModeCallback implements ActionMode.Callback, TextWatcher, View.OnLongClickListener, View.OnClickListener { @@ -180,6 +178,14 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher, @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { + if (!mode.isUiFocusable()) { + // If the action mode we're running in is not focusable the user + // will not be able to type into the find on page field. This + // should only come up when we're running in a dialog which is + // already less than ideal; disable the option for now. + return false; + } + mode.setCustomView(mCustomView); mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find, menu); @@ -195,6 +201,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher, @Override public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; mWebView.notifyFindDialogDismissed(); mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0); } @@ -247,4 +254,13 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher, // Does nothing. Needed to implement TextWatcher. } + public int getActionModeHeight() { + if (mActionMode == null) { + return 0; + } + View parent = (View) mCustomView.getParent(); + return parent != null ? parent.getMeasuredHeight() + : mCustomView.getMeasuredHeight(); + } + } diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java index 3600d09..9fc48a1 100644 --- a/core/java/android/webkit/HTML5Audio.java +++ b/core/java/android/webkit/HTML5Audio.java @@ -16,12 +16,9 @@ package android.webkit; +import android.content.Context; +import android.media.AudioManager; import android.media.MediaPlayer; -import android.media.MediaPlayer.OnBufferingUpdateListener; -import android.media.MediaPlayer.OnCompletionListener; -import android.media.MediaPlayer.OnErrorListener; -import android.media.MediaPlayer.OnPreparedListener; -import android.media.MediaPlayer.OnSeekCompleteListener; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -45,7 +42,8 @@ class HTML5Audio extends Handler MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, MediaPlayer.OnPreparedListener, - MediaPlayer.OnSeekCompleteListener { + MediaPlayer.OnSeekCompleteListener, + AudioManager.OnAudioFocusChangeListener { // Logging tag. private static final String LOGTAG = "HTML5Audio"; @@ -69,6 +67,7 @@ class HTML5Audio extends Handler private String mUrl; private boolean mAskToPlay = false; + private Context mContext; // Timer thread -> UI thread private static final int TIMEUPDATE = 100; @@ -183,6 +182,7 @@ class HTML5Audio extends Handler // Save the native ptr mNativePointer = nativePtr; resetMediaPlayer(); + mContext = webViewCore.getContext(); mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter( webViewCore.getContext().getMainLooper(), webViewCore.getWebView()); } @@ -233,6 +233,34 @@ class HTML5Audio extends Handler } } + @Override + public void onAudioFocusChange(int focusChange) { + switch (focusChange) { + case AudioManager.AUDIOFOCUS_GAIN: + // resume playback + if (mMediaPlayer == null) resetMediaPlayer(); + else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start(); + mState = STARTED; + break; + + case AudioManager.AUDIOFOCUS_LOSS: + // Lost focus for an unbounded amount of time: stop playback and release media player + if (mMediaPlayer.isPlaying()) mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + // Lost focus for a short time, but we have to stop + // playback. We don't release the media player because playback + // is likely to resume + if (mMediaPlayer.isPlaying()) mMediaPlayer.pause(); + break; + } + } + + private void play() { if ((mState >= ERROR && mState < PREPARED) && mUrl != null) { resetMediaPlayer(); @@ -241,8 +269,17 @@ class HTML5Audio extends Handler } if (mState >= PREPARED) { - mMediaPlayer.start(); - mState = STARTED; + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + + if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + // could not get audio focus. + teardown(); + } else { + mMediaPlayer.start(); + mState = STARTED; + } } } @@ -276,4 +313,5 @@ class HTML5Audio extends Handler private native void nativeOnEnded(int nativePointer); private native void nativeOnPrepared(int duration, int width, int height, int nativePointer); private native void nativeOnTimeupdate(int position, int nativePointer); + } diff --git a/core/java/android/webkit/SelectActionModeCallback.java b/core/java/android/webkit/SelectActionModeCallback.java index 104deb1..8c174aa 100644 --- a/core/java/android/webkit/SelectActionModeCallback.java +++ b/core/java/android/webkit/SelectActionModeCallback.java @@ -17,13 +17,12 @@ package android.webkit; import android.app.SearchManager; +import android.content.Context; import android.content.Intent; import android.provider.Browser; -import android.webkit.WebView; import android.view.ActionMode; import android.view.Menu; import android.view.MenuItem; -import android.view.View; class SelectActionModeCallback implements ActionMode.Callback { private WebView mWebView; @@ -45,9 +44,25 @@ class SelectActionModeCallback implements ActionMode.Callback { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_copy, - menu); - mode.setTitle(com.android.internal.R.string.textSelectionCABTitle); + mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_copy, menu); + + final Context context = mWebView.getContext(); + boolean allowText = context.getResources().getBoolean( + com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); + mode.setTitle(allowText ? + context.getString(com.android.internal.R.string.textSelectionCABTitle) : null); + + if (!mode.isUiFocusable()) { + // If the action mode UI we're running in isn't capable of taking window focus + // the user won't be able to type into the find on page UI. Disable this functionality. + // (Note that this should only happen in floating dialog windows.) + // This can be removed once we can handle multiple focusable windows at a time + // in a better way. + final MenuItem findOnPageItem = menu.findItem(com.android.internal.R.id.find); + if (findOnPageItem != null) { + findOnPageItem.setVisible(false); + } + } mActionMode = mode; return true; } diff --git a/core/java/android/webkit/SslCertLookupTable.java b/core/java/android/webkit/SslCertLookupTable.java index 052244f..98ace4f 100644 --- a/core/java/android/webkit/SslCertLookupTable.java +++ b/core/java/android/webkit/SslCertLookupTable.java @@ -19,13 +19,18 @@ package android.webkit; import android.os.Bundle; import android.net.http.SslError; +import java.net.MalformedURLException; +import java.net.URL; + /** * Stores the user's decision of whether to allow or deny an invalid certificate. * - * This class is not threadsafe. It is used only on the WebCore thread. + * This class is not threadsafe. It is used only on the WebCore thread. Also, it + * is used only by the Chromium HTTP stack. */ final class SslCertLookupTable { private static SslCertLookupTable sTable; + // We store the most severe error we're willing to allow for each host. private final Bundle table; public static SslCertLookupTable getInstance() { @@ -39,12 +44,26 @@ final class SslCertLookupTable { table = new Bundle(); } - public void setIsAllowed(SslError sslError, boolean allow) { - table.putBoolean(sslError.toString(), allow); + public void setIsAllowed(SslError sslError) { + String host; + try { + host = new URL(sslError.getUrl()).getHost(); + } catch(MalformedURLException e) { + return; + } + table.putInt(host, sslError.getPrimaryError()); } + // We allow the decision to be re-used if it's for the same host and is for + // an error of equal or greater severity than this error. public boolean isAllowed(SslError sslError) { - return table.getBoolean(sslError.toString()); + String host; + try { + host = new URL(sslError.getUrl()).getHost(); + } catch(MalformedURLException e) { + return false; + } + return table.containsKey(host) && sslError.getPrimaryError() <= table.getInt(host); } public void clear() { diff --git a/core/java/android/webkit/SslErrorHandlerImpl.java b/core/java/android/webkit/SslErrorHandlerImpl.java index e029e37..b2e4b13 100644 --- a/core/java/android/webkit/SslErrorHandlerImpl.java +++ b/core/java/android/webkit/SslErrorHandlerImpl.java @@ -16,8 +16,6 @@ package android.webkit; -import junit.framework.Assert; - import android.net.http.SslError; import android.os.Bundle; import android.os.Handler; @@ -54,7 +52,7 @@ class SslErrorHandlerImpl extends SslErrorHandler { private final SslErrorHandler mOriginHandler; private final LoadListener mLoadListener; - // Message id for handling the response + // Message id for handling the response from the client. private static final int HANDLE_RESPONSE = 100; @Override @@ -130,7 +128,9 @@ class SslErrorHandlerImpl extends SslErrorHandler { } /** - * Handles SSL error(s) on the way up to the user. + * Handles requests from the network stack about whether to proceed with a + * load given an SSL error(s). We may ask the client what to do, or use a + * cached response. */ /* package */ synchronized void handleSslErrorRequest(LoadListener loader) { if (DebugFlags.SSL_ERROR_HANDLER) { @@ -147,8 +147,10 @@ class SslErrorHandlerImpl extends SslErrorHandler { } /** - * Check the preference table for a ssl error that has already been shown - * to the user. + * Check the preference table to see if we already have a 'proceed' decision + * from the client for this host and for an error of equal or greater + * severity than the supplied error. If so, instruct the loader to proceed + * and return true. Otherwise return false. */ /* package */ synchronized boolean checkSslPrefTable(LoadListener loader, SslError error) { @@ -156,21 +158,22 @@ class SslErrorHandlerImpl extends SslErrorHandler { final int primary = error.getPrimaryError(); if (DebugFlags.SSL_ERROR_HANDLER) { - Assert.assertTrue(host != null && primary != 0); + assert host != null; + assert primary != -1; } - if (mSslPrefTable.containsKey(host)) { - if (primary <= mSslPrefTable.getInt(host)) { - handleSslErrorResponse(loader, error, true); - return true; + if (mSslPrefTable.containsKey(host) && primary <= mSslPrefTable.getInt(host)) { + if (!loader.cancelled()) { + loader.handleSslErrorResponse(true); } + return true; } return false; } /** * Processes queued SSL-error confirmation requests in - * a tight loop while there is no need to ask the user. + * a tight loop while there is no need to ask the client. */ /* package */void fastProcessQueuedSslErrors() { while (processNextLoader()); @@ -195,19 +198,18 @@ class SslErrorHandlerImpl extends SslErrorHandler { SslError error = loader.sslError(); if (DebugFlags.SSL_ERROR_HANDLER) { - Assert.assertNotNull(error); + assert error != null; } - // checkSslPrefTable will handle the ssl error response if the - // answer is available. It does not remove the loader from the - // queue. + // checkSslPrefTable() will instruct the loader to proceed if we + // have a cached 'proceed' decision. It does not remove the loader + // from the queue. if (checkSslPrefTable(loader, error)) { mLoaderQueue.remove(loader); return true; } - // if we do not have information on record, ask - // the user (display a dialog) + // If we can not proceed based on a cached decision, ask the client. CallbackProxy proxy = loader.getFrame().getCallbackProxy(); proxy.onReceivedSslError(new SslErrorHandlerImpl(this, loader), error); } @@ -217,32 +219,31 @@ class SslErrorHandlerImpl extends SslErrorHandler { } /** - * Proceed with the SSL certificate. + * Proceed with this load. */ public void proceed() { - mOriginHandler.sendMessage( - mOriginHandler.obtainMessage( - HANDLE_RESPONSE, 1, 0, mLoadListener)); + mOriginHandler.sendMessage(mOriginHandler.obtainMessage( + HANDLE_RESPONSE, 1, 0, mLoadListener)); } /** - * Cancel this request and all pending requests for the WebView that had - * the error. + * Cancel this load and all pending loads for the WebView that had the + * error. */ public void cancel() { - mOriginHandler.sendMessage( - mOriginHandler.obtainMessage( - HANDLE_RESPONSE, 0, 0, mLoadListener)); + mOriginHandler.sendMessage(mOriginHandler.obtainMessage( + HANDLE_RESPONSE, 0, 0, mLoadListener)); } /** - * Handles SSL error(s) on the way down from the user. + * Handles the response from the client about whether to proceed with this + * load. We save the response to be re-used in the future. */ /* package */ synchronized void handleSslErrorResponse(LoadListener loader, SslError error, boolean proceed) { if (DebugFlags.SSL_ERROR_HANDLER) { - Assert.assertNotNull(loader); - Assert.assertNotNull(error); + assert loader != null; + assert error != null; } if (DebugFlags.SSL_ERROR_HANDLER) { @@ -253,16 +254,16 @@ class SslErrorHandlerImpl extends SslErrorHandler { if (!loader.cancelled()) { if (proceed) { - // update the user's SSL error preference table + // Update the SSL error preference table int primary = error.getPrimaryError(); String host = loader.host(); if (DebugFlags.SSL_ERROR_HANDLER) { - Assert.assertTrue(host != null && primary != 0); + assert host != null; + assert primary != -1; } boolean hasKey = mSslPrefTable.containsKey(host); - if (!hasKey || - primary > mSslPrefTable.getInt(host)) { + if (!hasKey || primary > mSslPrefTable.getInt(host)) { mSslPrefTable.putInt(host, primary); } } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 9c44138..f1c2bde 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -211,6 +211,7 @@ public class WebSettings { private ZoomDensity mDefaultZoom = ZoomDensity.MEDIUM; private RenderPriority mRenderPriority = RenderPriority.NORMAL; private int mOverrideCacheMode = LOAD_DEFAULT; + private int mDoubleTapZoom = 100; private boolean mSaveFormData = true; private boolean mAutoFillEnabled = false; private boolean mSavePassword = true; @@ -769,6 +770,27 @@ public class WebSettings { } /** + * 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. + * @hide + */ + public void setDoubleTapZoom(int doubleTapZoom) { + if (mDoubleTapZoom != doubleTapZoom) { + mDoubleTapZoom = doubleTapZoom; + mWebView.updateDoubleTapZoom(); + } + } + + /** + * Get the double-tap zoom of the page in percent. + * @return A percent value describing the double-tap zoom. + * @hide + */ + public int getDoubleTapZoom() { + return mDoubleTapZoom; + } + + /** * Set the default zoom density of the page. This should be called from UI * thread. * @param zoom A ZoomDensity value diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 217ad7c..b0ecf09 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -768,9 +768,13 @@ import junit.framework.Assert; imm.hideSoftInputFromWindow(getWindowToken(), 0); } mInsideRemove = true; + boolean isFocused = hasFocus(); mWebView.removeView(this); - mWebView.requestFocus(); + if (isFocused) { + mWebView.requestFocus(); + } mInsideRemove = false; + mHandler.removeCallbacksAndMessages(null); } @Override @@ -828,7 +832,7 @@ import junit.framework.Assert; TextView tv = (TextView) super.getView(position, convertView, parent); if (tv != null && mTextView != null) { - tv.setTextSize(mTextView.getTextSize()); + tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextView.getTextSize()); } return tv; } @@ -892,7 +896,10 @@ import junit.framework.Assert; * WebTextView represents. */ /* package */ void setNodePointer(int ptr) { - mNodePointer = ptr; + if (ptr != mNodePointer) { + mNodePointer = ptr; + setAdapterCustom(null); + } } /** @@ -995,6 +1002,9 @@ import junit.framework.Assert; | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN; + if (!mWebView.nativeFocusCandidateIsSpellcheck()) { + inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; + } if (TEXT_AREA != type && mWebView.nativeFocusCandidateHasNextTextfield()) { imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; @@ -1047,11 +1057,12 @@ import junit.framework.Assert; } setHint(null); setThreshold(1); + boolean autoComplete = false; if (single) { mWebView.requestLabel(mWebView.nativeFocusCandidateFramePointer(), mNodePointer); maxLength = mWebView.nativeFocusCandidateMaxLength(); - boolean autoComplete = mWebView.nativeFocusCandidateIsAutoComplete(); + autoComplete = mWebView.nativeFocusCandidateIsAutoComplete(); if (type != PASSWORD && (mAutoFillable || autoComplete)) { String name = mWebView.nativeFocusCandidateName(); if (name != null && name.length() > 0) { @@ -1066,8 +1077,9 @@ import junit.framework.Assert; setInputType(inputType); setImeOptions(imeOptions); setVisibility(VISIBLE); - AutoCompleteAdapter adapter = null; - setAdapterCustom(adapter); + if (!autoComplete) { + setAdapterCustom(null); + } } /** @@ -1092,4 +1104,11 @@ import junit.framework.Assert; return url != null ? url.getProtocol() + "://" + url.getHost() + url.getPath() : null; } + + public void setGravityForRtl(boolean rtl) { + int gravity = rtl ? Gravity.RIGHT : Gravity.LEFT; + gravity |= mSingle ? Gravity.CENTER_VERTICAL : Gravity.TOP; + setGravity(gravity); + } + } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 370cce4..8da1820 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -77,7 +77,9 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; @@ -1303,6 +1305,31 @@ public class WebView extends AbsoluteLayout } @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(isScrollableForAccessibility()); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(isScrollableForAccessibility()); + event.setScrollX(mScrollX); + event.setScrollY(mScrollY); + final int convertedContentWidth = contentToViewX(getContentWidth()); + final int adjustedViewWidth = getWidth() - mPaddingLeft - mPaddingRight; + event.setMaxScrollX(Math.max(convertedContentWidth - adjustedViewWidth, 0)); + final int convertedContentHeight = contentToViewY(getContentHeight()); + final int adjustedViewHeight = getHeight() - mPaddingTop - mPaddingBottom; + event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0)); + } + + private boolean isScrollableForAccessibility() { + return (contentToViewX(getContentWidth()) > getWidth() - mPaddingLeft - mPaddingRight + || contentToViewY(getContentHeight()) > getHeight() - mPaddingTop - mPaddingBottom); + } + + @Override public void setOverScrollMode(int mode) { super.setOverScrollMode(mode); if (mode != OVER_SCROLL_NEVER) { @@ -1456,7 +1483,8 @@ public class WebView extends AbsoluteLayout private int getVisibleTitleHeightImpl() { // need to restrict mScrollY due to over scroll - return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0); + return Math.max(getTitleHeight() - Math.max(0, mScrollY), + mFindCallback != null ? mFindCallback.getActionModeHeight() : 0); } /* @@ -1581,6 +1609,11 @@ public class WebView extends AbsoluteLayout mListBoxDialog.dismiss(); mListBoxDialog = null; } + // remove so that it doesn't cause events + if (mWebTextView != null) { + mWebTextView.remove(); + mWebTextView = null; + } if (mNativeClass != 0) nativeStopGL(); if (mWebViewCore != null) { // Set the handlers to null before destroying WebViewCore so no @@ -2041,7 +2074,13 @@ public class WebView extends AbsoluteLayout * If the value of the encoding parameter is 'base64', then the data must * be encoded as base64. Otherwise, the data must use ASCII encoding for * octets inside the range of safe URL characters and use the standard %xx - * hex encoding of URLs for octets outside that range. + * hex encoding of URLs for octets outside that range. For example, + * '#', '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively. + * <p> + * The 'data' scheme URL formed by this method uses the default US-ASCII + * charset. If you need need to set a different charset, you should form a + * 'data' scheme URL which specifies a charset parameter and call + * {@link #loadUrl(String)} instead. * @param data A String of data in the given encoding. * @param mimeType The MIMEType of the data, e.g. 'text/html'. * @param encoding The encoding of the data. @@ -2947,6 +2986,13 @@ public class WebView extends AbsoluteLayout return false; } + /** + * Update the double-tap zoom. + */ + /* package */ void updateDoubleTapZoom() { + mZoomManager.updateDoubleTapZoom(); + } + private int computeRealHorizontalScrollRange() { if (mDrawHistory) { return mHistoryWidth; @@ -3241,8 +3287,7 @@ public class WebView extends AbsoluteLayout public void clearFormData() { checkThread(); if (inEditingMode()) { - AutoCompleteAdapter adapter = null; - mWebTextView.setAdapterCustom(adapter); + mWebTextView.setAdapterCustom(null); } } @@ -4078,8 +4123,8 @@ public class WebView extends AbsoluteLayout boolean pressed = (mTouchMode == TOUCH_SHORTPRESS_START_MODE || mTouchMode == TOUCH_INIT_MODE || mTouchMode == TOUCH_SHORTPRESS_MODE); - nativeRecordButtons(hasFocus() && hasWindowFocus(), - (pressed && !USE_WEBKIT_RINGS) + recordButtons(canvas, + hasFocus() && hasWindowFocus(), (pressed && !USE_WEBKIT_RINGS) || mTrackballDown || mGotCenterDown, false); drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing && drawRings); @@ -4760,12 +4805,12 @@ public class WebView extends AbsoluteLayout } String text = nativeFocusCandidateText(); int nodePointer = nativeFocusCandidatePointer(); - mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ? - Gravity.RIGHT : Gravity.NO_GRAVITY); // This needs to be called before setType, which may call // requestFormData, and it needs to have the correct nodePointer. mWebTextView.setNodePointer(nodePointer); mWebTextView.setType(nativeFocusCandidateType()); + // Gravity needs to be set after setType + mWebTextView.setGravityForRtl(nativeFocusCandidateIsRtlText()); updateWebTextViewPadding(); if (null == text) { if (DebugFlags.WEB_VIEW) { @@ -5133,7 +5178,7 @@ public class WebView extends AbsoluteLayout .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); // Already checked mNativeClass, so we do not need to check it // again. - nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); + recordButtons(null, hasFocus() && hasWindowFocus(), true, true); if (!wantsKeyEvents) return true; } // Bubble up the key event as WebView doesn't handle it @@ -5561,7 +5606,7 @@ public class WebView extends AbsoluteLayout mDrawCursorRing = true; setFocusControllerActive(true); if (mNativeClass != 0) { - nativeRecordButtons(true, false, true); + recordButtons(null, true, false, true); } } else { if (!inEditingMode()) { @@ -5570,7 +5615,7 @@ public class WebView extends AbsoluteLayout mDrawCursorRing = false; setFocusControllerActive(false); } - // We do not call nativeRecordButtons here because we assume + // We do not call recordButtons here because we assume // that when we lost focus, or window focus, it got called with // false for the first parameter } @@ -5589,7 +5634,7 @@ public class WebView extends AbsoluteLayout mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); mTouchMode = TOUCH_DONE_MODE; if (mNativeClass != 0) { - nativeRecordButtons(false, false, true); + recordButtons(null, false, false, true); } setFocusControllerActive(false); } @@ -5647,13 +5692,13 @@ public class WebView extends AbsoluteLayout if (hasWindowFocus()) { mDrawCursorRing = true; if (mNativeClass != 0) { - nativeRecordButtons(true, false, true); + recordButtons(null, true, false, true); } setFocusControllerActive(true); //} else { // The WebView has gained focus while we do not have // windowfocus. When our window lost focus, we should have - // called nativeRecordButtons(false...) + // called recordButtons(false...) } } else { // When we lost focus, unless focus went to the TextView (which is @@ -5661,7 +5706,7 @@ public class WebView extends AbsoluteLayout if (!inEditingMode()) { mDrawCursorRing = false; if (mNativeClass != 0) { - nativeRecordButtons(false, false, true); + recordButtons(null, false, false, true); } setFocusControllerActive(false); } @@ -6762,7 +6807,7 @@ public class WebView extends AbsoluteLayout if (mNativeClass == 0) { return false; } - nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); + recordButtons(null, hasFocus() && hasWindowFocus(), true, true); if (time - mLastCursorTime <= TRACKBALL_TIMEOUT && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { nativeSelectBestAt(mLastCursorBounds); @@ -7378,6 +7423,10 @@ public class WebView extends AbsoluteLayout } } + void sendPluginDrawMsg() { + mWebViewCore.sendMessage(EventHub.PLUGIN_SURFACE_READY); + } + /** * Returns plugin bounds if x/y in content coordinates corresponds to a * plugin. Otherwise a NULL rectangle is returned. @@ -7636,6 +7685,10 @@ public class WebView extends AbsoluteLayout } /* package */ void passToJavaScript(String currentText, KeyEvent event) { + // check if mWebViewCore has been destroyed + if (mWebViewCore == null) { + return; + } WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData(); arg.mEvent = event; arg.mCurrentText = currentText; @@ -9349,6 +9402,24 @@ public class WebView extends AbsoluteLayout return nativeTileProfilingGetFloat(frame, tile, key); } + /** + * Helper method to deal with differences between hardware and software rendering + */ + private void recordButtons(Canvas canvas, boolean focus, boolean pressed, + boolean inval) { + boolean isHardwareAccel = canvas != null + ? canvas.isHardwareAccelerated() + : isHardwareAccelerated(); + if (isHardwareAccel) { + // We never want to change button state if we are hardware accelerated, + // but we DO want to invalidate as necessary so that the GL ring + // can be drawn + nativeRecordButtons(false, false, inval); + } else { + nativeRecordButtons(focus, pressed, inval); + } + } + private native int nativeCacheHitFramePointer(); private native boolean nativeCacheHitIsPlugin(); private native Rect nativeCacheHitNodeBounds(); @@ -9395,6 +9466,7 @@ public class WebView extends AbsoluteLayout private native boolean nativeFocusCandidateIsTextInput(); /* package */ native int nativeFocusCandidateMaxLength(); /* package */ native boolean nativeFocusCandidateIsAutoComplete(); + /* package */ native boolean nativeFocusCandidateIsSpellcheck(); /* package */ native String nativeFocusCandidateName(); private native Rect nativeFocusCandidateNodeBounds(); /** diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index d3be2bf..81de356 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -186,11 +186,11 @@ public class WebViewClient { } /** - * Notify the host application to handle a SSL certificate error request - * (display the error to the user and ask whether to proceed 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 the load. + * Notify the host application that an SSL error occurred while loading a + * resource. The host application must call either handler.cancel() or + * handler.proceed(). Note that the decision may be retained for use in + * response to future SSL errors. The default behavior is to cancel the + * load. * * @param view The WebView that is initiating the callback. * @param handler An SslErrorHandler object that will handle the user's @@ -203,6 +203,15 @@ public class WebViewClient { } /** + * Notify the host application that an SSL error occurred while loading a + * resource, but the WebView but chose to proceed anyway based on a + * 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 diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 63c4d03..1294a28 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -518,7 +518,7 @@ public final class WebViewCore { /** * Update the layers' content */ - private native int nativeUpdateLayers(Region invalRegion); + private native boolean nativeUpdateLayers(int baseLayer); private native boolean nativeFocusBoundsChanged(); @@ -1032,6 +1032,8 @@ public final class WebViewCore { static final int EXECUTE_JS = 194; + static final int PLUGIN_SURFACE_READY = 195; + // private message ids private static final int DESTROY = 200; @@ -1587,6 +1589,10 @@ public final class WebViewCore { nativeFullScreenPluginHidden(msg.arg1); break; + case PLUGIN_SURFACE_READY: + nativePluginSurfaceReady(); + break; + case ADD_PACKAGE_NAMES: if (BrowserFrame.sJavaBridge == null) { throw new IllegalStateException("No WebView " + @@ -1662,24 +1668,15 @@ public final class WebViewCore { mDrawIsScheduled = false; } if (mMessages != null) { - Log.w(LOGTAG, "Not supported in this case."); + Throwable throwable = new Throwable( + "EventHub.removeMessages(int what = " + what + ") is not supported " + + "before the WebViewCore is set up."); + Log.w(LOGTAG, Log.getStackTraceString(throwable)); } else { mHandler.removeMessages(what); } } - private synchronized boolean hasMessages(int what) { - if (mBlockMessages) { - return false; - } - if (mMessages != null) { - Log.w(LOGTAG, "hasMessages() is not supported in this case."); - return false; - } else { - return mHandler.hasMessages(what); - } - } - private synchronized void sendMessageDelayed(Message msg, long delay) { if (mBlockMessages) { return; @@ -2004,18 +2001,25 @@ public final class WebViewCore { boolean mFocusSizeChanged; } + DrawData mLastDrawData = null; + // Only update the layers' content, not the base surface // PictureSet. private void webkitDrawLayers() { mDrawLayersIsScheduled = false; - if (mDrawIsScheduled) { + if (mDrawIsScheduled || mLastDrawData == null) { removeMessages(EventHub.WEBKIT_DRAW); webkitDraw(); return; } - DrawData draw = new DrawData(); - draw.mBaseLayer = nativeUpdateLayers(draw.mInvalRegion); - webkitDraw(draw); + // Directly update the layers we last passed to the UI side + if (nativeUpdateLayers(mLastDrawData.mBaseLayer)) { + // If anything more complex than position has been touched, let's do a full draw + webkitDraw(); + } + mWebView.mPrivateHandler.removeMessages(WebView.INVAL_RECT_MSG_ID); + mWebView.mPrivateHandler.sendMessageAtFrontOfQueue(mWebView.mPrivateHandler + .obtainMessage(WebView.INVAL_RECT_MSG_ID)); } private void webkitDraw() { @@ -2032,6 +2036,7 @@ public final class WebViewCore { } return; } + mLastDrawData = draw; webkitDraw(draw); } @@ -2827,6 +2832,7 @@ public final class WebViewCore { private native void nativeResume(); private native void nativeFreeMemory(); private native void nativeFullScreenPluginHidden(int npp); + private native void nativePluginSurfaceReady(); private native boolean nativeValidNodeAndBounds(int frame, int node, Rect bounds); diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 7f526e7..206142a 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -145,11 +145,11 @@ class ZoomManager { private float mInvDefaultScale; /* - * The scale factor that is used to determine the zoom level for reading text. - * The value is initially set to equal the display density. - * TODO: Support changing this in WebSettings + * The logical density of the display. This is a scaling factor for the + * Density Independent Pixel unit, where one DIP is one pixel on an + * approximately 160 dpi screen (see android.util.DisplayMetrics.density) */ - private float mReadingLevelScale; + private float mDisplayDensity; /* * The scale factor that is used as the minimum increment when going from @@ -233,11 +233,11 @@ class ZoomManager { public void init(float density) { assert density > 0; + mDisplayDensity = density; setDefaultZoomScale(density); mActualScale = density; mInvActualScale = 1 / density; - mReadingLevelScale = density; - mTextWrapScale = density; + mTextWrapScale = getReadingLevelScale(); } /** @@ -310,8 +310,11 @@ class ZoomManager { return mInitialScale > 0 ? mInitialScale : mDefaultScale; } + /** + * Returns the zoom scale used for reading text on a double-tap. + */ public final float getReadingLevelScale() { - return mReadingLevelScale; + return mDisplayDensity * mWebView.getSettings().getDoubleTapZoom() / 100.0f; } public final float getInvDefaultScale() { @@ -510,6 +513,13 @@ class ZoomManager { return mZoomScale != 0 || mInHWAcceleratedZoom; } + public void updateDoubleTapZoom() { + if (mInZoomOverview) { + mTextWrapScale = getReadingLevelScale(); + refreshZoomScale(true); + } + } + public void refreshZoomScale(boolean reflowText) { setZoomScale(mActualScale, reflowText, true); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 7b8c7f2..38bb2e1 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -237,9 +237,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te SparseBooleanArray mCheckStates; /** - * Running state of which IDs are currently checked + * Running state of which IDs are currently checked. + * If there is a value for a given key, the checked state for that ID is true + * and the value holds the last known position in the adapter for that id. */ - LongSparseArray<Boolean> mCheckedIdStates; + LongSparseArray<Integer> mCheckedIdStates; /** * Controls how the next layout will happen @@ -472,6 +474,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te static final int OVERSCROLL_LIMIT_DIVISOR = 3; /** + * How many positions in either direction we will search to try to + * find a checked item with a stable ID that moved position across + * a data set change. If the item isn't found it will be unselected. + */ + private static final int CHECK_POSITION_SEARCH_DISTANCE = 20; + + /** * Used to request a layout when we changed touch mode */ private static final int TOUCH_MODE_UNKNOWN = -1; @@ -806,7 +815,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (adapter != null) { if (mChoiceMode != CHOICE_MODE_NONE && mAdapter.hasStableIds() && mCheckedIdStates == null) { - mCheckedIdStates = new LongSparseArray<Boolean>(); + mCheckedIdStates = new LongSparseArray<Integer>(); } } @@ -901,7 +910,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return new long[0]; } - final LongSparseArray<Boolean> idStates = mCheckedIdStates; + final LongSparseArray<Integer> idStates = mCheckedIdStates; final int count = idStates.size(); final long[] ids = new long[count]; @@ -948,7 +957,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mCheckStates.put(position, value); if (mCheckedIdStates != null && mAdapter.hasStableIds()) { if (value) { - mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE); + mCheckedIdStates.put(mAdapter.getItemId(position), position); } else { mCheckedIdStates.delete(mAdapter.getItemId(position)); } @@ -980,7 +989,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (value) { mCheckStates.put(position, true); if (updateIds) { - mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE); + mCheckedIdStates.put(mAdapter.getItemId(position), position); } mCheckedItemCount = 1; } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { @@ -1010,7 +1019,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mCheckStates.put(position, newValue); if (mCheckedIdStates != null && mAdapter.hasStableIds()) { if (newValue) { - mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE); + mCheckedIdStates.put(mAdapter.getItemId(position), position); } else { mCheckedIdStates.delete(mAdapter.getItemId(position)); } @@ -1032,7 +1041,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mCheckStates.put(position, true); if (mCheckedIdStates != null && mAdapter.hasStableIds()) { mCheckedIdStates.clear(); - mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE); + mCheckedIdStates.put(mAdapter.getItemId(position), position); } mCheckedItemCount = 1; } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { @@ -1081,7 +1090,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mCheckStates = new SparseBooleanArray(); } if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) { - mCheckedIdStates = new LongSparseArray<Boolean>(); + mCheckedIdStates = new LongSparseArray<Integer>(); } // Modal multi-choice mode only has choices when the mode is active. Clear them. if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { @@ -1270,40 +1279,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setScrollable(true); - } - - @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 // events. if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - final int lastPosition = mFirstPosition + getChildCount(); - if (mLastAccessibilityScrollEventFromIndex == mFirstPosition - && mLastAccessibilityScrollEventToIndex == lastPosition) { + final int firstVisiblePosition = getFirstVisiblePosition(); + final int lastVisiblePosition = getLastVisiblePosition(); + if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition + && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) { return; } else { - mLastAccessibilityScrollEventFromIndex = mFirstPosition; - mLastAccessibilityScrollEventToIndex = lastPosition; + mLastAccessibilityScrollEventFromIndex = firstVisiblePosition; + mLastAccessibilityScrollEventToIndex = lastVisiblePosition; } } super.sendAccessibilityEvent(eventType); } - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - event.setScrollable(true); - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - event.setFromIndex(mFirstPosition); - event.setToIndex(mFirstPosition + getChildCount()); - event.setItemCount(mItemCount); - } - } - /** * Indicates whether the children's drawing cache is used during a scroll. * By default, the drawing cache is enabled but this will consume more memory. @@ -1427,7 +1420,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te boolean inActionMode; int checkedItemCount; SparseBooleanArray checkState; - LongSparseArray<Boolean> checkIdState; + LongSparseArray<Integer> checkIdState; /** * Constructor called from {@link AbsListView#onSaveInstanceState()} @@ -1450,11 +1443,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te inActionMode = in.readByte() != 0; checkedItemCount = in.readInt(); checkState = in.readSparseBooleanArray(); - long[] idState = in.createLongArray(); - - if (idState.length > 0) { - checkIdState = new LongSparseArray<Boolean>(); - checkIdState.setValues(idState, Boolean.TRUE); + final int N = in.readInt(); + if (N > 0) { + checkIdState = new LongSparseArray<Integer>(); + for (int i=0; i<N; i++) { + final long key = in.readLong(); + final int value = in.readInt(); + checkIdState.put(key, value); + } } } @@ -1470,7 +1466,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te out.writeByte((byte) (inActionMode ? 1 : 0)); out.writeInt(checkedItemCount); out.writeSparseBooleanArray(checkState); - out.writeLongArray(checkIdState != null ? checkIdState.getKeys() : new long[0]); + final int N = checkIdState != null ? checkIdState.size() : 0; + out.writeInt(N); + for (int i=0; i<N; i++) { + out.writeLong(checkIdState.keyAt(i)); + out.writeInt(checkIdState.valueAt(i)); + } } @Override @@ -1565,7 +1566,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te ss.checkState = mCheckStates.clone(); } if (mCheckedIdStates != null) { - final LongSparseArray<Boolean> idState = new LongSparseArray<Boolean>(); + final LongSparseArray<Integer> idState = new LongSparseArray<Integer>(); final int count = mCheckedIdStates.size(); for (int i = 0; i < count; i++) { idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i)); @@ -4786,15 +4787,63 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return selectedPos >= 0; } + void confirmCheckedPositionsById() { + // Clear out the positional check states, we'll rebuild it below from IDs. + mCheckStates.clear(); + + boolean checkedCountChanged = false; + for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) { + final long id = mCheckedIdStates.keyAt(checkedIndex); + final int lastPos = mCheckedIdStates.valueAt(checkedIndex); + + final long lastPosId = mAdapter.getItemId(lastPos); + if (id != lastPosId) { + // Look around to see if the ID is nearby. If not, uncheck it. + final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE); + final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount); + boolean found = false; + for (int searchPos = start; searchPos < end; searchPos++) { + final long searchId = mAdapter.getItemId(searchPos); + if (id == searchId) { + found = true; + mCheckStates.put(searchPos, true); + mCheckedIdStates.setValueAt(checkedIndex, searchPos); + break; + } + } + + if (!found) { + mCheckedIdStates.delete(id); + checkedIndex--; + mCheckedItemCount--; + checkedCountChanged = true; + if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) { + mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, + lastPos, id, false); + } + } + } else { + mCheckStates.put(lastPos, true); + } + } + + if (checkedCountChanged && mChoiceActionMode != null) { + mChoiceActionMode.invalidate(); + } + } + @Override protected void handleDataChanged() { int count = mItemCount; int lastHandledItemCount = mLastHandledItemCount; mLastHandledItemCount = mItemCount; - if (count > 0) { - int newPos; + if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) { + confirmCheckedPositionsById(); + } + if (count > 0) { + int newPos; int selectablePos; // Find the row we are supposed to sync to diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index df8eb05..475b8ee 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -335,6 +335,7 @@ public abstract class AbsSeekBar extends ProgressBar { mTouchDownX = event.getX(); } else { setPressed(true); + invalidate(mThumb.getBounds()); // This may be within the padding region onStartTrackingTouch(); trackTouchEvent(event); attemptClaimDrag(); @@ -348,6 +349,7 @@ public abstract class AbsSeekBar extends ProgressBar { final float x = event.getX(); if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) { setPressed(true); + invalidate(mThumb.getBounds()); // This may be within the padding region onStartTrackingTouch(); trackTouchEvent(event); attemptClaimDrag(); diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java index c37cc52..60b24bc 100644 --- a/core/java/android/widget/ActivityChooserView.java +++ b/core/java/android/widget/ActivityChooserView.java @@ -33,6 +33,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.widget.ActivityChooserModel.ActivityChooserModelClient; /** @@ -169,6 +171,11 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod private boolean mIsAttachedToWindow; /** + * String resource for formatting content description of the default target. + */ + private int mDefaultActionButtonContentDescription; + + /** * Create a new instance. * * @param context The application environment. @@ -259,7 +266,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod * * <strong>Note:</strong> Clients would like to set this drawable * as a clue about the action the chosen activity will perform. For - * example, if share activity is to be chosen the drawable should + * example, if a share activity is to be chosen the drawable should * give a clue that sharing is to be performed. * * @param drawable The drawable. @@ -269,6 +276,21 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod } /** + * Sets the content description for the button that expands the activity + * overflow list. + * + * description as a clue about the action performed by the button. + * For example, if a share activity is to be chosen the content + * description should be something like "Share with". + * + * @param resourceId The content description resource id. + */ + public void setExpandActivityOverflowButtonContentDescription(int resourceId) { + CharSequence contentDescription = mContext.getString(resourceId); + mExpandActivityOverflowButtonImage.setContentDescription(contentDescription); + } + + /** * Set the provider hosting this view, if applicable. * @hide Internal use only */ @@ -329,6 +351,8 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod if (mProvider != null) { mProvider.subUiVisibilityChanged(true); } + popupWindow.getListView().setContentDescription(mContext.getString( + R.string.activitychooserview_choose_application)); } } @@ -431,6 +455,20 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod } /** + * Sets a content description of the default action button. This + * resource should be a string taking one formatting argument and + * will be used for formatting the content description of the button + * dynamically as the default target changes. For example, a resource + * pointing to the string "share with %1$s" will result in a content + * description "share with Bluetooth" for the Bluetooth activity. + * + * @param resourceId The resource id. + */ + public void setDefaultActionButtonContentDescription(int resourceId) { + mDefaultActionButtonContentDescription = resourceId; + } + + /** * Gets the list popup window which is lazily initialized. * * @return The popup. @@ -465,6 +503,12 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod ResolveInfo activity = mAdapter.getDefaultActivity(); PackageManager packageManager = mContext.getPackageManager(); mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager)); + if (mDefaultActionButtonContentDescription != 0) { + CharSequence label = activity.loadLabel(packageManager); + String contentDescription = mContext.getString( + mDefaultActionButtonContentDescription, label); + mDefaultActivityButton.setContentDescription(contentDescription); + } } else { mDefaultActivityButton.setVisibility(View.GONE); } diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index a4b4e78..e16a8bd 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -29,6 +29,7 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** @@ -881,25 +882,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - final int eventType = event.getEventType(); - switch (eventType) { - case AccessibilityEvent.TYPE_VIEW_SCROLLED: - // Do not populate the text of scroll events. - return true; - case AccessibilityEvent.TYPE_VIEW_FOCUSED: - // This is an exceptional case which occurs when a window gets the - // focus and sends a focus event via its focused child to announce - // current focus/selection. AdapterView fires selection but not focus - // events so we change the event type here. - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) { - event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED); - } - break; - } - View selectedView = getSelectedView(); - if (selectedView != null && selectedView.getVisibility() == VISIBLE) { - getSelectedView().dispatchPopulateAccessibilityEvent(event); + if (selectedView != null && selectedView.getVisibility() == VISIBLE + && selectedView.dispatchPopulateAccessibilityEvent(event)) { + return true; } return false; } @@ -919,19 +905,37 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { } @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(isScrollableForAccessibility()); + View selectedView = getSelectedView(); + if (selectedView != null) { + info.setEnabled(selectedView.isEnabled()); + } + } + + @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - + event.setScrollable(isScrollableForAccessibility()); View selectedView = getSelectedView(); if (selectedView != null) { event.setEnabled(selectedView.isEnabled()); } - event.setItemCount(getCount()); event.setCurrentItemIndex(getSelectedItemPosition()); - if (getChildCount() > 0) { - event.setFromIndex(getFirstVisiblePosition()); - event.setToIndex(getLastVisiblePosition()); + event.setFromIndex(getFirstVisiblePosition()); + event.setToIndex(getLastVisiblePosition()); + event.setItemCount(getAdapter().getCount()); + } + + private boolean isScrollableForAccessibility() { + T adapter = getAdapter(); + if (adapter != null) { + final int itemCount = adapter.getCount(); + return itemCount > 0 + && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1); } + return false; } @Override diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 84ebec3..63a0870 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -25,6 +25,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.text.format.DateUtils; import android.text.format.Time; import android.util.AttributeSet; import android.view.View; @@ -228,6 +229,8 @@ public class AnalogClock extends View { mMinutes = minute + second / 60.0f; mHour = hour + mMinutes / 60.0f; mChanged = true; + + updateContentDescription(mCalendar); } private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -243,4 +246,11 @@ public class AnalogClock extends View { invalidate(); } }; + + private void updateContentDescription(Time time) { + final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR; + String contentDescription = DateUtils.formatDateTime(mContext, + time.toMillis(false), flags); + setContentDescription(contentDescription); + } } diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 51506e8..083a952 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -29,12 +29,14 @@ import android.os.Handler; import android.os.SystemClock; import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.widget.AbsListView.OnScrollListener; /** * Helper class for AbsListView to draw and control the Fast Scroll thumb */ class FastScroller { + private static final String TAG = "FastScroller"; // Minimum number of pages to justify showing a fast scroll thumb private static int MIN_PAGES = 4; @@ -81,15 +83,15 @@ class FastScroller { private Drawable mOverlayDrawableLeft; private Drawable mOverlayDrawableRight; - private int mThumbH; - private int mThumbW; - private int mThumbY; + int mThumbH; + int mThumbW; + int mThumbY; private RectF mOverlayPos; private int mOverlaySize; - private AbsListView mList; - private boolean mScrollCompleted; + AbsListView mList; + boolean mScrollCompleted; private int mVisibleItem; private Paint mPaint; private int mListOffset; @@ -105,7 +107,7 @@ class FastScroller { private Handler mHandler = new Handler(); - private BaseAdapter mListAdapter; + BaseAdapter mListAdapter; private SectionIndexer mSectionIndexer; private boolean mChangedBounds; @@ -118,10 +120,36 @@ class FastScroller { private boolean mMatchDragPosition; + float mInitialTouchY; + boolean mPendingDrag; + private int mScaledTouchSlop; + private static final int FADE_TIMEOUT = 1500; + private static final int PENDING_DRAG_DELAY = 180; private final Rect mTmpRect = new Rect(); + private final Runnable mDeferStartDrag = new Runnable() { + public void run() { + if (mList.mIsAttached) { + beginDrag(); + + final int viewHeight = mList.getHeight(); + // Jitter + int newThumbY = (int) mInitialTouchY - mThumbH + 10; + if (newThumbY < 0) { + newThumbY = 0; + } else if (newThumbY + mThumbH > viewHeight) { + newThumbY = viewHeight - mThumbH; + } + mThumbY = newThumbY; + scrollTo((float) mThumbY / (viewHeight - mThumbH)); + } + + mPendingDrag = false; + } + }; + public FastScroller(Context context, AbsListView listView) { mList = listView; init(context); @@ -264,6 +292,8 @@ class FastScroller { ta.recycle(); + mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mMatchDragPosition = context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB; @@ -456,7 +486,7 @@ class FastScroller { return mSections; } - private void getSectionsFromIndexer() { + void getSectionsFromIndexer() { Adapter adapter = mList.getAdapter(); mSectionIndexer = null; if (adapter instanceof HeaderViewListAdapter) { @@ -489,7 +519,7 @@ class FastScroller { mListAdapter = null; } - private void scrollTo(float position) { + void scrollTo(float position) { int count = mList.getCount(); mScrollCompleted = false; float fThreshold = (1.0f / count) / 8; @@ -647,12 +677,45 @@ class FastScroller { cancelFling.recycle(); } + void cancelPendingDrag() { + mList.removeCallbacks(mDeferStartDrag); + mPendingDrag = false; + } + + void startPendingDrag() { + mPendingDrag = true; + mList.postDelayed(mDeferStartDrag, PENDING_DRAG_DELAY); + } + + void beginDrag() { + setState(STATE_DRAGGING); + if (mListAdapter == null && mList != null) { + getSectionsFromIndexer(); + } + if (mList != null) { + mList.requestDisallowInterceptTouchEvent(true); + mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + } + + cancelFling(); + } + boolean onInterceptTouchEvent(MotionEvent ev) { - if (mState > STATE_NONE && ev.getAction() == MotionEvent.ACTION_DOWN) { - if (isPointInside(ev.getX(), ev.getY())) { - setState(STATE_DRAGGING); - return true; - } + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + if (mState > STATE_NONE && isPointInside(ev.getX(), ev.getY())) { + if (!mList.isInScrollingContainer()) { + beginDrag(); + return true; + } + mInitialTouchY = ev.getY(); + startPendingDrag(); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + cancelPendingDrag(); + break; } return false; } @@ -666,19 +729,32 @@ class FastScroller { if (action == MotionEvent.ACTION_DOWN) { if (isPointInside(me.getX(), me.getY())) { - setState(STATE_DRAGGING); - if (mListAdapter == null && mList != null) { - getSectionsFromIndexer(); + if (!mList.isInScrollingContainer()) { + beginDrag(); + return true; } - if (mList != null) { - mList.requestDisallowInterceptTouchEvent(true); - mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + mInitialTouchY = me.getY(); + startPendingDrag(); + } + } else if (action == MotionEvent.ACTION_UP) { // don't add ACTION_CANCEL here + if (mPendingDrag) { + // Allow a tap to scroll. + beginDrag(); + + final int viewHeight = mList.getHeight(); + // Jitter + int newThumbY = (int) me.getY() - mThumbH + 10; + if (newThumbY < 0) { + newThumbY = 0; + } else if (newThumbY + mThumbH > viewHeight) { + newThumbY = viewHeight - mThumbH; } + mThumbY = newThumbY; + scrollTo((float) mThumbY / (viewHeight - mThumbH)); - cancelFling(); - return true; + cancelPendingDrag(); + // Will hit the STATE_DRAGGING check below } - } else if (action == MotionEvent.ACTION_UP) { // don't add ACTION_CANCEL here if (mState == STATE_DRAGGING) { if (mList != null) { // ViewGroup does the right thing already, but there might @@ -698,6 +774,23 @@ class FastScroller { return true; } } else if (action == MotionEvent.ACTION_MOVE) { + if (mPendingDrag) { + final float y = me.getY(); + if (Math.abs(y - mInitialTouchY) > mScaledTouchSlop) { + setState(STATE_DRAGGING); + if (mListAdapter == null && mList != null) { + getSectionsFromIndexer(); + } + if (mList != null) { + mList.requestDisallowInterceptTouchEvent(true); + mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + } + + cancelFling(); + cancelPendingDrag(); + // Will hit the STATE_DRAGGING check below + } + } if (mState == STATE_DRAGGING) { final int viewHeight = mList.getHeight(); // Jitter @@ -717,6 +810,8 @@ class FastScroller { } return true; } + } else if (action == MotionEvent.ACTION_CANCEL) { + cancelPendingDrag(); } return false; } diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index 3f5b571..5e37fa8 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -32,8 +32,6 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Transformation; import com.android.internal.R; @@ -354,33 +352,6 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList return child.getMeasuredHeight(); } - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setScrollable(true); - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - event.setScrollable(true); - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - event.setFromIndex(mFirstPosition); - event.setToIndex(mFirstPosition + getChildCount()); - event.setItemCount(mItemCount); - } - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - // Do not append text content to scroll events they are fired frequently - // and the client has already received another event type with the text. - if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) { - super.dispatchPopulateAccessibilityEvent(event); - } - return false; - } - /** * Tracks a motion scroll. In reality, this is used to do just about any * movement to items (touch scroll, arrow-key scroll, set an item as selected). diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 1bbc501..57701ae 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -716,23 +716,17 @@ public class HorizontalScrollView extends FrameLayout { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - info.setScrollable(true); + info.setScrollable(getScrollRange() > 0); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - event.setScrollable(true); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - // Do not append text content to scroll events they are fired frequently - // and the client has already received another event type with the text. - if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) { - super.dispatchPopulateAccessibilityEvent(event); - } - return false; + event.setScrollable(getScrollRange() > 0); + event.setScrollX(mScrollX); + event.setScrollY(mScrollY); + event.setMaxScrollX(getScrollRange()); + event.setMaxScrollY(mScrollY); } private int getScrollRange() { diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 42e27b1..9ef1aa1 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1997,31 +1997,6 @@ public class ListView extends AbsListView { } } - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - - // If the item count is less than 15 then subtract disabled items from the count and - // position. Otherwise ignore disabled items. - int itemCount = 0; - int currentItemIndex = getSelectedItemPosition(); - - ListAdapter adapter = getAdapter(); - if (adapter != null) { - final int count = adapter.getCount(); - for (int i = 0; i < count; i++) { - if (adapter.isEnabled(i)) { - itemCount++; - } else if (i <= currentItemIndex) { - currentItemIndex--; - } - } - } - - event.setItemCount(itemCount); - event.setCurrentItemIndex(currentItemIndex); - } - /** * setSelectionAfterHeaderView set the selection to be the first list item * after the header views. diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 61ea5c9..767eaee 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -721,23 +721,18 @@ public class ScrollView extends FrameLayout { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - info.setScrollable(true); + info.setScrollable(getScrollRange() > 0); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - event.setScrollable(true); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - // Do not append text content to scroll events they are fired frequently - // and the client has already received another event type with the text. - if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) { - super.dispatchPopulateAccessibilityEvent(event); - } - return false; + final boolean scrollable = getScrollRange() > 0; + event.setScrollable(scrollable); + event.setScrollX(mScrollX); + event.setScrollY(mScrollY); + event.setMaxScrollX(mScrollX); + event.setMaxScrollY(getScrollRange()); } private int getScrollRange() { diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index adf2b7b..6df80e4 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -92,6 +92,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { private static final boolean DBG = false; private static final String LOG_TAG = "SearchView"; + /** + * Private constant for removing the microphone in the keyboard. + */ + private static final String IME_OPTION_NO_MICROPHONE = "nm"; + private OnQueryTextListener mOnQueryChangeListener; private OnCloseListener mOnCloseListener; private OnFocusChangeListener mOnQueryTextFocusChangeListener; @@ -256,7 +261,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { mQueryTextView.setOnItemClickListener(mOnItemClickListener); mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener); mQueryTextView.setOnKeyListener(mTextKeyListener); - // Inform any listener of focus changes + // Inform any listener of focus changes mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { @@ -335,6 +340,12 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } // Cache the voice search capability mVoiceButtonEnabled = hasVoiceSearch(); + + if (mVoiceButtonEnabled) { + // Disable the microphone on the keyboard, as a mic is displayed near the text box + // TODO: use imeOptions to disable voice input when the new API will be available + mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE); + } updateViewsVisibility(isIconified()); } diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java index 3627890..bb27b73 100644 --- a/core/java/android/widget/ShareActionProvider.java +++ b/core/java/android/widget/ShareActionProvider.java @@ -171,6 +171,12 @@ public class ShareActionProvider extends ActionProvider { activityChooserView.setExpandActivityOverflowButtonDrawable(drawable); activityChooserView.setProvider(this); + // Set content description. + activityChooserView.setDefaultActionButtonContentDescription( + R.string.shareactionprovider_share_with_application); + activityChooserView.setExpandActivityOverflowButtonContentDescription( + R.string.shareactionprovider_share_with); + return activityChooserView; } diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index ac9535a..1da18aa 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -20,6 +20,7 @@ import android.content.Context; import android.text.Editable; import android.text.Selection; import android.text.Spanned; +import android.text.method.WordIterator; import android.text.style.SpellCheckSpan; import android.text.style.SuggestionSpan; import android.view.textservice.SpellCheckerSession; @@ -30,7 +31,7 @@ import android.view.textservice.TextServicesManager; import com.android.internal.util.ArrayUtils; -import java.util.Locale; +import java.text.BreakIterator; /** @@ -40,6 +41,8 @@ import java.util.Locale; */ public class SpellChecker implements SpellCheckerSessionListener { + private final static int MAX_SPELL_BATCH_SIZE = 50; + private final TextView mTextView; final SpellCheckerSession mSpellCheckerSession; @@ -47,12 +50,15 @@ public class SpellChecker implements SpellCheckerSessionListener { // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated // SpellCheckSpan has been recycled and can be-reused. - // May contain null SpellCheckSpans after a given index. + // Contains null SpellCheckSpans after index mLength. private int[] mIds; private SpellCheckSpan[] mSpellCheckSpans; // The mLength first elements of the above arrays have been initialized private int mLength; + // Parsers on chunck of text, cutting text into words that will be checked + private SpellParser[] mSpellParsers = new SpellParser[0]; + private int mSpanSequenceCounter = 0; public SpellChecker(TextView textView) { @@ -64,7 +70,7 @@ public class SpellChecker implements SpellCheckerSessionListener { null /* not currently used by the textServicesManager */, null /* null locale means use the languages defined in Settings if referToSpellCheckerLanguageSettings is true */, - this, true /* means use the languages defined in Settings */); + this, true /* means use the languages defined in Settings */); mCookie = hashCode(); // Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand @@ -78,7 +84,7 @@ public class SpellChecker implements SpellCheckerSessionListener { * @return true if a spell checker session has successfully been created. Returns false if not, * for instance when spell checking has been disabled in settings. */ - public boolean isSessionActive() { + private boolean isSessionActive() { return mSpellCheckerSession != null; } @@ -86,6 +92,11 @@ public class SpellChecker implements SpellCheckerSessionListener { if (mSpellCheckerSession != null) { mSpellCheckerSession.close(); } + + final int length = mSpellParsers.length; + for (int i = 0; i < length; i++) { + mSpellParsers[i].close(); + } } private int nextSpellCheckSpanIndex() { @@ -108,10 +119,9 @@ public class SpellChecker implements SpellCheckerSessionListener { return mLength - 1; } - public void addSpellCheckSpan(int wordStart, int wordEnd) { + private void addSpellCheckSpan(Editable editable, int start, int end) { final int index = nextSpellCheckSpanIndex(); - ((Editable) mTextView.getText()).setSpan(mSpellCheckSpans[index], wordStart, wordEnd, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + editable.setSpan(mSpellCheckSpans[index], start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mIds[index] = mSpanSequenceCounter++; } @@ -129,10 +139,34 @@ public class SpellChecker implements SpellCheckerSessionListener { spellCheck(); } - public void spellCheck() { + public void spellCheck(int start, int end) { + if (!isSessionActive()) return; + + final int length = mSpellParsers.length; + for (int i = 0; i < length; i++) { + final SpellParser spellParser = mSpellParsers[i]; + if (spellParser.isDone()) { + spellParser.init(start, end); + spellParser.parse(); + return; + } + } + + // No available parser found in pool, create a new one + SpellParser[] newSpellParsers = new SpellParser[length + 1]; + System.arraycopy(mSpellParsers, 0, newSpellParsers, 0, length); + mSpellParsers = newSpellParsers; + + SpellParser spellParser = new SpellParser(); + mSpellParsers[length] = spellParser; + spellParser.init(start, end); + spellParser.parse(); + } + + private void spellCheck() { if (mSpellCheckerSession == null) return; - final Editable editable = (Editable) mTextView.getText(); + Editable editable = (Editable) mTextView.getText(); final int selectionStart = Selection.getSelectionStart(editable); final int selectionEnd = Selection.getSelectionEnd(editable); @@ -155,7 +189,7 @@ public class SpellChecker implements SpellCheckerSessionListener { } if (textInfosCount > 0) { - if (textInfosCount < mLength) { + if (textInfosCount < textInfos.length) { TextInfo[] textInfosCopy = new TextInfo[textInfosCount]; System.arraycopy(textInfos, 0, textInfosCopy, 0, textInfosCount); textInfos = textInfosCopy; @@ -167,15 +201,14 @@ public class SpellChecker implements SpellCheckerSessionListener { @Override public void onGetSuggestions(SuggestionsInfo[] results) { - final Editable editable = (Editable) mTextView.getText(); + Editable editable = (Editable) mTextView.getText(); + for (int i = 0; i < results.length; i++) { SuggestionsInfo suggestionsInfo = results[i]; if (suggestionsInfo.getCookie() != mCookie) continue; final int sequenceNumber = suggestionsInfo.getSequence(); for (int j = 0; j < mLength; j++) { - final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j]; - if (sequenceNumber == mIds[j]) { final int attributes = suggestionsInfo.getSuggestionsAttributes(); boolean isInDictionary = @@ -183,32 +216,214 @@ public class SpellChecker implements SpellCheckerSessionListener { boolean looksLikeTypo = ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0); + SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j]; if (!isInDictionary && looksLikeTypo) { - String[] suggestions = getSuggestions(suggestionsInfo); - SuggestionSpan suggestionSpan = new SuggestionSpan( - mTextView.getContext(), suggestions, - SuggestionSpan.FLAG_EASY_CORRECT | - SuggestionSpan.FLAG_MISSPELLED); - final int start = editable.getSpanStart(spellCheckSpan); - final int end = editable.getSpanEnd(spellCheckSpan); - editable.setSpan(suggestionSpan, start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - // TODO limit to the word rectangle region - mTextView.invalidate(); + createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan); } editable.removeSpan(spellCheckSpan); + break; + } + } + } + + final int length = mSpellParsers.length; + for (int i = 0; i < length; i++) { + final SpellParser spellParser = mSpellParsers[i]; + if (!spellParser.isDone()) { + spellParser.parse(); + } + } + } + + private void createMisspelledSuggestionSpan(Editable editable, + SuggestionsInfo suggestionsInfo, SpellCheckSpan spellCheckSpan) { + final int start = editable.getSpanStart(spellCheckSpan); + final int end = editable.getSpanEnd(spellCheckSpan); + if (start < 0 || end < 0) return; // span was removed in the meantime + + // Other suggestion spans may exist on that region, with identical suggestions, filter + // them out to avoid duplicates. First, filter suggestion spans on that exact region. + SuggestionSpan[] suggestionSpans = editable.getSpans(start, end, SuggestionSpan.class); + final int length = suggestionSpans.length; + for (int i = 0; i < length; i++) { + final int spanStart = editable.getSpanStart(suggestionSpans[i]); + final int spanEnd = editable.getSpanEnd(suggestionSpans[i]); + if (spanStart != start || spanEnd != end) { + suggestionSpans[i] = null; + } + } + + final int suggestionsCount = suggestionsInfo.getSuggestionsCount(); + String[] suggestions; + if (suggestionsCount <= 0) { + // A negative suggestion count is possible + suggestions = ArrayUtils.emptyArray(String.class); + } else { + int numberOfSuggestions = 0; + suggestions = new String[suggestionsCount]; + + for (int i = 0; i < suggestionsCount; i++) { + final String spellSuggestion = suggestionsInfo.getSuggestionAt(i); + if (spellSuggestion == null) break; + boolean suggestionFound = false; + + for (int j = 0; j < length && !suggestionFound; j++) { + if (suggestionSpans[j] == null) break; + + String[] suggests = suggestionSpans[j].getSuggestions(); + for (int k = 0; k < suggests.length; k++) { + if (spellSuggestion.equals(suggests[k])) { + // The suggestion is already provided by an other SuggestionSpan + suggestionFound = true; + break; + } + } + } + + if (!suggestionFound) { + suggestions[numberOfSuggestions++] = spellSuggestion; } } + + if (numberOfSuggestions != suggestionsCount) { + String[] newSuggestions = new String[numberOfSuggestions]; + System.arraycopy(suggestions, 0, newSuggestions, 0, numberOfSuggestions); + suggestions = newSuggestions; + } } + + SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions, + SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED); + editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + // TODO limit to the word rectangle region + mTextView.invalidate(); } - private static String[] getSuggestions(SuggestionsInfo suggestionsInfo) { - // A negative suggestion count is possible - final int len = Math.max(0, suggestionsInfo.getSuggestionsCount()); - String[] suggestions = new String[len]; - for (int j = 0; j < len; j++) { - suggestions[j] = suggestionsInfo.getSuggestionAt(j); + private class SpellParser { + private WordIterator mWordIterator = new WordIterator(/*TODO Locale*/); + private Object mRange = new Object(); + + public void init(int start, int end) { + ((Editable) mTextView.getText()).setSpan(mRange, start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + public void close() { + ((Editable) mTextView.getText()).removeSpan(mRange); + } + + public boolean isDone() { + return ((Editable) mTextView.getText()).getSpanStart(mRange) < 0; + } + + public void parse() { + Editable editable = (Editable) mTextView.getText(); + // Iterate over the newly added text and schedule new SpellCheckSpans + final int start = editable.getSpanStart(mRange); + final int end = editable.getSpanEnd(mRange); + mWordIterator.setCharSequence(editable, start, end); + + // Move back to the beginning of the current word, if any + int wordStart = mWordIterator.preceding(start); + int wordEnd; + if (wordStart == BreakIterator.DONE) { + wordEnd = mWordIterator.following(start); + if (wordEnd != BreakIterator.DONE) { + wordStart = mWordIterator.getBeginning(wordEnd); + } + } else { + wordEnd = mWordIterator.getEnd(wordStart); + } + if (wordEnd == BreakIterator.DONE) { + editable.removeSpan(mRange); + return; + } + + // We need to expand by one character because we want to include the spans that + // end/start at position start/end respectively. + SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1, + SpellCheckSpan.class); + SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1, + SuggestionSpan.class); + + int nbWordsChecked = 0; + boolean scheduleOtherSpellCheck = false; + + while (wordStart <= end) { + if (wordEnd >= start && wordEnd > wordStart) { + // A new word has been created across the interval boundaries with this edit. + // Previous spans (ended on start / started on end) removed, not valid anymore + if (wordStart < start && wordEnd > start) { + removeSpansAt(editable, start, spellCheckSpans); + removeSpansAt(editable, start, suggestionSpans); + } + + if (wordStart < end && wordEnd > end) { + removeSpansAt(editable, end, spellCheckSpans); + removeSpansAt(editable, end, suggestionSpans); + } + + // Do not create new boundary spans if they already exist + boolean createSpellCheckSpan = true; + if (wordEnd == start) { + for (int i = 0; i < spellCheckSpans.length; i++) { + final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]); + if (spanEnd == start) { + createSpellCheckSpan = false; + break; + } + } + } + + if (wordStart == end) { + for (int i = 0; i < spellCheckSpans.length; i++) { + final int spanStart = editable.getSpanStart(spellCheckSpans[i]); + if (spanStart == end) { + createSpellCheckSpan = false; + break; + } + } + } + + if (createSpellCheckSpan) { + if (nbWordsChecked == MAX_SPELL_BATCH_SIZE) { + scheduleOtherSpellCheck = true; + break; + } + addSpellCheckSpan(editable, wordStart, wordEnd); + nbWordsChecked++; + } + } + + // iterate word by word + wordEnd = mWordIterator.following(wordEnd); + if (wordEnd == BreakIterator.DONE) break; + wordStart = mWordIterator.getBeginning(wordEnd); + if (wordStart == BreakIterator.DONE) { + break; + } + } + + if (scheduleOtherSpellCheck) { + editable.setSpan(mRange, wordStart, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + editable.removeSpan(mRange); + } + + spellCheck(); + } + + private <T> void removeSpansAt(Editable editable, int offset, T[] spans) { + final int length = spans.length; + for (int i = 0; i < length; i++) { + final T span = spans[i]; + final int start = editable.getSpanStart(span); + if (start > offset) continue; + final int end = editable.getSpanEnd(span); + if (end < offset) continue; + editable.removeSpan(span); + } } - return suggestions; } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 5cd7902..324198f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2476,6 +2476,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (gravity != mGravity) { invalidate(); + mLayoutAlignment = null; } mGravity = gravity; @@ -2937,11 +2938,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Spannable sp = new SpannableString(mText); - for (ChangeWatcher cw : - sp.getSpans(0, sp.length(), ChangeWatcher.class)) { + for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) { sp.removeSpan(cw); } + SuggestionSpan[] suggestionSpans = sp.getSpans(0, sp.length(), SuggestionSpan.class); + for (int i = 0; i < suggestionSpans.length; i++) { + int flags = suggestionSpans[i].getFlags(); + if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 + && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { + sp.removeSpan(suggestionSpans[i]); + } + } + sp.removeSpan(mSuggestionRangeSpan); ss.text = sp; @@ -4449,7 +4458,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mSpellChecker != null) { mSpellChecker.closeSession(); - removeMisspelledSpans(); // Forces the creation of a new SpellChecker next time this window is created. // Will handle the cases where the settings has been changed in the meantime. mSpellChecker = null; @@ -6115,7 +6123,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * not the full view width with padding. * {@hide} */ - protected void makeNewLayout(int w, int hintWidth, + protected void makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView) { @@ -6127,8 +6135,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mHighlightPathBogus = true; - if (w < 0) { - w = 0; + if (wantWidth < 0) { + wantWidth = 0; } if (hintWidth < 0) { hintWidth = 0; @@ -6148,12 +6156,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener resolveTextDirection(); } - mLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment, shouldEllipsize, + mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, effectiveEllipsize, effectiveEllipsize == mEllipsize); if (switchEllipsize) { TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ? TruncateAt.END : TruncateAt.MARQUEE; - mSavedMarqueeModeLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment, + mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); } @@ -6161,7 +6169,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mHintLayout = null; if (mHint != null) { - if (shouldEllipsize) hintWidth = w; + if (shouldEllipsize) hintWidth = wantWidth; if (hintBoring == UNKNOWN_BORING) { hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, @@ -6245,15 +6253,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener prepareCursorControllers(); } - private Layout makeSingleLayout(int w, BoringLayout.Metrics boring, int ellipsisWidth, + private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved) { Layout result = null; if (mText instanceof Spannable) { - result = new DynamicLayout(mText, mTransformed, mTextPaint, w, + result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null, - ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + ellipsisWidth); } else { if (boring == UNKNOWN_BORING) { boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); @@ -6263,53 +6271,53 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (boring != null) { - if (boring.width <= w && + if (boring.width <= wantWidth && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { if (useSaved && mSavedLayout != null) { result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, - w, alignment, mSpacingMult, mSpacingAdd, + wantWidth, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad); } else { result = BoringLayout.make(mTransformed, mTextPaint, - w, alignment, mSpacingMult, mSpacingAdd, + wantWidth, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad); } if (useSaved) { mSavedLayout = (BoringLayout) result; } - } else if (shouldEllipsize && boring.width <= w) { + } else if (shouldEllipsize && boring.width <= wantWidth) { if (useSaved && mSavedLayout != null) { result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, - w, alignment, mSpacingMult, mSpacingAdd, + wantWidth, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad, effectiveEllipsize, ellipsisWidth); } else { result = BoringLayout.make(mTransformed, mTextPaint, - w, alignment, mSpacingMult, mSpacingAdd, + wantWidth, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad, effectiveEllipsize, ellipsisWidth); } } else if (shouldEllipsize) { result = new StaticLayout(mTransformed, 0, mTransformed.length(), - mTextPaint, w, alignment, mTextDir, mSpacingMult, + mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, effectiveEllipsize, ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); } else { result = new StaticLayout(mTransformed, mTextPaint, - w, alignment, mTextDir, mSpacingMult, mSpacingAdd, + wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad); } } else if (shouldEllipsize) { result = new StaticLayout(mTransformed, 0, mTransformed.length(), - mTextPaint, w, alignment, mTextDir, mSpacingMult, + mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, effectiveEllipsize, ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); } else { result = new StaticLayout(mTransformed, mTextPaint, - w, alignment, mTextDir, mSpacingMult, mSpacingAdd, + wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad); } } @@ -7740,98 +7748,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Create new SpellCheckSpans on the modified region. */ private void updateSpellCheckSpans(int start, int end) { - if (!isTextEditable() || !isSuggestionsEnabled() || !getSpellChecker().isSessionActive()) - return; - Editable text = (Editable) mText; - - final int shift = prepareWordIterator(start, end); - final int shiftedStart = start - shift; - final int shiftedEnd = end - shift; - - // Move back to the beginning of the current word, if any - int wordStart = mWordIterator.preceding(shiftedStart); - int wordEnd; - if (wordStart == BreakIterator.DONE) { - wordEnd = mWordIterator.following(shiftedStart); - if (wordEnd != BreakIterator.DONE) { - wordStart = mWordIterator.getBeginning(wordEnd); - } - } else { - wordEnd = mWordIterator.getEnd(wordStart); - } - if (wordEnd == BreakIterator.DONE) { - return; - } - - // We need to expand by one character because we want to include the spans that end/start - // at position start/end respectively. - SpellCheckSpan[] spellCheckSpans = text.getSpans(start - 1, end + 1, SpellCheckSpan.class); - SuggestionSpan[] suggestionSpans = text.getSpans(start - 1, end + 1, SuggestionSpan.class); - final int numberOfSpellCheckSpans = spellCheckSpans.length; - - // Iterate over the newly added text and schedule new SpellCheckSpans - while (wordStart <= shiftedEnd) { - if (wordEnd >= shiftedStart) { - // A new word has been created across the interval boundaries. Remove previous spans - if (wordStart < shiftedStart && wordEnd > shiftedStart) { - removeSpansAt(start, spellCheckSpans, text); - removeSpansAt(start, suggestionSpans, text); - } - - if (wordStart < shiftedEnd && wordEnd > shiftedEnd) { - removeSpansAt(end, spellCheckSpans, text); - removeSpansAt(end, suggestionSpans, text); - } - - // Do not create new boundary spans if they already exist - boolean createSpellCheckSpan = true; - if (wordEnd == shiftedStart) { - for (int i = 0; i < numberOfSpellCheckSpans; i++) { - final int spanEnd = text.getSpanEnd(spellCheckSpans[i]); - if (spanEnd == start) { - createSpellCheckSpan = false; - break; - } - } - } - - if (wordStart == shiftedEnd) { - for (int i = 0; i < numberOfSpellCheckSpans; i++) { - final int spanStart = text.getSpanStart(spellCheckSpans[i]); - if (spanStart == end) { - createSpellCheckSpan = false; - break; - } - } - } - - if (createSpellCheckSpan) { - mSpellChecker.addSpellCheckSpan(wordStart + shift, wordEnd + shift); - } - } - - // iterate word by word - wordEnd = mWordIterator.following(wordEnd); - if (wordEnd == BreakIterator.DONE) break; - wordStart = mWordIterator.getBeginning(wordEnd); - if (wordStart == BreakIterator.DONE) { - Log.e(LOG_TAG, "No word beginning from " + (wordEnd + shift) + "in " + mText); - break; - } - } - - mSpellChecker.spellCheck(); - } - - private static <T> void removeSpansAt(int offset, T[] spans, Editable text) { - final int length = spans.length; - for (int i = 0; i < length; i++) { - final T span = spans[i]; - final int start = text.getSpanStart(span); - if (start > offset) continue; - final int end = text.getSpanEnd(span); - if (end < offset) continue; - text.removeSpan(span); + if (isTextEditable() && isSuggestionsEnabled()) { + getSpellChecker().spellCheck(start, end); } } @@ -8461,24 +8379,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - /** - * Removes the suggestion spans for misspelled words. - */ - private void removeMisspelledSpans() { - if (mText instanceof Spannable) { - Spannable spannable = (Spannable) mText; - SuggestionSpan[] suggestionSpans = spannable.getSpans(0, - spannable.length(), SuggestionSpan.class); - for (int i = 0; i < suggestionSpans.length; i++) { - int flags = suggestionSpans[i].getFlags(); - if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 - && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { - spannable.removeSpan(suggestionSpans[i]); - } - } - } - } - @Override public boolean onGenericMotionEvent(MotionEvent event) { if (mMovement != null && mText instanceof Spannable && mLayout != null) { @@ -8934,37 +8834,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // If a URLSpan (web address, email, phone...) is found at that position, select it. URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class); - if (urlSpans.length == 1) { - URLSpan url = urlSpans[0]; - selectionStart = ((Spanned) mText).getSpanStart(url); - selectionEnd = ((Spanned) mText).getSpanEnd(url); + if (urlSpans.length >= 1) { + URLSpan urlSpan = urlSpans[0]; + selectionStart = ((Spanned) mText).getSpanStart(urlSpan); + selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan); } else { - final int shift = prepareWordIterator(minOffset, maxOffset); + if (mWordIterator == null) { + mWordIterator = new WordIterator(); + } + mWordIterator.setCharSequence(mText, minOffset, maxOffset); - selectionStart = mWordIterator.getBeginning(minOffset - shift); + selectionStart = mWordIterator.getBeginning(minOffset); if (selectionStart == BreakIterator.DONE) return false; - selectionStart += shift; - selectionEnd = mWordIterator.getEnd(maxOffset - shift); + selectionEnd = mWordIterator.getEnd(maxOffset); if (selectionEnd == BreakIterator.DONE) return false; - selectionEnd += shift; + + if (selectionStart == selectionEnd) { + // Possible when the word iterator does not properly handle the text's language + long range = getCharRange(selectionStart); + selectionStart = extractRangeStartFromLong(range); + selectionEnd = extractRangeEndFromLong(range); + } } Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); - return true; + return selectionEnd > selectionStart; } - int prepareWordIterator(int start, int end) { - if (mWordIterator == null) { - mWordIterator = new WordIterator(); + private long getCharRange(int offset) { + final int textLength = mText.length(); + if (offset + 1 < textLength) { + final char currentChar = mText.charAt(offset); + final char nextChar = mText.charAt(offset + 1); + if (Character.isSurrogatePair(currentChar, nextChar)) { + return packRangeInLong(offset, offset + 2); + } } - - final int TEXT_WINDOW_WIDTH = 50; // Should be larger than the longest word's length - final int windowStart = Math.max(0, start - TEXT_WINDOW_WIDTH); - final int windowEnd = Math.min(mText.length(), end + TEXT_WINDOW_WIDTH); - mWordIterator.setCharSequence(mText.subSequence(windowStart, windowEnd)); - - return windowStart; + if (offset < textLength) { + return packRangeInLong(offset, offset + 1); + } + if (offset - 2 >= 0) { + final char previousChar = mText.charAt(offset - 1); + final char previousPreviousChar = mText.charAt(offset - 2); + if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { + return packRangeInLong(offset - 2, offset); + } + } + if (offset - 1 >= 0) { + return packRangeInLong(offset - 1, offset); + } + return packRangeInLong(offset, offset); } private SpellChecker getSpellChecker() { @@ -9340,7 +9260,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Start a new selection if (!handled) { - handled = startSelectionActionMode(); + vibrate = handled = startSelectionActionMode(); } if (vibrate) { @@ -9688,15 +9608,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener SpannableStringBuilder text = new SpannableStringBuilder(); TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext, android.R.style.TextAppearance_SuggestionHighlight); - - void removeMisspelledFlag() { - int suggestionSpanFlags = suggestionSpan.getFlags(); - if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) { - suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED; - suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT; - suggestionSpan.setFlags(suggestionSpanFlags); - } - } } private class SuggestionAdapter extends BaseAdapter { @@ -9930,7 +9841,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Fallback on the default highlight color when the first span does not provide one mSuggestionRangeSpan.setBackgroundColor(mHighlightColor); } else { - final float BACKGROUND_TRANSPARENCY = 0.3f; + final float BACKGROUND_TRANSPARENCY = 0.4f; final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY); mSuggestionRangeSpan.setBackgroundColor( (underlineColor & 0x00FFFFFF) + (newAlpha << 24)); @@ -9956,8 +9867,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Add the text before and after the span. - suggestionInfo.text.insert(0, mText.subSequence(unionStart, spanStart).toString()); - suggestionInfo.text.append(mText.subSequence(spanEnd, unionEnd).toString()); + suggestionInfo.text.insert(0, mText.toString().substring(unionStart, spanStart)); + suggestionInfo.text.append(mText.toString().substring(spanEnd, unionEnd)); } @Override @@ -9989,14 +9900,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hide(); return; } - final String originalText = mText.subSequence(spanStart, spanEnd).toString(); + final String originalText = mText.toString().substring(spanStart, spanEnd); if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) { Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT); intent.putExtra("word", originalText); intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); getContext().startActivity(intent); - suggestionInfo.removeMisspelledFlag(); + // There is no way to know if the word was indeed added. Re-check. + editable.removeSpan(suggestionInfo.suggestionSpan); + updateSpellCheckSpans(spanStart, spanEnd); } else { // SuggestionSpans are removed by replace: save them before SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd, @@ -10010,6 +9923,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan); suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan); suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan); + + // Remove potential misspelled flags + int suggestionSpanFlags = suggestionSpan.getFlags(); + if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) { + suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED; + suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT; + suggestionSpan.setFlags(suggestionSpanFlags); + } } final int suggestionStart = suggestionInfo.suggestionStart; @@ -10018,14 +9939,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener suggestionStart, suggestionEnd).toString(); editable.replace(spanStart, spanEnd, suggestion); - suggestionInfo.removeMisspelledFlag(); - // Notify source IME of the suggestion pick. Do this before swaping texts. if (!TextUtils.isEmpty( suggestionInfo.suggestionSpan.getNotificationTargetClassName())) { InputMethodManager imm = InputMethodManager.peekInstance(); - imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText, - suggestionInfo.suggestionIndex); + if (imm != null) { + imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText, + suggestionInfo.suggestionIndex); + } } // Swap text content between actual text and Suggestion span @@ -10045,7 +9966,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - // Move cursor at the end of the replacement word + // Move cursor at the end of the replaced word Selection.setSelection(editable, spanEnd + lengthDifference); } @@ -10174,8 +10095,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (!hasSelection()) { // There may already be a selection on device rotation - boolean currentWordSelected = selectCurrentWord(); - if (!currentWordSelected) { + if (!selectCurrentWord()) { // No word found under cursor or text selection not permitted. return false; } @@ -10256,7 +10176,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme); + TypedArray styledAttributes = mContext.obtainStyledAttributes( + com.android.internal.R.styleable.SelectionModeDrawables); boolean allowText = getContext().getResources().getBoolean( com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); @@ -10269,7 +10190,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (!allowText) { // Provide an icon, text will not be displayed on smaller screens. selectAllIconId = styledAttributes.getResourceId( - R.styleable.Theme_actionModeSelectAllDrawable, 0); + R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0); } menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). @@ -10281,7 +10202,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (canCut()) { menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut). setIcon(styledAttributes.getResourceId( - R.styleable.Theme_actionModeCutDrawable, 0)). + R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)). setAlphabeticShortcut('x'). setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); @@ -10290,7 +10211,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (canCopy()) { menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy). setIcon(styledAttributes.getResourceId( - R.styleable.Theme_actionModeCopyDrawable, 0)). + R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)). setAlphabeticShortcut('c'). setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); @@ -10299,7 +10220,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (canPaste()) { menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). setIcon(styledAttributes.getResourceId( - R.styleable.Theme_actionModePasteDrawable, 0)). + R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)). setAlphabeticShortcut('v'). setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); @@ -11579,7 +11500,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final RectF sTempRect = new RectF(); // XXX should be much larger - private static final int VERY_WIDE = 16384; + private static final int VERY_WIDE = 1024*1024; private static final int BLINK = 500; diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 7444d46..f52e773 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -237,9 +237,7 @@ public class TimePicker extends FrameLayout { } // set the content descriptions - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - setContentDescriptions(); - } + setContentDescriptions(); } @Override diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 351714e..6a0cd36 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -68,6 +68,7 @@ interface IBatteryStats { void noteScanWifiLockReleasedFromSource(in WorkSource ws); void noteWifiMulticastEnabledFromSource(in WorkSource ws); void noteWifiMulticastDisabledFromSource(in WorkSource ws); + void noteNetworkInterfaceType(String iface, int type); void setBatteryState(int status, int health, int plugType, int level, int temp, int volt); long getAwakeTimeBattery(); long getAwakeTimePlugged(); diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index 9fbbb3d..a0e125a 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -17,32 +17,79 @@ package com.android.internal.app; import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; import android.os.Bundle; +import android.os.Handler; +import android.os.Vibrator; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; import android.widget.ImageView; import android.widget.Toast; public class PlatLogoActivity extends Activity { Toast mToast; + ImageView mContent; + Vibrator mZzz = new Vibrator(); + int mCount; + final Handler mHandler = new Handler(); + + Runnable mSuperLongPress = new Runnable() { + public void run() { + mCount++; + mZzz.vibrate(50 * mCount); + final float scale = 1f + 0.25f * mCount * mCount; + mContent.setScaleX(scale); + mContent.setScaleY(scale); + + if (mCount <= 3) { + mHandler.postDelayed(mSuperLongPress, ViewConfiguration.getLongPressTimeout()); + } else { + try { + startActivity(new Intent(Intent.ACTION_MAIN) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .setClassName("com.android.systemui","com.android.systemui.Nyandroid")); + } catch (ActivityNotFoundException ex) { + android.util.Log.e("PlatLogoActivity", "Couldn't find platlogo screensaver."); + } + finish(); + } + } + }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mToast = Toast.makeText(this, "REZZZZZZZ...", Toast.LENGTH_SHORT); + mToast = Toast.makeText(this, "Android 4.0: Ice Cream Sandwich", Toast.LENGTH_SHORT); - ImageView content = new ImageView(this); - content.setImageResource(com.android.internal.R.drawable.platlogo); - content.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - - setContentView(content); - } + mContent = new ImageView(this); + mContent.setImageResource(com.android.internal.R.drawable.platlogo); + mContent.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_UP) { - mToast.show(); - } - return super.dispatchTouchEvent(ev); + mContent.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + final int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + mContent.setPressed(true); + mHandler.removeCallbacks(mSuperLongPress); + mCount = 0; + mHandler.postDelayed(mSuperLongPress, 2*ViewConfiguration.getLongPressTimeout()); + } else if (action == MotionEvent.ACTION_UP) { + if (mContent.isPressed()) { + mContent.setPressed(false); + mHandler.removeCallbacks(mSuperLongPress); + mToast.show(); + } + } + return true; + } + }); + + setContentView(mContent); } } diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java index 719847e..36fc24e 100644 --- a/core/java/com/android/internal/app/RingtonePickerActivity.java +++ b/core/java/com/android/internal/app/RingtonePickerActivity.java @@ -47,7 +47,9 @@ public final class RingtonePickerActivity extends AlertActivity implements private static final String TAG = "RingtonePickerActivity"; private static final int DELAY_MS_SELECTION_PLAYED = 300; - + + private static final String SAVE_CLICKED_POS = "clicked_pos"; + private RingtoneManager mRingtoneManager; private Cursor mCursor; @@ -120,7 +122,10 @@ public final class RingtonePickerActivity extends AlertActivity implements if (mUriForDefaultItem == null) { mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI; } - + + if (savedInstanceState != null) { + mClickedPos = savedInstanceState.getInt(SAVE_CLICKED_POS, -1); + } // Get whether to show the 'Silent' item mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); @@ -167,6 +172,12 @@ public final class RingtonePickerActivity extends AlertActivity implements setupAlert(); } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(SAVE_CLICKED_POS, mClickedPos); + } + public void onPrepareListView(ListView listView) { if (mHasDefaultItem) { diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java index daabf42..77d0c97 100644 --- a/core/java/com/android/internal/app/ShutdownThread.java +++ b/core/java/com/android/internal/app/ShutdownThread.java @@ -109,7 +109,6 @@ public final class ShutdownThread extends Thread { if (confirm) { final CloseDialogReceiver closer = new CloseDialogReceiver(context); final AlertDialog dialog = new AlertDialog.Builder(context) - .setIconAttribute(android.R.attr.alertDialogIcon) .setTitle(com.android.internal.R.string.power_off) .setMessage(resourceId) .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java new file mode 100644 index 0000000..ee3f23b --- /dev/null +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static com.android.server.NetworkManagementSocketTagger.kernelToTag; + +import android.net.NetworkStats; +import android.os.SystemClock; +import android.util.Slog; + +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; +import com.google.android.collect.Sets; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.StringTokenizer; + +import libcore.io.IoUtils; + +/** + * Creates {@link NetworkStats} instances by parsing various {@code /proc/} + * files as needed. + */ +public class NetworkStatsFactory { + private static final String TAG = "NetworkStatsFactory"; + + // TODO: consider moving parsing to native code + + /** Path to {@code /proc/net/dev}. */ + @Deprecated + private final File mStatsIface; + /** Path to {@code /proc/net/xt_qtaguid/iface_stat}. */ + @Deprecated + private final File mStatsXtIface; + /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */ + private final File mStatsXtIfaceAll; + /** Path to {@code /proc/net/xt_qtaguid/stats}. */ + private final File mStatsXtUid; + + /** {@link #mStatsXtUid} and {@link #mStatsXtIfaceAll} headers. */ + private static final String KEY_IDX = "idx"; + private static final String KEY_IFACE = "iface"; + private static final String KEY_ACTIVE = "active"; + private static final String KEY_UID = "uid_tag_int"; + private static final String KEY_COUNTER_SET = "cnt_set"; + private static final String KEY_TAG_HEX = "acct_tag_hex"; + private static final String KEY_SNAP_RX_BYTES = "snap_rx_bytes"; + private static final String KEY_SNAP_RX_PACKETS = "snap_rx_packets"; + private static final String KEY_SNAP_TX_BYTES = "snap_tx_bytes"; + private static final String KEY_SNAP_TX_PACKETS = "snap_tx_packets"; + private static final String KEY_RX_BYTES = "rx_bytes"; + private static final String KEY_RX_PACKETS = "rx_packets"; + private static final String KEY_TX_BYTES = "tx_bytes"; + private static final String KEY_TX_PACKETS = "tx_packets"; + + public NetworkStatsFactory() { + this(new File("/proc/")); + } + + // @VisibleForTesting + public NetworkStatsFactory(File procRoot) { + mStatsIface = new File(procRoot, "net/dev"); + mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats"); + mStatsXtIface = new File(procRoot, "net/xt_qtaguid/iface_stat"); + mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all"); + } + + /** + * Parse and return interface-level summary {@link NetworkStats}. Values + * monotonically increase since device boot, and may include details about + * inactive interfaces. + * + * @throws IllegalStateException when problem parsing stats. + */ + public NetworkStats readNetworkStatsSummary() throws IllegalStateException { + if (mStatsXtIfaceAll.exists()) { + return readNetworkStatsSummarySingleFile(); + } else { + return readNetworkStatsSummaryMultipleFiles(); + } + } + + private NetworkStats readNetworkStatsSummarySingleFile() { + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + + // TODO: read directly from proc once headers are added + final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES, + KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES, + KEY_RX_PACKETS, KEY_TX_BYTES, KEY_TX_PACKETS); + final ArrayList<String> values = Lists.newArrayList(); + final HashMap<String, String> parsed = Maps.newHashMap(); + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(mStatsXtIfaceAll)); + + String line; + while ((line = reader.readLine()) != null) { + splitLine(line, values); + parseLine(keys, values, parsed); + + entry.iface = parsed.get(KEY_IFACE); + entry.uid = UID_ALL; + entry.set = SET_DEFAULT; + entry.tag = TAG_NONE; + + // always include snapshot values + entry.rxBytes = getParsedLong(parsed, KEY_SNAP_RX_BYTES); + entry.rxPackets = getParsedLong(parsed, KEY_SNAP_RX_PACKETS); + entry.txBytes = getParsedLong(parsed, KEY_SNAP_TX_BYTES); + entry.txPackets = getParsedLong(parsed, KEY_SNAP_TX_PACKETS); + + // fold in active numbers, but only when active + final boolean active = getParsedInt(parsed, KEY_ACTIVE) != 0; + if (active) { + entry.rxBytes += getParsedLong(parsed, KEY_RX_BYTES); + entry.rxPackets += getParsedLong(parsed, KEY_RX_PACKETS); + entry.txBytes += getParsedLong(parsed, KEY_TX_BYTES); + entry.txPackets += getParsedLong(parsed, KEY_TX_PACKETS); + } + + stats.addValues(entry); + } + } catch (NullPointerException e) { + throw new IllegalStateException("problem parsing stats: " + e); + } catch (NumberFormatException e) { + throw new IllegalStateException("problem parsing stats: " + e); + } catch (IOException e) { + throw new IllegalStateException("problem parsing stats: " + e); + } finally { + IoUtils.closeQuietly(reader); + } + return stats; + } + + /** + * @deprecated remove once {@code iface_stat_all} is merged to all kernels. + */ + @Deprecated + private NetworkStats readNetworkStatsSummaryMultipleFiles() { + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + + final HashSet<String> knownIfaces = Sets.newHashSet(); + final HashSet<String> activeIfaces = Sets.newHashSet(); + + // collect any historical stats and active state + for (String iface : fileListWithoutNull(mStatsXtIface)) { + final File ifacePath = new File(mStatsXtIface, iface); + + final long active = readSingleLongFromFile(new File(ifacePath, "active")); + if (active == 1) { + knownIfaces.add(iface); + activeIfaces.add(iface); + } else if (active == 0) { + knownIfaces.add(iface); + } else { + continue; + } + + entry.iface = iface; + entry.uid = UID_ALL; + entry.set = SET_DEFAULT; + entry.tag = TAG_NONE; + entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes")); + entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets")); + entry.txBytes = readSingleLongFromFile(new File(ifacePath, "tx_bytes")); + entry.txPackets = readSingleLongFromFile(new File(ifacePath, "tx_packets")); + + stats.addValues(entry); + } + + final ArrayList<String> values = Lists.newArrayList(); + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(mStatsIface)); + + // skip first two header lines + reader.readLine(); + reader.readLine(); + + // parse remaining lines + String line; + while ((line = reader.readLine()) != null) { + splitLine(line, values); + + try { + entry.iface = values.get(0); + entry.uid = UID_ALL; + entry.set = SET_DEFAULT; + entry.tag = TAG_NONE; + entry.rxBytes = Long.parseLong(values.get(1)); + entry.rxPackets = Long.parseLong(values.get(2)); + entry.txBytes = Long.parseLong(values.get(9)); + entry.txPackets = Long.parseLong(values.get(10)); + + if (activeIfaces.contains(entry.iface)) { + // combine stats when iface is active + stats.combineValues(entry); + } else if (!knownIfaces.contains(entry.iface)) { + // add stats when iface is unknown + stats.addValues(entry); + } + } catch (NumberFormatException e) { + Slog.w(TAG, "problem parsing stats row '" + line + "': " + e); + } + } + } catch (NullPointerException e) { + throw new IllegalStateException("problem parsing stats: " + e); + } catch (NumberFormatException e) { + throw new IllegalStateException("problem parsing stats: " + e); + } catch (IOException e) { + throw new IllegalStateException("problem parsing stats: " + e); + } finally { + IoUtils.closeQuietly(reader); + } + + return stats; + } + + public NetworkStats readNetworkStatsDetail() { + return readNetworkStatsDetail(UID_ALL); + } + + /** + * Parse and return {@link NetworkStats} with UID-level details. Values + * monotonically increase since device boot. + * + * @throws IllegalStateException when problem parsing stats. + */ + public NetworkStats readNetworkStatsDetail(int limitUid) throws IllegalStateException { + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + + // TODO: remove knownLines check once 5087722 verified + final HashSet<String> knownLines = Sets.newHashSet(); + // TODO: remove lastIdx check once 5270106 verified + int lastIdx; + + final ArrayList<String> keys = Lists.newArrayList(); + final ArrayList<String> values = Lists.newArrayList(); + final HashMap<String, String> parsed = Maps.newHashMap(); + + BufferedReader reader = null; + String line = null; + try { + reader = new BufferedReader(new FileReader(mStatsXtUid)); + + // parse first line as header + line = reader.readLine(); + splitLine(line, keys); + lastIdx = 1; + + // parse remaining lines + while ((line = reader.readLine()) != null) { + splitLine(line, values); + parseLine(keys, values, parsed); + + if (!knownLines.add(line)) { + throw new IllegalStateException("duplicate proc entry: " + line); + } + + final int idx = getParsedInt(parsed, KEY_IDX); + if (idx != lastIdx + 1) { + throw new IllegalStateException( + "inconsistent idx=" + idx + " after lastIdx=" + lastIdx); + } + lastIdx = idx; + + entry.iface = parsed.get(KEY_IFACE); + entry.uid = getParsedInt(parsed, KEY_UID); + entry.set = getParsedInt(parsed, KEY_COUNTER_SET); + entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX)); + entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES); + entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS); + entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES); + entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS); + + if (limitUid == UID_ALL || limitUid == entry.uid) { + stats.addValues(entry); + } + } + } catch (NullPointerException e) { + throw new IllegalStateException("problem parsing line: " + line, e); + } catch (NumberFormatException e) { + throw new IllegalStateException("problem parsing line: " + line, e); + } catch (IOException e) { + throw new IllegalStateException("problem parsing line: " + line, e); + } finally { + IoUtils.closeQuietly(reader); + } + return stats; + } + + private static int getParsedInt(HashMap<String, String> parsed, String key) { + final String value = parsed.get(key); + return value != null ? Integer.parseInt(value) : 0; + } + + private static long getParsedLong(HashMap<String, String> parsed, String key) { + final String value = parsed.get(key); + return value != null ? Long.parseLong(value) : 0; + } + + /** + * Split given line into {@link ArrayList}. + */ + private static void splitLine(String line, ArrayList<String> outSplit) { + outSplit.clear(); + + final StringTokenizer t = new StringTokenizer(line, " \t\n\r\f:"); + while (t.hasMoreTokens()) { + outSplit.add(t.nextToken()); + } + } + + /** + * Zip the two given {@link ArrayList} as key and value pairs into + * {@link HashMap}. + */ + private static void parseLine( + ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) { + outParsed.clear(); + + final int size = Math.min(keys.size(), values.size()); + for (int i = 0; i < size; i++) { + outParsed.put(keys.get(i), values.get(i)); + } + } + + /** + * Utility method to read a single plain-text {@link Long} from the given + * {@link File}, usually from a {@code /proc/} filesystem. + */ + private static long readSingleLongFromFile(File file) { + try { + final byte[] buffer = IoUtils.readFileAsByteArray(file.toString()); + return Long.parseLong(new String(buffer).trim()); + } catch (NumberFormatException e) { + return -1; + } catch (IOException e) { + return -1; + } + } + + /** + * Wrapper for {@link File#list()} that returns empty array instead of + * {@code null}. + */ + private static String[] fileListWithoutNull(File file) { + final String[] list = file.list(); + return list != null ? list : new String[0]; + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index df5071f..e2a2566 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -16,11 +16,15 @@ package com.android.internal.os; -import com.android.internal.util.JournaledFile; +import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.UID_ALL; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; -import android.net.TrafficStats; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.NetworkStats; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.FileUtils; @@ -43,6 +47,11 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; +import com.android.internal.R; +import com.android.internal.net.NetworkStatsFactory; +import com.android.internal.util.JournaledFile; +import com.google.android.collect.Sets; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -52,6 +61,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -69,6 +79,8 @@ public final class BatteryStatsImpl extends BatteryStats { private static final boolean DEBUG_HISTORY = false; private static final boolean USE_OLD_HISTORY = false; // for debugging. + // TODO: remove "tcp" from network methods, since we measure total stats. + // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0xBA757475; // 'BATSTATS' @@ -314,6 +326,11 @@ public final class BatteryStatsImpl extends BatteryStats { private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>(); + private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory(); + + /** Network ifaces that {@link ConnectivityManager} has claimed as mobile. */ + private HashSet<String> mMobileIfaces = Sets.newHashSet(); + // For debugging public BatteryStatsImpl() { mFile = null; @@ -1036,7 +1053,8 @@ public final class BatteryStatsImpl extends BatteryStats { String name; int count; long totalTime; - int startIndex, endIndex; + int startIndex; + int endIndex; int numUpdatedWlNames = 0; // Advance past the first line. @@ -1390,30 +1408,48 @@ public final class BatteryStatsImpl extends BatteryStats { } public void doUnplugLocked(long batteryUptime, long batteryRealtime) { - for (int iu = mUidStats.size() - 1; iu >= 0; iu--) { - Uid u = mUidStats.valueAt(iu); - u.mStartedTcpBytesReceived = TrafficStats.getUidRxBytes(u.mUid); - u.mStartedTcpBytesSent = TrafficStats.getUidTxBytes(u.mUid); + NetworkStats.Entry entry = null; + + // Track UID data usage + final NetworkStats uidStats = getNetworkStatsDetailGroupedByUid(); + final int size = uidStats.size(); + for (int i = 0; i < size; i++) { + entry = uidStats.getValues(i, entry); + + final Uid u = mUidStats.get(entry.uid); + if (u == null) continue; + + u.mStartedTcpBytesReceived = entry.rxBytes; + u.mStartedTcpBytesSent = entry.txBytes; u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived; u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent; } + for (int i = mUnpluggables.size() - 1; i >= 0; i--) { mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime); } - // Track total mobile data - doDataUnplug(mMobileDataRx, TrafficStats.getMobileRxBytes()); - doDataUnplug(mMobileDataTx, TrafficStats.getMobileTxBytes()); - doDataUnplug(mTotalDataRx, TrafficStats.getTotalRxBytes()); - doDataUnplug(mTotalDataTx, TrafficStats.getTotalTxBytes()); + + // Track both mobile and total overall data + final NetworkStats ifaceStats = getNetworkStatsSummary(); + entry = ifaceStats.getTotal(entry, mMobileIfaces); + doDataUnplug(mMobileDataRx, entry.rxBytes); + doDataUnplug(mMobileDataTx, entry.txBytes); + entry = ifaceStats.getTotal(entry); + doDataUnplug(mTotalDataRx, entry.rxBytes); + doDataUnplug(mTotalDataTx, entry.txBytes); + // Track radio awake time mRadioDataStart = getCurrentRadioDataUptime(); mRadioDataUptime = 0; + // Track bt headset ping count mBluetoothPingStart = getCurrentBluetoothPingCount(); mBluetoothPingCount = 0; } public void doPlugLocked(long batteryUptime, long batteryRealtime) { + NetworkStats.Entry entry = null; + for (int iu = mUidStats.size() - 1; iu >= 0; iu--) { Uid u = mUidStats.valueAt(iu); if (u.mStartedTcpBytesReceived >= 0) { @@ -1428,10 +1464,16 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i = mUnpluggables.size() - 1; i >= 0; i--) { mUnpluggables.get(i).plug(batteryUptime, batteryRealtime); } - doDataPlug(mMobileDataRx, TrafficStats.getMobileRxBytes()); - doDataPlug(mMobileDataTx, TrafficStats.getMobileTxBytes()); - doDataPlug(mTotalDataRx, TrafficStats.getTotalRxBytes()); - doDataPlug(mTotalDataTx, TrafficStats.getTotalTxBytes()); + + // Track both mobile and total overall data + final NetworkStats ifaceStats = getNetworkStatsSummary(); + entry = ifaceStats.getTotal(entry, mMobileIfaces); + doDataPlug(mMobileDataRx, entry.rxBytes); + doDataPlug(mMobileDataTx, entry.txBytes); + entry = ifaceStats.getTotal(entry); + doDataPlug(mTotalDataRx, entry.rxBytes); + doDataPlug(mTotalDataTx, entry.txBytes); + // Track radio awake time mRadioDataUptime = getRadioDataUptime(); mRadioDataStart = -1; @@ -2216,6 +2258,14 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public void noteNetworkInterfaceTypeLocked(String iface, int networkType) { + if (ConnectivityManager.isNetworkTypeMobile(networkType)) { + mMobileIfaces.add(iface); + } else { + mMobileIfaces.remove(iface); + } + } + @Override public long getScreenOnTime(long batteryRealtime, int which) { return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which); } @@ -2400,8 +2450,10 @@ public final class BatteryStatsImpl extends BatteryStats { } public long computeCurrentTcpBytesReceived() { + final long uidRxBytes = getNetworkStatsDetailGroupedByUid().getTotal( + null, mUid).rxBytes; return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0 - ? (TrafficStats.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0); + ? (uidRxBytes - mStartedTcpBytesReceived) : 0); } @Override @@ -2619,8 +2671,10 @@ public final class BatteryStatsImpl extends BatteryStats { } public long computeCurrentTcpBytesSent() { + final long uidTxBytes = getNetworkStatsDetailGroupedByUid().getTotal( + null, mUid).txBytes; return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0 - ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0); + ? (uidTxBytes - mStartedTcpBytesSent) : 0); } /** @@ -4518,22 +4572,26 @@ public final class BatteryStatsImpl extends BatteryStats { /** Only STATS_UNPLUGGED works properly */ public long getMobileTcpBytesSent(int which) { - return getTcpBytes(TrafficStats.getMobileTxBytes(), mMobileDataTx, which); + final long mobileTxBytes = getNetworkStatsSummary().getTotal(null, mMobileIfaces).txBytes; + return getTcpBytes(mobileTxBytes, mMobileDataTx, which); } /** Only STATS_UNPLUGGED works properly */ public long getMobileTcpBytesReceived(int which) { - return getTcpBytes(TrafficStats.getMobileRxBytes(), mMobileDataRx, which); + final long mobileRxBytes = getNetworkStatsSummary().getTotal(null, mMobileIfaces).rxBytes; + return getTcpBytes(mobileRxBytes, mMobileDataRx, which); } /** Only STATS_UNPLUGGED works properly */ public long getTotalTcpBytesSent(int which) { - return getTcpBytes(TrafficStats.getTotalTxBytes(), mTotalDataTx, which); + final long totalTxBytes = getNetworkStatsSummary().getTotal(null).txBytes; + return getTcpBytes(totalTxBytes, mTotalDataTx, which); } /** Only STATS_UNPLUGGED works properly */ public long getTotalTcpBytesReceived(int which) { - return getTcpBytes(TrafficStats.getTotalRxBytes(), mTotalDataRx, which); + final long totalRxBytes = getNetworkStatsSummary().getTotal(null).rxBytes; + return getTcpBytes(totalRxBytes, mTotalDataRx, which); } @Override @@ -5637,7 +5695,47 @@ public final class BatteryStatsImpl extends BatteryStats { mGlobalWifiRunningTimer.logState(pr, " "); pr.println("*** Bluetooth timer:"); mBluetoothOnTimer.logState(pr, " "); + pr.println("*** Mobile ifaces:"); + pr.println(mMobileIfaces.toString()); } super.dumpLocked(pw); } + + private NetworkStats mNetworkSummaryCache; + private NetworkStats mNetworkDetailCache; + + private NetworkStats getNetworkStatsSummary() { + // NOTE: calls from BatteryStatsService already hold this lock + synchronized (this) { + if (mNetworkSummaryCache == null + || mNetworkSummaryCache.getElapsedRealtimeAge() > SECOND_IN_MILLIS) { + try { + mNetworkSummaryCache = mNetworkStatsFactory.readNetworkStatsSummary(); + } catch (IllegalStateException e) { + // log problem and return empty object + Log.wtf(TAG, "problem reading network stats", e); + mNetworkSummaryCache = new NetworkStats(SystemClock.elapsedRealtime(), 0); + } + } + return mNetworkSummaryCache; + } + } + + private NetworkStats getNetworkStatsDetailGroupedByUid() { + // NOTE: calls from BatteryStatsService already hold this lock + synchronized (this) { + if (mNetworkDetailCache == null + || mNetworkDetailCache.getElapsedRealtimeAge() > SECOND_IN_MILLIS) { + try { + mNetworkDetailCache = mNetworkStatsFactory + .readNetworkStatsDetail().groupedByUid(); + } catch (IllegalStateException e) { + // log problem and return empty object + Log.wtf(TAG, "problem reading network stats", e); + mNetworkDetailCache = new NetworkStats(SystemClock.elapsedRealtime(), 0); + } + } + return mNetworkDetailCache; + } + } } diff --git a/core/java/com/android/internal/policy/IFaceLockCallback.aidl b/core/java/com/android/internal/policy/IFaceLockCallback.aidl index 4f76c71..add3f1c 100644 --- a/core/java/com/android/internal/policy/IFaceLockCallback.aidl +++ b/core/java/com/android/internal/policy/IFaceLockCallback.aidl @@ -21,4 +21,5 @@ import android.os.IBinder; oneway interface IFaceLockCallback { void unlock(); void cancel(); + void pokeWakelock(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 3916e86..aca1fa2 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -34,7 +34,6 @@ oneway interface IStatusBar void topAppWindowChanged(boolean menuVisible); void setImeWindowStatus(in IBinder token, int vis, int backDisposition); void setHardKeyboardStatus(boolean available, boolean enabled); - void userActivity(); void toggleRecentApps(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 07430e7..ecebfc0 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -46,6 +46,5 @@ interface IStatusBarService void onNotificationClear(String pkg, String tag, int id); void setSystemUiVisibility(int vis); void setHardKeyboardEnabled(boolean enabled); - void userActivity(); void toggleRecentApps(); } diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index b9948fe..b227700 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -27,6 +27,7 @@ import android.view.IWindowSession; public class BaseIWindow extends IWindow.Stub { private IWindowSession mSession; + public int mSeq; public void setSession(IWindowSession session) { mSession = session; @@ -69,7 +70,9 @@ public class BaseIWindow extends IWindow.Stub { public void dispatchDragEvent(DragEvent event) { } - public void dispatchSystemUiVisibilityChanged(int visibility) { + public void dispatchSystemUiVisibilityChanged(int seq, int globalUi, + int localValue, int localChanges) { + mSeq = seq; } public void dispatchWallpaperCommand(String action, int x, int y, diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java index ecda47e..edf4443 100644 --- a/core/java/com/android/internal/view/StandaloneActionMode.java +++ b/core/java/com/android/internal/view/StandaloneActionMode.java @@ -36,17 +36,19 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call private ActionMode.Callback mCallback; private WeakReference<View> mCustomView; private boolean mFinished; + private boolean mFocusable; private MenuBuilder mMenu; public StandaloneActionMode(Context context, ActionBarContextView view, - ActionMode.Callback callback) { + ActionMode.Callback callback, boolean isFocusable) { mContext = context; mContextView = view; mCallback = callback; mMenu = new MenuBuilder(context).setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); mMenu.setCallback(this); + mFocusable = isFocusable; } @Override @@ -139,4 +141,8 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call invalidate(); mContextView.showOverflowMenu(); } + + public boolean isUiFocusable() { + return mFocusable; + } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index aabea2c..f25d65f 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -31,9 +31,6 @@ import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageButton; import java.util.ArrayList; @@ -71,8 +68,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); int mOpenSubMenuId; - public ActionMenuPresenter() { - super(com.android.internal.R.layout.action_menu_layout, + public ActionMenuPresenter(Context context) { + super(context, com.android.internal.R.layout.action_menu_layout, com.android.internal.R.layout.action_menu_item_layout); } @@ -98,7 +95,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter int width = mWidthLimit; if (mReserveOverflow) { if (mOverflowButton == null) { - mOverflowButton = new OverflowMenuButton(mContext); + mOverflowButton = new OverflowMenuButton(mSystemContext); final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); mOverflowButton.measure(spec, spec); } @@ -215,7 +212,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (hasOverflow) { if (mOverflowButton == null) { - mOverflowButton = new OverflowMenuButton(mContext); + mOverflowButton = new OverflowMenuButton(mSystemContext); } ViewGroup parent = (ViewGroup) mOverflowButton.getParent(); if (parent != mMenuView) { diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java index bec437a..1e06b5a 100644 --- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java @@ -29,8 +29,10 @@ import java.util.ArrayList; * be reused if possible when items change. */ public abstract class BaseMenuPresenter implements MenuPresenter { + protected Context mSystemContext; protected Context mContext; protected MenuBuilder mMenu; + protected LayoutInflater mSystemInflater; protected LayoutInflater mInflater; private Callback mCallback; @@ -44,10 +46,13 @@ public abstract class BaseMenuPresenter implements MenuPresenter { /** * Construct a new BaseMenuPresenter. * + * @param context Context for generating system-supplied views * @param menuLayoutRes Layout resource ID for the menu container view * @param itemLayoutRes Layout resource ID for a single item view */ - public BaseMenuPresenter(int menuLayoutRes, int itemLayoutRes) { + public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) { + mSystemContext = context; + mSystemInflater = LayoutInflater.from(context); mMenuLayoutRes = menuLayoutRes; mItemLayoutRes = itemLayoutRes; } @@ -62,7 +67,7 @@ public abstract class BaseMenuPresenter implements MenuPresenter { @Override public MenuView getMenuView(ViewGroup root) { if (mMenuView == null) { - mMenuView = (MenuView) mInflater.inflate(mMenuLayoutRes, root, false); + mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false); mMenuView.initialize(mMenu); updateMenuView(true); } @@ -138,7 +143,7 @@ public abstract class BaseMenuPresenter implements MenuPresenter { * @return The new item view */ public MenuView.ItemView createItemView(ViewGroup parent) { - return (MenuView.ItemView) mInflater.inflate(mItemLayoutRes, parent, false); + return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false); } /** diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java index 723ece4..47058ad 100644 --- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java +++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java @@ -63,11 +63,6 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men setChildrenDrawingCacheEnabled(false); } - @Override - protected boolean recycleOnMeasure() { - return false; - } - public boolean invokeItem(MenuItemImpl item) { return mMenu.performItemAction(item, 0); } diff --git a/core/java/com/android/internal/view/menu/IconMenuPresenter.java b/core/java/com/android/internal/view/menu/IconMenuPresenter.java index 24ddad6..2439b5d 100644 --- a/core/java/com/android/internal/view/menu/IconMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/IconMenuPresenter.java @@ -22,7 +22,6 @@ import android.os.Bundle; import android.os.Parcelable; import android.util.SparseArray; import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -43,16 +42,15 @@ public class IconMenuPresenter extends BaseMenuPresenter { private static final String VIEWS_TAG = "android:menu:icon"; private static final String OPEN_SUBMENU_KEY = "android:menu:icon:submenu"; - public IconMenuPresenter() { - super(com.android.internal.R.layout.icon_menu_layout, + public IconMenuPresenter(Context context) { + super(new ContextThemeWrapper(context, com.android.internal.R.style.Theme_IconMenu), + com.android.internal.R.layout.icon_menu_layout, com.android.internal.R.layout.icon_menu_item_layout); } @Override public void initForMenu(Context context, MenuBuilder menu) { - mContext = new ContextThemeWrapper(context, com.android.internal.R.style.Theme_IconMenu); - mInflater = LayoutInflater.from(mContext); - mMenu = menu; + super.initForMenu(context, menu); mMaxItems = -1; } diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index a1e16d4..df579c6 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -34,6 +34,7 @@ import android.widget.TextView; * The item view for each item in the ListView-based MenuViews. */ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView { + private static final String TAG = "ListMenuItemView"; private MenuItemImpl mItemData; private ImageView mIconView; @@ -121,27 +122,25 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView } public void setCheckable(boolean checkable) { - if (!checkable && mRadioButton == null && mCheckBox == null) { return; } - if (mRadioButton == null) { - insertRadioButton(); - } - if (mCheckBox == null) { - insertCheckBox(); - } - // Depending on whether its exclusive check or not, the checkbox or // radio button will be the one in use (and the other will be otherCompoundButton) final CompoundButton compoundButton; final CompoundButton otherCompoundButton; if (mItemData.isExclusiveCheckable()) { + if (mRadioButton == null) { + insertRadioButton(); + } compoundButton = mRadioButton; otherCompoundButton = mCheckBox; } else { + if (mCheckBox == null) { + insertCheckBox(); + } compoundButton = mCheckBox; otherCompoundButton = mRadioButton; } @@ -155,12 +154,12 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView } // Make sure the other compound button isn't visible - if (otherCompoundButton.getVisibility() != GONE) { + if (otherCompoundButton != null && otherCompoundButton.getVisibility() != GONE) { otherCompoundButton.setVisibility(GONE); } } else { - mCheckBox.setVisibility(GONE); - mRadioButton.setVisibility(GONE); + if (mCheckBox != null) mCheckBox.setVisibility(GONE); + if (mRadioButton != null) mRadioButton.setVisibility(GONE); } } diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index f95de62..0c572705 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -118,6 +118,14 @@ public class ActionBarContainer extends FrameLayout { return true; } + @Override + public boolean onHoverEvent(MotionEvent ev) { + super.onHoverEvent(ev); + + // An action bar always eats hover events. + return true; + } + public void setTabContainer(ScrollingTabContainerView tabView) { if (mTabContainer != null) { removeView(mTabContainer); diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index acffa5c..446dab1 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -207,7 +207,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi }); final MenuBuilder menu = (MenuBuilder) mode.getMenu(); - mActionMenuPresenter = new ActionMenuPresenter(); + mActionMenuPresenter = new ActionMenuPresenter(mContext); mActionMenuPresenter.setReserveOverflow(true); final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 6d2e823..61bce60 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -373,7 +373,7 @@ public class ActionBarView extends AbsActionBarView { } } if (mActionMenuPresenter == null) { - mActionMenuPresenter = new ActionMenuPresenter(); + mActionMenuPresenter = new ActionMenuPresenter(mContext); mActionMenuPresenter.setCallback(cb); mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter); mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 3795a7c..d5450e4 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -24,11 +24,15 @@ import android.app.admin.DevicePolicyManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; import android.os.FileObserver; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.storage.IMountService; import android.provider.Settings; import android.security.KeyStore; @@ -55,6 +59,8 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public class LockPatternUtils { + private static final String OPTION_ENABLE_FACELOCK = "enable_facelock"; + private static final String TAG = "LockPatternUtils"; private static final String SYSTEM_DIRECTORY = "/system/"; @@ -110,8 +116,11 @@ public class LockPatternUtils { public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate"; private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt"; private final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled"; + private final static String LOCKSCREEN_OPTIONS = "lockscreen.options"; public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK = "lockscreen.biometric_weak_fallback"; + public final static String BIOMETRIC_WEAK_EVER_CHOSEN_KEY + = "lockscreen.biometricweakeverchosen"; private final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory"; @@ -126,8 +135,6 @@ public class LockPatternUtils { private static FileObserver sPasswordObserver; - private static boolean mLastAttemptWasBiometric = false; - private static class PasswordFileObserver extends FileObserver { public PasswordFileObserver(String path, int mask) { super(path, mask); @@ -336,17 +343,36 @@ public class LockPatternUtils { } /** + * Return true if the user has ever chosen biometric weak. This is true even if biometric + * weak is not current set. + * + * @return True if the user has ever chosen biometric weak. + */ + public boolean isBiometricWeakEverChosen() { + return getBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY); + } + + /** * Used by device policy manager to validate the current password * information it has. */ public int getActivePasswordQuality() { int activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - switch (getKeyguardStoredPasswordQuality()) { + // Note we don't want to use getKeyguardStoredPasswordQuality() because we want this to + // return biometric_weak if that is being used instead of the backup + int quality = + (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + switch (quality) { case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: if (isLockPatternEnabled()) { activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; } break; + case DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK: + if (isBiometricWeakInstalled()) { + activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; + } + break; case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: if (isLockPasswordEnabled()) { activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; @@ -368,6 +394,7 @@ public class LockPatternUtils { } break; } + return activePasswordQuality; } @@ -394,13 +421,6 @@ public class LockPatternUtils { } /** - * Sets whether the last lockscreen setup attempt was biometric - */ - public static void setLastAttemptWasBiometric(boolean val) { - mLastAttemptWasBiometric = val; - } - - /** * Determine if LockScreen can be disabled. This is used, for example, to tell if we should * show LockScreen or go straight to the home screen. * @@ -430,22 +450,20 @@ public class LockPatternUtils { } /** - * Calls back SetupFaceLock to delete the temporary gallery file if this is the backup lock. + * Calls back SetupFaceLock to delete the temporary gallery file */ public void deleteTempGallery() { - //if(mLastAttemptWasBiometric) { - Intent intent = new Intent().setClassName("com.android.facelock", - "com.android.facelock.SetupFaceLock"); - intent.putExtra("deleteTempGallery", true); - mContext.startActivity(intent); - //} + Intent intent = new Intent().setClassName("com.android.facelock", + "com.android.facelock.SetupFaceLock"); + intent.putExtra("deleteTempGallery", true); + mContext.startActivity(intent); } /** * Calls back SetupFaceLock to delete the gallery file when the lock type is changed */ void deleteGallery() { - if(isBiometricEnabled()) { + if(usingBiometricWeak()) { Intent intent = new Intent().setClassName("com.android.facelock", "com.android.facelock.SetupFaceLock"); intent.putExtra("deleteGallery", true); @@ -483,6 +501,7 @@ public class LockPatternUtils { setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true); moveTempGallery(); } dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern @@ -600,6 +619,7 @@ public class LockPatternUtils { } else { setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality)); + setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true); moveTempGallery(); } if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { @@ -688,6 +708,9 @@ public class LockPatternUtils { return quality; } + /** + * @return true if the lockscreen method is set to biometric weak + */ public boolean usingBiometricWeak() { int quality = (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); @@ -821,7 +844,7 @@ public class LockPatternUtils { || backupMode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; return savedPasswordExists() && (passwordEnabled || - (isBiometricEnabled() && backupEnabled)); + (usingBiometricWeak() && backupEnabled)); } /** @@ -835,16 +858,36 @@ public class LockPatternUtils { return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED) && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING || - (isBiometricEnabled() && backupEnabled)); + (usingBiometricWeak() && backupEnabled)); } /** - * @return Whether biometric weak lock is enabled. + * @return Whether biometric weak lock is installed and that the front facing camera exists */ - public boolean isBiometricEnabled() { - // TODO: check if it's installed - return getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) - == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; + public boolean isBiometricWeakInstalled() { + // Check that the system flag was set + if (!OPTION_ENABLE_FACELOCK.equals(getString(LOCKSCREEN_OPTIONS))) { + return false; + } + + // Check that it's installed + PackageManager pm = mContext.getPackageManager(); + try { + pm.getPackageInfo("com.android.facelock", PackageManager.GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + + // Check that the camera is enabled + if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) { + return false; + } + if (getDevicePolicyManager().getCameraDisabled(null)) { + return false; + } + + + return true; } /** diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 97bbe52..a2fc6e2 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -17,8 +17,6 @@ package com.android.internal.widget; -import com.android.internal.R; - import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -34,11 +32,15 @@ import android.os.Debug; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; -import android.os.Vibrator; import android.util.AttributeSet; import android.util.Log; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.R; import java.util.ArrayList; import java.util.List; @@ -57,9 +59,6 @@ public class LockPatternView extends View { private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h) private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) - // Vibrator pattern for creating a tactile bump - private static final long[] DEFAULT_VIBE_PATTERN = {0, 1, 40, 41}; - private static final boolean PROFILE_DRAWING = false; private boolean mDrawingProfilingStarted = false; @@ -100,7 +99,7 @@ public class LockPatternView extends View { private DisplayMode mPatternDisplayMode = DisplayMode.Correct; private boolean mInputEnabled = true; private boolean mInStealthMode = false; - private boolean mTactileFeedbackEnabled = true; + private boolean mEnableHapticFeedback = true; private boolean mPatternInProgress = false; private float mDiameterFactor = 0.10f; // TODO: move to attrs @@ -125,11 +124,6 @@ public class LockPatternView extends View { private int mBitmapWidth; private int mBitmapHeight; - - private Vibrator vibe; // Vibrator for creating tactile feedback - - private long[] mVibePattern; - private int mAspect; private final Matrix mArrowMatrix = new Matrix(); private final Matrix mCircleMatrix = new Matrix(); @@ -248,7 +242,6 @@ public class LockPatternView extends View { public LockPatternView(Context context, AttributeSet attrs) { super(context, attrs); - vibe = new Vibrator(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LockPatternView); @@ -293,26 +286,6 @@ public class LockPatternView extends View { mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight()); } - // allow vibration pattern to be customized - mVibePattern = loadVibratePattern(com.android.internal.R.array.config_virtualKeyVibePattern); - } - - private long[] loadVibratePattern(int id) { - int[] pattern = null; - try { - pattern = getResources().getIntArray(id); - } catch (Resources.NotFoundException e) { - Log.e(TAG, "Vibrate pattern missing, using default", e); - } - if (pattern == null) { - return DEFAULT_VIBE_PATTERN; - } - - long[] tmpPattern = new long[pattern.length]; - for (int i = 0; i < pattern.length; i++) { - tmpPattern[i] = pattern[i]; - } - return tmpPattern; } private Bitmap getBitmapFor(int resId) { @@ -330,7 +303,7 @@ public class LockPatternView extends View { * @return Whether the view has tactile feedback enabled. */ public boolean isTactileFeedbackEnabled() { - return mTactileFeedbackEnabled; + return mEnableHapticFeedback; } /** @@ -350,7 +323,7 @@ public class LockPatternView extends View { * @param tactileFeedbackEnabled Whether tactile feedback is enabled */ public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) { - mTactileFeedbackEnabled = tactileFeedbackEnabled; + mEnableHapticFeedback = tactileFeedbackEnabled; } /** @@ -401,6 +374,34 @@ public class LockPatternView extends View { invalidate(); } + private void notifyCellAdded() { + if (mOnPatternListener != null) { + mOnPatternListener.onPatternCellAdded(mPattern); + } + sendAccessEvent(R.string.lockscreen_access_pattern_cell_added); + } + + private void notifyPatternStarted() { + if (mOnPatternListener != null) { + mOnPatternListener.onPatternStart(); + } + sendAccessEvent(R.string.lockscreen_access_pattern_start); + } + + private void notifyPatternDetected() { + if (mOnPatternListener != null) { + mOnPatternListener.onPatternDetected(mPattern); + } + sendAccessEvent(R.string.lockscreen_access_pattern_detected); + } + + private void notifyPatternCleared() { + if (mOnPatternListener != null) { + mOnPatternListener.onPatternCleared(); + } + sendAccessEvent(R.string.lockscreen_access_pattern_cleared); + } + /** * Clear the pattern. */ @@ -543,8 +544,10 @@ public class LockPatternView extends View { addCellToPattern(fillInGapCell); } addCellToPattern(cell); - if (mTactileFeedbackEnabled){ - vibe.vibrate(mVibePattern, -1); // Generate tactile feedback + if (mEnableHapticFeedback) { + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } return cell; } @@ -554,9 +557,7 @@ public class LockPatternView extends View { private void addCellToPattern(Cell newCell) { mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true; mPattern.add(newCell); - if (mOnPatternListener != null) { - mOnPatternListener.onPatternCellAdded(mPattern); - } + notifyCellAdded(); } // helper method to find which cell a point maps to @@ -619,6 +620,27 @@ public class LockPatternView extends View { } @Override + public boolean onHoverEvent(MotionEvent event) { + if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + event.setAction(MotionEvent.ACTION_DOWN); + break; + case MotionEvent.ACTION_HOVER_MOVE: + event.setAction(MotionEvent.ACTION_MOVE); + break; + case MotionEvent.ACTION_HOVER_EXIT: + event.setAction(MotionEvent.ACTION_UP); + break; + } + onTouchEvent(event); + event.setAction(action); + } + return super.onHoverEvent(event); + } + + @Override public boolean onTouchEvent(MotionEvent event) { if (!mInputEnabled || !isEnabled()) { return false; @@ -636,10 +658,8 @@ public class LockPatternView extends View { return true; case MotionEvent.ACTION_CANCEL: resetPattern(); - if (mOnPatternListener != null) { - mPatternInProgress = false; - mOnPatternListener.onPatternCleared(); - } + mPatternInProgress = false; + notifyPatternCleared(); if (PROFILE_DRAWING) { if (mDrawingProfilingStarted) { Debug.stopMethodTracing(); @@ -661,9 +681,9 @@ public class LockPatternView extends View { final int patternSizePreHitDetect = mPattern.size(); Cell hitCell = detectAndAddHit(x, y); final int patternSize = mPattern.size(); - if (hitCell != null && (mOnPatternListener != null) && (patternSize == 1)) { + if (hitCell != null && patternSize == 1) { mPatternInProgress = true; - mOnPatternListener.onPatternStart(); + notifyPatternStarted(); } // note current x and y for rubber banding of in progress patterns final float dx = Math.abs(x - mInProgressX); @@ -778,11 +798,17 @@ public class LockPatternView extends View { } } + private void sendAccessEvent(int resId) { + setContentDescription(mContext.getString(resId)); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + setContentDescription(null); + } + private void handleActionUp(MotionEvent event) { // report pattern detected - if (!mPattern.isEmpty() && mOnPatternListener != null) { + if (!mPattern.isEmpty()) { mPatternInProgress = false; - mOnPatternListener.onPatternDetected(mPattern); + notifyPatternDetected(); invalidate(); } if (PROFILE_DRAWING) { @@ -798,13 +824,13 @@ public class LockPatternView extends View { final float x = event.getX(); final float y = event.getY(); final Cell hitCell = detectAndAddHit(x, y); - if (hitCell != null && mOnPatternListener != null) { + if (hitCell != null) { mPatternInProgress = true; mPatternDisplayMode = DisplayMode.Correct; - mOnPatternListener.onPatternStart(); - } else if (mOnPatternListener != null) { + notifyPatternStarted(); + } else { mPatternInProgress = false; - mOnPatternListener.onPatternCleared(); + notifyPatternCleared(); } if (hitCell != null) { final float startX = getCenterXForColumn(hitCell.column); @@ -1061,7 +1087,7 @@ public class LockPatternView extends View { return new SavedState(superState, LockPatternUtils.patternToString(mPattern), mPatternDisplayMode.ordinal(), - mInputEnabled, mInStealthMode, mTactileFeedbackEnabled); + mInputEnabled, mInStealthMode, mEnableHapticFeedback); } @Override @@ -1074,7 +1100,7 @@ public class LockPatternView extends View { mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; mInputEnabled = ss.isInputEnabled(); mInStealthMode = ss.isInStealthMode(); - mTactileFeedbackEnabled = ss.isTactileFeedbackEnabled(); + mEnableHapticFeedback = ss.isTactileFeedbackEnabled(); } /** diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java index 366b983..2e7810f 100644 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java +++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java @@ -26,6 +26,7 @@ import android.os.SystemClock; import android.os.Vibrator; import android.provider.Settings; import android.util.Log; +import android.view.HapticFeedbackConstants; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.View; @@ -48,11 +49,11 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { private PasswordEntryKeyboard mSymbolsKeyboard; private PasswordEntryKeyboard mSymbolsKeyboardShifted; private PasswordEntryKeyboard mNumericKeyboard; - private Context mContext; - private View mTargetView; - private KeyboardView mKeyboardView; + private final Context mContext; + private final View mTargetView; + private final KeyboardView mKeyboardView; private long[] mVibratePattern; - private Vibrator mVibrator; + private boolean mEnableHaptics = false; public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) { this(context, keyboardView, targetView, true); @@ -71,7 +72,10 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { mKeyboardView.getLayoutParams().height); } mKeyboardView.setOnKeyboardActionListener(this); - mVibrator = new Vibrator(); + } + + public void setEnableHaptics(boolean enabled) { + mEnableHaptics = enabled; } public boolean isAlpha() { @@ -228,8 +232,9 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { } } - private void handleBackspace() { + public void handleBackspace() { sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); + performHapticFeedback(); } private void handleShift() { @@ -272,8 +277,14 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { } public void onPress(int primaryCode) { - if (mVibratePattern != null) { - mVibrator.vibrate(mVibratePattern, -1); + performHapticFeedback(); + } + + private void performHapticFeedback() { + if (mEnableHaptics) { + mKeyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } } diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java index 1042a59..73d9f10 100644 --- a/core/java/com/android/internal/widget/TransportControlView.java +++ b/core/java/com/android/internal/widget/TransportControlView.java @@ -336,20 +336,27 @@ public class TransportControlView extends FrameLayout implements OnClickListener if (state == mPlayState) { return; } + final int imageResId; + final int imageDescId; switch (state) { case RemoteControlClient.PLAYSTATE_PLAYING: - mBtnPlay.setImageResource(com.android.internal.R.drawable.ic_media_pause); + imageResId = com.android.internal.R.drawable.ic_media_pause; + imageDescId = com.android.internal.R.string.lockscreen_transport_pause_description; break; case RemoteControlClient.PLAYSTATE_BUFFERING: - mBtnPlay.setImageResource(com.android.internal.R.drawable.ic_media_stop); + imageResId = com.android.internal.R.drawable.ic_media_stop; + imageDescId = com.android.internal.R.string.lockscreen_transport_stop_description; break; case RemoteControlClient.PLAYSTATE_PAUSED: default: - mBtnPlay.setImageResource(com.android.internal.R.drawable.ic_media_play); + imageResId = com.android.internal.R.drawable.ic_media_play; + imageDescId = com.android.internal.R.string.lockscreen_transport_play_description; break; } + mBtnPlay.setImageResource(imageResId); + mBtnPlay.setContentDescription(getResources().getString(imageDescId)); mPlayState = state; } |
