diff options
Diffstat (limited to 'core/java/android')
83 files changed, 3424 insertions, 2341 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 044c0c2..ebe2b98 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -59,9 +59,13 @@ import com.android.internal.os.HandlerCaller; * An accessibility is declared as any other service in an AndroidManifest.xml but it * must also specify that it handles the "android.accessibilityservice.AccessibilityService" * {@link android.content.Intent}. Failure to declare this intent will cause the system to - * ignore the accessibility service. Following is an example declaration: + * ignore the accessibility service. Additionally an accessibility service must request + * "android.permission.BIND_ACCESSIBILITY_SERVICE" permission to ensure that only the system + * can bind to it. Failure to declare this intent will cause the system to ignore the + * accessibility service. Following is an example declaration: * </p> - * <pre> <service android:name=".MyAccessibilityService"> + * <pre> <service android:name=".MyAccessibilityService" + * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE> * <intent-filter> * <action android:name="android.accessibilityservice.AccessibilityService" /> * </intent-filter> diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 136c68c..291e75e 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -119,8 +119,6 @@ public class ChooseTypeAndAccountActivity extends Activity + savedInstanceState + ")"); } - setContentView(R.layout.choose_type_and_account); - if (savedInstanceState != null) { mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST); mExistingAccounts = @@ -164,14 +162,29 @@ public class ChooseTypeAndAccountActivity extends Activity } } - // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes - Set<String> setOfAllowableAccountTypes = null; - final String[] validAccountTypes = + // An account type is relevant iff it is allowed by the caller and supported by the account + // manager. + Set<String> setOfRelevantAccountTypes = null; + final String[] allowedAccountTypes = intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); - if (validAccountTypes != null) { - setOfAllowableAccountTypes = new HashSet<String>(validAccountTypes.length); - for (String type : validAccountTypes) { - setOfAllowableAccountTypes.add(type); + if (allowedAccountTypes != null) { + + setOfRelevantAccountTypes = new HashSet<String>(allowedAccountTypes.length); + Set<String> setOfAllowedAccountTypes = new HashSet<String>(allowedAccountTypes.length); + for (String type : allowedAccountTypes) { + setOfAllowedAccountTypes.add(type); + } + + AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes(); + Set<String> supportedAccountTypes = new HashSet<String>(descs.length); + for (AuthenticatorDescription desc : descs) { + supportedAccountTypes.add(desc.type); + } + + for (String acctType : setOfAllowedAccountTypes) { + if (supportedAccountTypes.contains(acctType)) { + setOfRelevantAccountTypes.add(acctType); + } } } @@ -185,8 +198,8 @@ public class ChooseTypeAndAccountActivity extends Activity && !setOfAllowableAccounts.contains(account)) { continue; } - if (setOfAllowableAccountTypes != null - && !setOfAllowableAccountTypes.contains(account.type)) { + if (setOfRelevantAccountTypes != null + && !setOfRelevantAccountTypes.contains(account.type)) { continue; } mAccountInfos.add(new AccountInfo(account, @@ -194,6 +207,29 @@ public class ChooseTypeAndAccountActivity extends Activity account.equals(selectedAccount))); } + if (mPendingRequest == REQUEST_NULL) { + // If there are no relevant accounts and only one relevant account typoe go directly to + // add account. Otherwise let the user choose. + if (mAccountInfos.isEmpty()) { + if (setOfRelevantAccountTypes.size() == 1) { + runAddAccountForAuthenticator(setOfRelevantAccountTypes.iterator().next()); + } else { + 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; + } + } + + setContentView(R.layout.choose_type_and_account); + // 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); @@ -201,6 +237,7 @@ public class ChooseTypeAndAccountActivity extends Activity android.R.layout.simple_list_item_1, mAccountInfos)); list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override public void onItemClick(AdapterView<?> parent, View v, int position, long id) { onListItemClick((ListView)parent, v, position, id); } @@ -209,26 +246,11 @@ public class ChooseTypeAndAccountActivity extends Activity // set the listener for the addAccount button Button addAccountButton = (Button) findViewById(R.id.addAccount); addAccountButton.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(final View v) { startChooseAccountTypeActivity(); } }); - - if (mPendingRequest == REQUEST_NULL) { - // If there are no allowable accounts go directly to add account - if (shouldSkipToChooseAccountTypeFlow()) { - 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 @@ -267,7 +289,7 @@ public class ChooseTypeAndAccountActivity extends Activity if (resultCode == RESULT_CANCELED) { // if cancelling out of addAccount and the original state caused us to skip this, // finish this activity - if (shouldSkipToChooseAccountTypeFlow()) { + if (mAccountInfos.isEmpty()) { setResult(Activity.RESULT_CANCELED); finish(); } @@ -324,14 +346,6 @@ public class ChooseTypeAndAccountActivity extends Activity finish(); } - /** - * convenience method to check if we should skip the accounts list display and immediately - * jump to the flow that asks the user to select from the account type list - */ - private boolean shouldSkipToChooseAccountTypeFlow() { - return mAccountInfos.isEmpty(); - } - protected void runAddAccountForAuthenticator(String type) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "runAddAccountForAuthenticator: " + type); diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index c643137..bdcb2af 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -1208,6 +1208,9 @@ public class LayoutTransition { * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. */ private void addChild(ViewGroup parent, View child, boolean changesLayout) { + if (parent.getWindowVisibility() != View.VISIBLE) { + return; + } if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { // Want disappearing animations to finish up before proceeding cancel(DISAPPEARING); @@ -1243,6 +1246,9 @@ public class LayoutTransition { * @hide */ public void layoutChange(ViewGroup parent) { + if (parent.getWindowVisibility() != View.VISIBLE) { + return; + } if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING && !isRunning()) { // This method is called for all calls to layout() in the container, including // those caused by add/remove/hide/show events, which will already have set up @@ -1301,6 +1307,9 @@ public class LayoutTransition { * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. */ private void removeChild(ViewGroup parent, View child, boolean changesLayout) { + if (parent.getWindowVisibility() != View.VISIBLE) { + return; + } if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { // Want appearing animations to finish up before proceeding cancel(APPEARING); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 69ee434..f20fd33 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3273,7 +3273,7 @@ public class Activity extends ContextThemeWrapper if (mMenuInflater == null) { initActionBar(); if (mActionBar != null) { - mMenuInflater = new MenuInflater(mActionBar.getThemedContext()); + mMenuInflater = new MenuInflater(mActionBar.getThemedContext(), this); } else { mMenuInflater = new MenuInflater(this); } @@ -4999,7 +4999,8 @@ public class Activity extends ContextThemeWrapper mCurrentConfig = config; } - final IBinder getActivityToken() { + /** @hide */ + public final IBinder getActivityToken() { return mParent != null ? mParent.getActivityToken() : mToken; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 4e61c3c..17b1962 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; -import android.content.res.Configuration; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; @@ -36,16 +36,17 @@ import android.os.Debug; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; +import android.os.UserId; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; import android.view.Display; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -1798,6 +1799,40 @@ public class ActivityManager { } } + /** @hide */ + public static int checkComponentPermission(String permission, int uid, + int owningUid, boolean exported) { + // Root, system server get to do everything. + if (uid == 0 || uid == Process.SYSTEM_UID) { + return PackageManager.PERMISSION_GRANTED; + } + // Isolated processes don't get any permissions. + if (UserId.isIsolated(uid)) { + return PackageManager.PERMISSION_DENIED; + } + // If there is a uid that owns whatever is being accessed, it has + // blanket access to it regardless of the permissions it requires. + if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) { + return PackageManager.PERMISSION_GRANTED; + } + // If the target is not exported, then nobody else can get to it. + if (!exported) { + Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid); + return PackageManager.PERMISSION_DENIED; + } + if (permission == null) { + return PackageManager.PERMISSION_GRANTED; + } + try { + return AppGlobals.getPackageManager() + .checkUidPermission(permission, uid); + } catch (RemoteException e) { + // Should never happen, but if it does... deny! + Slog.e(TAG, "PackageManager is dead?!?", e); + } + return PackageManager.PERMISSION_DENIED; + } + /** * Returns the usage statistics of each installed package. * diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 2f2918d..4506546 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1656,6 +1656,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_LAUNCHED_FROM_UID_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + int res = getLaunchedFromUid(token); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + } return super.onTransact(code, data, reply, flags); @@ -3785,5 +3794,18 @@ class ActivityManagerProxy implements IActivityManager return result; } + public int getLaunchedFromUid(IBinder activityToken) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(activityToken); + mRemote.transact(GET_LAUNCHED_FROM_UID_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + data.recycle(); + reply.recycle(); + return result; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b29035d..33e639e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3751,9 +3751,6 @@ public final class ActivityThread { if (start) { try { switch (profileType) { - case 1: - ViewDebug.startLooperProfiling(pcd.path, pcd.fd.getFileDescriptor()); - break; default: mProfiler.setProfiler(pcd.path, pcd.fd); mProfiler.autoStopProfiler = false; @@ -3772,9 +3769,6 @@ public final class ActivityThread { } } else { switch (profileType) { - case 1: - ViewDebug.stopLooperProfiling(); - break; default: mProfiler.stopProfiling(); break; diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index a2c7fa4..cf304df 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -350,6 +350,10 @@ public interface IActivityManager extends IInterface { public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData) throws RemoteException; + // This is not public because you need to be very careful in how you + // manage your activity to make sure it is always the uid you expect. + public int getLaunchedFromUid(IBinder activityToken) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -592,4 +596,5 @@ public interface IActivityManager extends IInterface { int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146; int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147; int FINISH_ACTIVITY_AFFINITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+148; + int GET_LAUNCHED_FROM_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+149; } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 0fe7b5c..a79a8fc 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -28,7 +28,7 @@ import android.view.IOnKeyguardExitResult; * Class that can be used to lock and unlock the keyboard. Get an instance of this * class by calling {@link android.content.Context#getSystemService(java.lang.String)} * with argument {@link android.content.Context#KEYGUARD_SERVICE}. The - * Actual class to control the keyboard locking is + * actual class to control the keyboard locking is * {@link android.app.KeyguardManager.KeyguardLock}. */ public class KeyguardManager { @@ -73,7 +73,7 @@ public class KeyguardManager { /** * Reenable the keyguard. The keyguard will reappear if the previous - * call to {@link #disableKeyguard()} caused it it to be hidden. + * call to {@link #disableKeyguard()} caused it to be hidden. * * A good place to call this is from {@link android.app.Activity#onPause()} * @@ -130,13 +130,9 @@ public class KeyguardManager { } /** - * isKeyguardLocked - * * Return whether the keyguard is currently locked. * - * @return true if in keyguard is locked. - * - * @hide + * @return true if keyguard is locked. */ public boolean isKeyguardLocked() { try { @@ -147,13 +143,9 @@ public class KeyguardManager { } /** - * isKeyguardSecure - * * Return whether the keyguard requires a password to unlock. * - * @return true if in keyguard is secure. - * - * @hide + * @return true if keyguard is secure. */ public boolean isKeyguardSecure() { try { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6d3aa98..3ced82b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -20,6 +20,7 @@ import com.android.internal.R; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.graphics.Bitmap; import android.media.AudioManager; import android.net.Uri; @@ -31,6 +32,8 @@ import android.os.SystemClock; import android.text.TextUtils; import android.util.IntProperty; import android.util.Log; +import android.util.Slog; +import android.util.TypedValue; import android.view.View; import android.widget.ProgressBar; import android.widget.RemoteViews; @@ -1378,8 +1381,8 @@ public class Notification implements Parcelable private RemoteViews applyStandardTemplate(int resId) { RemoteViews contentView = new RemoteViews(mContext.getPackageName(), resId); - boolean hasLine3 = false; - boolean hasLine2 = false; + boolean showLine3 = false; + boolean showLine2 = false; int smallIconImageViewId = R.id.icon; if (mLargeIcon != null) { contentView.setImageViewBitmap(R.id.icon, mLargeIcon); @@ -1401,15 +1404,13 @@ public class Notification implements Parcelable contentView.setTextViewText(R.id.title, mContentTitle); } if (mContentText != null) { - contentView.setTextViewText( - (mSubText != null) ? R.id.text2 : R.id.text, - mContentText); - hasLine3 = true; + contentView.setTextViewText(R.id.text, mContentText); + showLine3 = true; } if (mContentInfo != null) { contentView.setTextViewText(R.id.info, mContentInfo); contentView.setViewVisibility(R.id.info, View.VISIBLE); - hasLine3 = true; + showLine3 = true; } else if (mNumber > 0) { final int tooBig = mContext.getResources().getInteger( R.integer.status_bar_notification_info_maxnum); @@ -1421,25 +1422,42 @@ public class Notification implements Parcelable contentView.setTextViewText(R.id.info, f.format(mNumber)); } contentView.setViewVisibility(R.id.info, View.VISIBLE); - hasLine3 = true; + showLine3 = true; } else { contentView.setViewVisibility(R.id.info, View.GONE); } + // Need to show three lines? if (mSubText != null) { contentView.setTextViewText(R.id.text, mSubText); - contentView.setViewVisibility(R.id.text2, - mContentText != null ? View.VISIBLE : View.GONE); + if (mContentText != null) { + contentView.setTextViewText(R.id.text2, mContentText); + // need to shrink all the type to make sure everything fits + contentView.setViewVisibility(R.id.text2, View.VISIBLE); + showLine2 = true; + } else { + contentView.setViewVisibility(R.id.text2, View.GONE); + } } else { contentView.setViewVisibility(R.id.text2, View.GONE); if (mProgressMax != 0 || mProgressIndeterminate) { contentView.setProgressBar( R.id.progress, mProgressMax, mProgress, mProgressIndeterminate); contentView.setViewVisibility(R.id.progress, View.VISIBLE); + showLine2 = true; } else { contentView.setViewVisibility(R.id.progress, View.GONE); } } + if (showLine2) { + final Resources res = mContext.getResources(); + final float subTextSize = res.getDimensionPixelSize( + R.dimen.notification_subtext_size); + contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize); + // vertical centering + contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); + } + if (mWhen != 0) { if (mUseChronometer) { contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); @@ -1451,7 +1469,7 @@ public class Notification implements Parcelable contentView.setLong(R.id.time, "setTime", mWhen); } } - contentView.setViewVisibility(R.id.line3, hasLine3 ? View.VISIBLE : View.GONE); + contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE); return contentView; } @@ -1629,23 +1647,21 @@ public class Notification implements Parcelable mBuilder.setContentTitle(mBigContentTitle); } - if (mBuilder.mSubText == null) { - mBuilder.setContentText(null); - } - RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId); - if (mBuilder.mSubText == null) { - contentView.setViewVisibility(R.id.line3, View.GONE); - } - if (mBigContentTitle != null && mBigContentTitle.equals("")) { contentView.setViewVisibility(R.id.line1, View.GONE); + } else { + contentView.setViewVisibility(R.id.line1, View.VISIBLE); } if (mSummaryText != null && !mSummaryText.equals("")) { contentView.setViewVisibility(R.id.overflow_title, View.VISIBLE); contentView.setTextViewText(R.id.overflow_title, mSummaryText); + contentView.setViewVisibility(R.id.line3, View.GONE); + } else { + contentView.setViewVisibility(R.id.overflow_title, View.GONE); + contentView.setViewVisibility(R.id.line3, View.VISIBLE); } return contentView; @@ -1673,6 +1689,8 @@ public class Notification implements Parcelable */ public static class BigPictureStyle extends Style { private Bitmap mPicture; + private Bitmap mBigLargeIcon; + private boolean mBigLargeIconSet = false; public BigPictureStyle() { } @@ -1703,6 +1721,16 @@ public class Notification implements Parcelable return this; } + /** + * @hide + * Override the large icon when the big notification is shown. + */ + public BigPictureStyle bigLargeIcon(Bitmap b) { + mBigLargeIconSet = true; + mBigLargeIcon = b; + return this; + } + private RemoteViews makeBigContentView() { RemoteViews contentView = getStandardView(R.layout.notification_template_big_picture); @@ -1715,6 +1743,9 @@ public class Notification implements Parcelable public Notification build() { checkBuilder(); Notification wip = mBuilder.buildUnstyled(); + if (mBigLargeIconSet ) { + mBuilder.mLargeIcon = mBigLargeIcon; + } wip.bigContentView = makeBigContentView(); return wip; } @@ -1844,7 +1875,12 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.text2, View.GONE); int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, - R.id.inbox_text4}; + R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; + + // Make sure all rows are gone in case we reuse a view. + for (int rowId : rowIds) { + contentView.setViewVisibility(rowId, View.GONE); + } int i=0; while (i < mTexts.size() && i < rowIds.length) { @@ -1856,6 +1892,12 @@ public class Notification implements Parcelable i++; } + if (mTexts.size() > rowIds.length) { + contentView.setViewVisibility(R.id.inbox_more, View.VISIBLE); + } else { + contentView.setViewVisibility(R.id.inbox_more, View.GONE); + } + return contentView; } diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java index 14c5736..f21b3fd 100644 --- a/core/java/android/app/TaskStackBuilder.java +++ b/core/java/android/app/TaskStackBuilder.java @@ -161,18 +161,12 @@ public class TaskStackBuilder { ActivityInfo info = pm.getActivityInfo( new ComponentName(mSourceContext, sourceActivityClass), 0); String parentActivity = info.parentActivityName; - Intent parent = new Intent().setComponent( - new ComponentName(mSourceContext, parentActivity)); - while (parent != null) { + while (parentActivity != null) { + Intent parent = new Intent().setComponent( + new ComponentName(mSourceContext, parentActivity)); mIntents.add(insertAt, parent); info = pm.getActivityInfo(parent.getComponent(), 0); parentActivity = info.parentActivityName; - if (parentActivity != null) { - parent = new Intent().setComponent( - new ComponentName(mSourceContext, parentActivity)); - } else { - parent = null; - } } } catch (NameNotFoundException e) { Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 7a8c1fb..3aa5181 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -320,6 +320,10 @@ public class AppWidgetManager { * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, * and outside of the handler. * This method will only work when called from the uid that owns the AppWidget provider. + * + * <p> + * The total Bitmap memory used by the RemoteViews object cannot exceed that required to + * fill the screen once, ie. (screen width x screen height x 4) bytes. * * @param appWidgetIds The AppWidget instances for which to set the RemoteViews. * @param views The RemoteViews object to show. @@ -385,6 +389,10 @@ public class AppWidgetManager { * and outside of the handler. * This method will only work when called from the uid that owns the AppWidget provider. * + * <p> + * The total Bitmap memory used by the RemoteViews object cannot exceed that required to + * fill the screen once, ie. (screen width x screen height x 4) bytes. + * * @param appWidgetId The AppWidget instance for which to set the RemoteViews. * @param views The RemoteViews object to show. */ diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 718a917..edd509b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6653,17 +6653,20 @@ public class Intent implements Parcelable, Cloneable { final String action = getAction(); if (ACTION_CHOOSER.equals(action)) { - // Inspect target intent to see if we need to migrate - final Intent target = getParcelableExtra(EXTRA_INTENT); - if (target.migrateExtraStreamToClipData()) { - // Since we migrated in child, we need to promote ClipData and - // flags to ourselves to grant. - setClipData(target.getClipData()); - addFlags(target.getFlags() - & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION)); - return true; - } else { - return false; + try { + // Inspect target intent to see if we need to migrate + final Intent target = getParcelableExtra(EXTRA_INTENT); + if (target != null && target.migrateExtraStreamToClipData()) { + // Since we migrated in child, we need to promote ClipData + // and flags to ourselves to grant. + setClipData(target.getClipData()); + addFlags(target.getFlags() + & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION)); + return true; + } else { + return false; + } + } catch (ClassCastException e) { } } else if (ACTION_SEND.equals(action)) { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 2baad62..bcdd012 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1050,6 +1050,17 @@ public abstract class PackageManager { public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This is a device dedicated to showing UI + * on a television. Television here is defined to be a typical living + * room television experience: displayed on a big screen, where the user + * is sitting far away from it, and the dominant form of input will be + * something like a DPAD, not through touch or mouse. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEVISION = "android.hardware.type.television"; + + /** * Action to external storage service to clean out removed apps. * @hide */ diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 079f739..423b9af 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -42,17 +42,23 @@ public final class Configuration implements Parcelable, Comparable<Configuration public float fontScale; /** - * IMSI MCC (Mobile Country Code). 0 if undefined. + * IMSI MCC (Mobile Country Code), corresponding to + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#MccQualifier">mcc</a> + * resource qualifier. 0 if undefined. */ public int mcc; /** - * IMSI MNC (Mobile Network Code). 0 if undefined. + * IMSI MNC (Mobile Network Code), corresponding to + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#MccQualifier">mnc</a> + * resource qualifier. 0 if undefined. */ public int mnc; /** - * Current user preference for the locale. + * Current user preference for the locale, corresponding to + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#LocaleQualifier">locale</a> + * resource qualifier. */ public Locale locale; @@ -69,29 +75,52 @@ public final class Configuration implements Parcelable, Comparable<Configuration * value indicating that no size has been set. */ public static final int SCREENLAYOUT_SIZE_UNDEFINED = 0x00; /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} - * value indicating the screen is at least approximately 320x426 dp units. + * value indicating the screen is at least approximately 320x426 dp units, + * corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">small</a> + * resource qualifier. * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting * Multiple Screens</a> for more information. */ public static final int SCREENLAYOUT_SIZE_SMALL = 0x01; /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} - * value indicating the screen is at least approximately 320x470 dp units. + * value indicating the screen is at least approximately 320x470 dp units, + * corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">normal</a> + * resource qualifier. * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting * Multiple Screens</a> for more information. */ public static final int SCREENLAYOUT_SIZE_NORMAL = 0x02; /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} - * value indicating the screen is at least approximately 480x640 dp units. + * value indicating the screen is at least approximately 480x640 dp units, + * corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">large</a> + * resource qualifier. * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting * Multiple Screens</a> for more information. */ public static final int SCREENLAYOUT_SIZE_LARGE = 0x03; /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} - * value indicating the screen is at least approximately 720x960 dp units. + * value indicating the screen is at least approximately 720x960 dp units, + * corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenSizeQualifier">xlarge</a> + * resource qualifier. * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting * Multiple Screens</a> for more information.*/ public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04; - + + /** Constant for {@link #screenLayout}: bits that encode the aspect ratio. */ public static final int SCREENLAYOUT_LONG_MASK = 0x30; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LONG_MASK} + * value indicating that no size has been set. */ public static final int SCREENLAYOUT_LONG_UNDEFINED = 0x00; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LONG_MASK} + * value that corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenAspectQualifier">notlong</a> + * resource qualifier. */ public static final int SCREENLAYOUT_LONG_NO = 0x10; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LONG_MASK} + * value that corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenAspectQualifier">long</a> + * resource qualifier. */ public static final int SCREENLAYOUT_LONG_YES = 0x20; /** @@ -135,21 +164,38 @@ public final class Configuration implements Parcelable, Comparable<Configuration return cur >= size; } + /** Constant for {@link #touchscreen}: a value indicating that no value has been set. */ public static final int TOUCHSCREEN_UNDEFINED = 0; + /** Constant for {@link #touchscreen}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#TouchscreenQualifier">notouch</a> + * resource qualifier. */ public static final int TOUCHSCREEN_NOTOUCH = 1; - public static final int TOUCHSCREEN_STYLUS = 2; + /** @deprecated Not currently supported or used. */ + @Deprecated public static final int TOUCHSCREEN_STYLUS = 2; + /** Constant for {@link #touchscreen}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#TouchscreenQualifier">finger</a> + * resource qualifier. */ public static final int TOUCHSCREEN_FINGER = 3; /** * The kind of touch screen attached to the device. - * One of: {@link #TOUCHSCREEN_NOTOUCH}, {@link #TOUCHSCREEN_STYLUS}, - * {@link #TOUCHSCREEN_FINGER}. + * One of: {@link #TOUCHSCREEN_NOTOUCH}, {@link #TOUCHSCREEN_FINGER}. */ public int touchscreen; - + + /** Constant for {@link #keyboard}: a value indicating that no value has been set. */ public static final int KEYBOARD_UNDEFINED = 0; + /** Constant for {@link #keyboard}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ImeQualifier">nokeys</a> + * resource qualifier. */ public static final int KEYBOARD_NOKEYS = 1; + /** Constant for {@link #keyboard}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ImeQualifier">qwerty</a> + * resource qualifier. */ public static final int KEYBOARD_QWERTY = 2; + /** Constant for {@link #keyboard}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ImeQualifier">12key</a> + * resource qualifier. */ public static final int KEYBOARD_12KEY = 3; /** @@ -158,9 +204,16 @@ public final class Configuration implements Parcelable, Comparable<Configuration * {@link #KEYBOARD_12KEY}. */ public int keyboard; - + + /** Constant for {@link #keyboardHidden}: a value indicating that no value has been set. */ public static final int KEYBOARDHIDDEN_UNDEFINED = 0; + /** Constant for {@link #keyboardHidden}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keysexposed</a> + * resource qualifier. */ public static final int KEYBOARDHIDDEN_NO = 1; + /** Constant for {@link #keyboardHidden}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keyshidden</a> + * resource qualifier. */ public static final int KEYBOARDHIDDEN_YES = 2; /** Constant matching actual resource implementation. {@hide} */ public static final int KEYBOARDHIDDEN_SOFT = 3; @@ -174,8 +227,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public int keyboardHidden; + /** Constant for {@link #hardKeyboardHidden}: a value indicating that no value has been set. */ public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0; + /** Constant for {@link #hardKeyboardHidden}, value corresponding to the + * physical keyboard being exposed. */ public static final int HARDKEYBOARDHIDDEN_NO = 1; + /** Constant for {@link #hardKeyboardHidden}, value corresponding to the + * physical keyboard being hidden. */ public static final int HARDKEYBOARDHIDDEN_YES = 2; /** @@ -186,10 +244,23 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public int hardKeyboardHidden; + /** Constant for {@link #navigation}: a value indicating that no value has been set. */ public static final int NAVIGATION_UNDEFINED = 0; + /** Constant for {@link #navigation}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavigationQualifier">nonav</a> + * resource qualifier. */ public static final int NAVIGATION_NONAV = 1; + /** Constant for {@link #navigation}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavigationQualifier">dpad</a> + * resource qualifier. */ public static final int NAVIGATION_DPAD = 2; + /** Constant for {@link #navigation}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavigationQualifier">trackball</a> + * resource qualifier. */ public static final int NAVIGATION_TRACKBALL = 3; + /** Constant for {@link #navigation}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavigationQualifier">wheel</a> + * resource qualifier. */ public static final int NAVIGATION_WHEEL = 4; /** @@ -199,8 +270,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public int navigation; + /** Constant for {@link #navigationHidden}: a value indicating that no value has been set. */ public static final int NAVIGATIONHIDDEN_UNDEFINED = 0; + /** Constant for {@link #navigationHidden}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavAvailQualifier">navexposed</a> + * resource qualifier. */ public static final int NAVIGATIONHIDDEN_NO = 1; + /** Constant for {@link #navigationHidden}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NavAvailQualifier">navhidden</a> + * resource qualifier. */ public static final int NAVIGATIONHIDDEN_YES = 2; /** @@ -211,29 +289,70 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public int navigationHidden; + /** Constant for {@link #orientation}: a value indicating that no value has been set. */ public static final int ORIENTATION_UNDEFINED = 0; + /** Constant for {@link #orientation}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#OrientationQualifier">port</a> + * resource qualifier. */ public static final int ORIENTATION_PORTRAIT = 1; + /** Constant for {@link #orientation}, value corresponding to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#OrientationQualifier">land</a> + * resource qualifier. */ public static final int ORIENTATION_LANDSCAPE = 2; - public static final int ORIENTATION_SQUARE = 3; + /** @deprecated Not currently supported or used. */ + @Deprecated public static final int ORIENTATION_SQUARE = 3; /** * Overall orientation of the screen. May be one of - * {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}, - * or {@link #ORIENTATION_SQUARE}. + * {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}. */ public int orientation; + /** Constant for {@link #uiMode}: bits that encode the mode type. */ public static final int UI_MODE_TYPE_MASK = 0x0f; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK} + * value indicating that no mode type has been set. */ public static final int UI_MODE_TYPE_UNDEFINED = 0x00; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK} + * value that corresponds to + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">no + * UI mode</a> resource qualifier specified. */ public static final int UI_MODE_TYPE_NORMAL = 0x01; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK} + * value that corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">desk</a> + * resource qualifier. */ public static final int UI_MODE_TYPE_DESK = 0x02; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK} + * value that corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">car</a> + * resource qualifier. */ public static final int UI_MODE_TYPE_CAR = 0x03; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK} + * value that corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">television</a> + * resource qualifier. */ public static final int UI_MODE_TYPE_TELEVISION = 0x04; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK} + * value that corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#UiModeQualifier">appliance</a> + * resource qualifier. */ public static final int UI_MODE_TYPE_APPLIANCE = 0x05; + /** Constant for {@link #uiMode}: bits that encode the night mode. */ public static final int UI_MODE_NIGHT_MASK = 0x30; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_NIGHT_MASK} + * value indicating that no mode type has been set. */ public static final int UI_MODE_NIGHT_UNDEFINED = 0x00; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_NIGHT_MASK} + * value that corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NightQualifier">notnight</a> + * resource qualifier. */ public static final int UI_MODE_NIGHT_NO = 0x10; + /** Constant for {@link #uiMode}: a {@link #UI_MODE_NIGHT_MASK} + * value that corresponds to the + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#NightQualifier">night</a> + * resource qualifier. */ public static final int UI_MODE_NIGHT_YES = 0x20; /** @@ -253,21 +372,30 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int SCREEN_WIDTH_DP_UNDEFINED = 0; /** - * The current width of the available screen space, in dp units. + * The current width of the available screen space, in dp units, + * corresponding to + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenWidthQualifier">screen + * width</a> resource qualifier. */ public int screenWidthDp; public static final int SCREEN_HEIGHT_DP_UNDEFINED = 0; /** - * The current height of the available screen space, in dp units. + * The current height of the available screen space, in dp units, + * corresponding to + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenHeightQualifier">screen + * height</a> resource qualifier. */ public int screenHeightDp; public static final int SMALLEST_SCREEN_WIDTH_DP_UNDEFINED = 0; /** - * The smallest screen size an application will see in normal operation. + * The smallest screen size an application will see in normal operation, + * corresponding to + * <a href="{@docRoot}guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier">smallest + * screen width</a> resource qualifier. * This is the smallest value of both screenWidthDp and screenHeightDp * in both portrait and landscape. */ diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index c682852..c630bb5 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -630,7 +630,20 @@ public class Resources { * Various types of objects will be returned depending on the underlying * resource -- for example, a solid color, PNG image, scalable image, etc. * The Drawable API hides these implementation details. - * + * + * <p class="note"><strong>Note:</strong> Prior to + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, this function + * would not correctly retrieve the final configuration density when + * the resource ID passed here is an alias to another Drawable resource. + * This means that if the density configuration of the alias resource + * is different than the actual resource, the density of the returned + * Drawable would be incorrect, resulting in bad scaling. To work + * around this, you can instead retrieve the Drawable through + * {@link TypedArray#getDrawable TypedArray.getDrawable}. Use + * {@link android.content.Context#obtainStyledAttributes(int[]) + * Context.obtainStyledAttributes} with + * an array containing the resource ID of interest to create the TypedArray.</p> + * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index aeb46cf..b8ad818 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -1122,9 +1122,9 @@ public abstract class SensorManager { /** Helper function to compute the angle change between two rotation matrices. * Given a current rotation matrix (R) and a previous rotation matrix - * (prevR) computes the rotation around the x,y, and z axes which + * (prevR) computes the rotation around the z,x, and y axes which * transforms prevR to R. - * outputs a 3 element vector containing the x,y, and z angle + * outputs a 3 element vector containing the z,x, and y angle * change at indexes 0, 1, and 2 respectively. * <p> Each input matrix is either as a 3x3 or 4x4 row-major matrix * depending on the length of the passed array: @@ -1143,14 +1143,13 @@ public abstract class SensorManager { *</pre> * @param R current rotation matrix * @param prevR previous rotation matrix - * @param angleChange an array of floats in which the angle change is stored + * @param angleChange an an array of floats (z, x, and y) in which the angle change is stored */ public static void getAngleChange( float[] angleChange, float[] R, float[] prevR) { float rd1=0,rd4=0, rd6=0,rd7=0, rd8=0; float ri0=0,ri1=0,ri2=0,ri3=0,ri4=0,ri5=0,ri6=0,ri7=0,ri8=0; float pri0=0, pri1=0, pri2=0, pri3=0, pri4=0, pri5=0, pri6=0, pri7=0, pri8=0; - int i, j, k; if(R.length == 9) { ri0 = R[0]; diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 3137947..9b6f82a 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -41,8 +41,13 @@ interface IInputManager { // Keyboard layouts configuration. KeyboardLayout[] getKeyboardLayouts(); KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor); - String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor); - void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor, + String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor); + void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor, + String keyboardLayoutDescriptor); + String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor); + void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor, + String keyboardLayoutDescriptor); + void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor, String keyboardLayoutDescriptor); // Registers an input devices changed listener. diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 6448b55..262d87d 100755 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -16,6 +16,8 @@ package android.hardware.input; +import com.android.internal.util.ArrayUtils; + import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; @@ -77,7 +79,8 @@ public final class InputManager { * The meta-data specifies a resource that contains a description of each keyboard * layout that is provided by the application. * <pre><code> - * <receiver android:name=".InputDeviceReceiver"> + * <receiver android:name=".InputDeviceReceiver" + * android:label="@string/keyboard_layouts_label"> * <intent-filter> * <action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" /> * </intent-filter> @@ -90,7 +93,9 @@ public final class InputManager { * an XML resource whose root element is <code><keyboard-layouts></code> that * contains zero or more <code><keyboard-layout></code> elements. * Each <code><keyboard-layout></code> element specifies the name, label, and location - * of a key character map for a particular keyboard layout. + * of a key character map for a particular keyboard layout. The label on the receiver + * is used to name the collection of keyboard layouts provided by this receiver in the + * keyboard layout settings. * <pre></code> * <?xml version="1.0" encoding="utf-8"?> * <keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android"> @@ -214,6 +219,41 @@ public final class InputManager { } /** + * Gets information about the input device with the specified descriptor. + * @param descriptor The input device descriptor. + * @return The input device or null if not found. + * @hide + */ + public InputDevice getInputDeviceByDescriptor(String descriptor) { + if (descriptor == null) { + throw new IllegalArgumentException("descriptor must not be null."); + } + + synchronized (mInputDevicesLock) { + populateInputDevicesLocked(); + + int numDevices = mInputDevices.size(); + for (int i = 0; i < numDevices; i++) { + InputDevice inputDevice = mInputDevices.valueAt(i); + if (inputDevice == null) { + int id = mInputDevices.keyAt(i); + try { + inputDevice = mIm.getInputDevice(id); + } catch (RemoteException ex) { + // Ignore the problem for the purposes of this method. + continue; + } + mInputDevices.setValueAt(i, inputDevice); + } + if (descriptor.equals(inputDevice.getDescriptor())) { + return inputDevice; + } + } + return null; + } + } + + /** * Gets the ids of all input devices in the system. * @return The input device ids. */ @@ -329,50 +369,129 @@ public final class InputManager { } /** - * Gets the keyboard layout descriptor for the specified input device. + * Gets the current keyboard layout descriptor for the specified input device. * * @param inputDeviceDescriptor The input device descriptor. - * @return The keyboard layout descriptor, or null if unknown or if the default - * keyboard layout will be used. + * @return The keyboard layout descriptor, or null if no keyboard layout has been set. * * @hide */ - public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) { + public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) { if (inputDeviceDescriptor == null) { throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); } try { - return mIm.getKeyboardLayoutForInputDevice(inputDeviceDescriptor); + return mIm.getCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor); } catch (RemoteException ex) { - Log.w(TAG, "Could not get keyboard layout for input device.", ex); + Log.w(TAG, "Could not get current keyboard layout for input device.", ex); return null; } } /** - * Sets the keyboard layout descriptor for the specified input device. + * Sets the current keyboard layout descriptor for the specified input device. + * <p> + * This method may have the side-effect of causing the input device in question + * to be reconfigured. + * </p> + * + * @param inputDeviceDescriptor The input device descriptor. + * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null. + * + * @hide + */ + public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor, + String keyboardLayoutDescriptor) { + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); + } + if (keyboardLayoutDescriptor == null) { + throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); + } + + try { + mIm.setCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor, + keyboardLayoutDescriptor); + } catch (RemoteException ex) { + Log.w(TAG, "Could not set current keyboard layout for input device.", ex); + } + } + + /** + * Gets all keyboard layout descriptors that are enabled for the specified input device. + * + * @param inputDeviceDescriptor The input device descriptor. + * @return The keyboard layout descriptors. + * + * @hide + */ + public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) { + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); + } + + try { + return mIm.getKeyboardLayoutsForInputDevice(inputDeviceDescriptor); + } catch (RemoteException ex) { + Log.w(TAG, "Could not get keyboard layouts for input device.", ex); + return ArrayUtils.emptyArray(String.class); + } + } + + /** + * Adds the keyboard layout descriptor for the specified input device. * <p> * This method may have the side-effect of causing the input device in question * to be reconfigured. * </p> * * @param inputDeviceDescriptor The input device descriptor. - * @param keyboardLayoutDescriptor The keyboard layout descriptor, or null to remove - * the mapping so that the default keyboard layout will be used for the input device. + * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add. * * @hide */ - public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor, + public void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor, String keyboardLayoutDescriptor) { if (inputDeviceDescriptor == null) { throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); } + if (keyboardLayoutDescriptor == null) { + throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); + } + + try { + mIm.addKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor); + } catch (RemoteException ex) { + Log.w(TAG, "Could not add keyboard layout for input device.", ex); + } + } + + /** + * Removes the keyboard layout descriptor for the specified input device. + * <p> + * This method may have the side-effect of causing the input device in question + * to be reconfigured. + * </p> + * + * @param inputDeviceDescriptor The input device descriptor. + * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove. + * + * @hide + */ + public void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor, + String keyboardLayoutDescriptor) { + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); + } + if (keyboardLayoutDescriptor == null) { + throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); + } try { - mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor); + mIm.removeKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor); } catch (RemoteException ex) { - Log.w(TAG, "Could not set keyboard layout for input device.", ex); + Log.w(TAG, "Could not remove keyboard layout for input device.", ex); } } diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java index e75a6dc..5402e75 100644 --- a/core/java/android/hardware/input/KeyboardLayout.java +++ b/core/java/android/hardware/input/KeyboardLayout.java @@ -28,6 +28,7 @@ public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayout> { private final String mDescriptor; private final String mLabel; + private final String mCollection; public static final Parcelable.Creator<KeyboardLayout> CREATOR = new Parcelable.Creator<KeyboardLayout>() { @@ -39,14 +40,16 @@ public final class KeyboardLayout implements Parcelable, } }; - public KeyboardLayout(String descriptor, String label) { + public KeyboardLayout(String descriptor, String label, String collection) { mDescriptor = descriptor; mLabel = label; + mCollection = collection; } private KeyboardLayout(Parcel source) { mDescriptor = source.readString(); mLabel = source.readString(); + mCollection = source.readString(); } /** @@ -68,6 +71,15 @@ public final class KeyboardLayout implements Parcelable, return mLabel; } + /** + * Gets the name of the collection to which the keyboard layout belongs. This is + * the label of the broadcast receiver or application that provided the keyboard layout. + * @return The keyboard layout collection name. + */ + public String getCollection() { + return mCollection; + } + @Override public int describeContents() { return 0; @@ -77,15 +89,23 @@ public final class KeyboardLayout implements Parcelable, public void writeToParcel(Parcel dest, int flags) { dest.writeString(mDescriptor); dest.writeString(mLabel); + dest.writeString(mCollection); } @Override public int compareTo(KeyboardLayout another) { - return mLabel.compareToIgnoreCase(another.mLabel); + int result = mLabel.compareToIgnoreCase(another.mLabel); + if (result == 0) { + result = mCollection.compareToIgnoreCase(another.mCollection); + } + return result; } @Override public String toString() { - return mLabel; + if (mCollection.isEmpty()) { + return mLabel; + } + return mLabel + " - " + mCollection; } }
\ No newline at end of file diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 7257521..4916244 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -855,15 +855,23 @@ public class KeyboardView extends View implements View.OnClickListener { Key oldKey = keys[oldKeyIndex]; oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY); invalidateKey(oldKeyIndex); + final int keyCode = oldKey.codes[0]; sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT, - oldKey.codes[0]); + keyCode); + // TODO: We need to implement AccessibilityNodeProvider for this view. + sendAccessibilityEventForUnicodeCharacter( + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode); } if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) { Key newKey = keys[mCurrentKeyIndex]; newKey.onPressed(); invalidateKey(mCurrentKeyIndex); + final int keyCode = newKey.codes[0]; sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, - newKey.codes[0]); + keyCode); + // TODO: We need to implement AccessibilityNodeProvider for this view. + sendAccessibilityEventForUnicodeCharacter( + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode); } } // If key changed and preview is on ... @@ -1154,20 +1162,17 @@ public class KeyboardView extends View implements View.OnClickListener { if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) { final int action = event.getAction(); switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: - case MotionEvent.ACTION_HOVER_MOVE: - final int touchX = (int) event.getX() - mPaddingLeft; - int touchY = (int) event.getY() - mPaddingTop; - if (touchY >= -mVerticalCorrection) { - touchY += mVerticalCorrection; - } - final int keyIndex = getKeyIndices(touchX, touchY, null); - showPreview(keyIndex); - break; - case MotionEvent.ACTION_HOVER_EXIT: - showPreview(NOT_A_KEY); - break; + 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; } + return onTouchEvent(event); } return true; } diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 844d055..fb7a4f8 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -111,6 +111,14 @@ public class NetworkStats implements Parcelable { && operations == 0; } + public void add(Entry another) { + this.rxBytes += another.rxBytes; + this.rxPackets += another.rxPackets; + this.txBytes += another.txBytes; + this.txPackets += another.txPackets; + this.operations += another.operations; + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 0003c6e..a37c26f 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -342,11 +342,23 @@ public class NetworkStatsHistory implements Parcelable { * for combining together stats for external reporting. */ public void recordEntireHistory(NetworkStatsHistory input) { + recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE); + } + + /** + * Record given {@link NetworkStatsHistory} into this history, copying only + * buckets that atomically occur in the inclusive time range. Doesn't + * interpolate across partial buckets. + */ + public void recordHistory(NetworkStatsHistory input, long start, long end) { final NetworkStats.Entry entry = new NetworkStats.Entry( IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); for (int i = 0; i < input.bucketCount; i++) { - final long start = input.bucketStart[i]; - final long end = start + input.bucketDuration; + final long bucketStart = input.bucketStart[i]; + final long bucketEnd = bucketStart + input.bucketDuration; + + // skip when bucket is outside requested range + if (bucketStart < start || bucketEnd > end) continue; entry.rxBytes = getLong(input.rxBytes, i, 0L); entry.rxPackets = getLong(input.rxPackets, i, 0L); @@ -354,7 +366,7 @@ public class NetworkStatsHistory implements Parcelable { entry.txPackets = getLong(input.txPackets, i, 0L); entry.operations = getLong(input.operations, i, 0L); - recordData(start, end, entry); + recordData(bucketStart, bucketEnd, entry); } } diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index 39a4d7b..d8e53d5 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -61,6 +61,13 @@ public class NetworkTemplate implements Parcelable { com.android.internal.R.array.config_data_usage_network_types); } + private static boolean sForceAllNetworkTypes = false; + + // @VisibleForTesting + public static void forceAllNetworkTypes() { + sForceAllNetworkTypes = true; + } + /** * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with * the given IMSI. @@ -225,7 +232,7 @@ public class NetworkTemplate implements Parcelable { // TODO: consider matching against WiMAX subscriber identity return true; } else { - return (contains(DATA_USAGE_NETWORK_TYPES, ident.mType) + return ((sForceAllNetworkTypes || contains(DATA_USAGE_NETWORK_TYPES, ident.mType)) && Objects.equal(mSubscriberId, ident.mSubscriberId)); } } @@ -291,7 +298,7 @@ public class NetworkTemplate implements Parcelable { if (ident.mType == TYPE_WIMAX) { return true; } else { - return contains(DATA_USAGE_NETWORK_TYPES, ident.mType); + return sForceAllNetworkTypes || contains(DATA_USAGE_NETWORK_TYPES, ident.mType); } } diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index 6a4f1f2..2703f1d 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -261,8 +261,8 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * server then the first protocol in the client's list will be selected. * The order of the client's protocols is otherwise insignificant. * - * @param npnProtocols a possibly-empty list of protocol byte arrays. All - * arrays must be non-empty and of length less than 256. + * @param npnProtocols a non-empty list of protocol byte arrays. All arrays + * must be non-empty and of length less than 256. */ public void setNpnProtocols(byte[][] npnProtocols) { this.mNpnProtocols = toNpnProtocolsList(npnProtocols); @@ -273,6 +273,9 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * strings. */ static byte[] toNpnProtocolsList(byte[]... npnProtocols) { + if (npnProtocols.length == 0) { + throw new IllegalArgumentException("npnProtocols.length == 0"); + } int totalLength = 0; for (byte[] s : npnProtocols) { if (s.length == 0 || s.length > 255) { diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java index 7ffa575..53b41d5 100644 --- a/core/java/android/nfc/NfcActivityManager.java +++ b/core/java/android/nfc/NfcActivityManager.java @@ -299,7 +299,23 @@ public final class NfcActivityManager extends INdefPushCallback.Stub callback = state.uriCallback; } if (callback != null) { - return callback.createBeamUris(mDefaultEvent); + uris = callback.createBeamUris(mDefaultEvent); + if (uris != null) { + for (Uri uri : uris) { + if (uri == null) { + Log.e(TAG, "Uri not allowed to be null."); + return null; + } + String scheme = uri.getScheme(); + if (scheme == null || (!scheme.equalsIgnoreCase("file") && + !scheme.equalsIgnoreCase("content"))) { + Log.e(TAG, "Uri needs to have " + + "either scheme file or scheme content"); + return null; + } + } + } + return uris; } else { return uris; } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 7bf9feb..4464d58 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -584,17 +584,138 @@ public final class NfcAdapter { } } - //TODO: make sure NFC service has permission for URI - //TODO: see if we will eventually support multiple URIs - //TODO: javadoc + /** + * Set one or more {@link Uri}s to send using Android Beam (TM). Every + * Uri you provide must have either scheme 'file' or scheme 'content'. + * + * <p>For the data provided through this method, Android Beam tries to + * switch to alternate transports such as Bluetooth to achieve a fast + * transfer speed. Hence this method is very suitable + * for transferring large files such as pictures or songs. + * + * <p>The receiving side will store the content of each Uri in + * a file and present a notification to the user to open the file + * with a {@link android.content.Intent} with action + * {@link android.content.Intent#ACTION_VIEW}. + * If multiple URIs are sent, the {@link android.content.Intent} will refer + * to the first of the stored files. + * + * <p>This method may be called at any time before {@link Activity#onDestroy}, + * but the URI(s) are only made available for Android Beam when the + * specified activity(s) are in resumed (foreground) state. The recommended + * approach is to call this method during your Activity's + * {@link Activity#onCreate} - see sample + * code below. This method does not immediately perform any I/O or blocking work, + * so is safe to call on your main thread. + * + * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback} + * have priority over both {@link #setNdefPushMessage} and + * {@link #setNdefPushMessageCallback}. + * + * <p>If {@link #setBeamPushUris} is called with a null Uri array, + * and/or {@link #setBeamPushUrisCallback} is called with a null callback, + * then the Uri push will be completely disabled for the specified activity(s). + * + * <p>Code example: + * <pre> + * protected void onCreate(Bundle savedInstanceState) { + * super.onCreate(savedInstanceState); + * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); + * if (nfcAdapter == null) return; // NFC not available on this device + * nfcAdapter.setBeamPushUris(new Uri[] {uri1, uri2}, this); + * } + * </pre> + * And that is it. Only one call per activity is necessary. The Android + * OS will automatically release its references to the Uri(s) and the + * Activity object when it is destroyed if you follow this pattern. + * + * <p>If your Activity wants to dynamically supply Uri(s), + * then set a callback using {@link #setBeamPushUrisCallback} instead + * of using this method. + * + * <p class="note">Do not pass in an Activity that has already been through + * {@link Activity#onDestroy}. This is guaranteed if you call this API + * during {@link Activity#onCreate}. + * + * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. + * + * @param uris an array of Uri(s) to push over Android Beam + * @param activity activity for which the Uri(s) will be pushed + */ public void setBeamPushUris(Uri[] uris, Activity activity) { if (activity == null) { throw new NullPointerException("activity cannot be null"); } + if (uris != null) { + for (Uri uri : uris) { + if (uri == null) throw new NullPointerException("Uri not " + + "allowed to be null"); + String scheme = uri.getScheme(); + if (scheme == null || (!scheme.equalsIgnoreCase("file") && + !scheme.equalsIgnoreCase("content"))) { + throw new IllegalArgumentException("URI needs to have " + + "either scheme file or scheme content"); + } + } + } mNfcActivityManager.setNdefPushContentUri(activity, uris); } - // TODO javadoc + /** + * Set a callback that will dynamically generate one or more {@link Uri}s + * to send using Android Beam (TM). Every Uri the callback provides + * must have either scheme 'file' or scheme 'content'. + * + * <p>For the data provided through this callback, Android Beam tries to + * switch to alternate transports such as Bluetooth to achieve a fast + * transfer speed. Hence this method is very suitable + * for transferring large files such as pictures or songs. + * + * <p>The receiving side will store the content of each Uri in + * a file and present a notification to the user to open the file + * with a {@link android.content.Intent} with action + * {@link android.content.Intent#ACTION_VIEW}. + * If multiple URIs are sent, the {@link android.content.Intent} will refer + * to the first of the stored files. + * + * <p>This method may be called at any time before {@link Activity#onDestroy}, + * but the URI(s) are only made available for Android Beam when the + * specified activity(s) are in resumed (foreground) state. The recommended + * approach is to call this method during your Activity's + * {@link Activity#onCreate} - see sample + * code below. This method does not immediately perform any I/O or blocking work, + * so is safe to call on your main thread. + * + * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback} + * have priority over both {@link #setNdefPushMessage} and + * {@link #setNdefPushMessageCallback}. + * + * <p>If {@link #setBeamPushUris} is called with a null Uri array, + * and/or {@link #setBeamPushUrisCallback} is called with a null callback, + * then the Uri push will be completely disabled for the specified activity(s). + * + * <p>Code example: + * <pre> + * protected void onCreate(Bundle savedInstanceState) { + * super.onCreate(savedInstanceState); + * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); + * if (nfcAdapter == null) return; // NFC not available on this device + * nfcAdapter.setBeamPushUrisCallback(callback, this); + * } + * </pre> + * And that is it. Only one call per activity is necessary. The Android + * OS will automatically release its references to the Uri(s) and the + * Activity object when it is destroyed if you follow this pattern. + * + * <p class="note">Do not pass in an Activity that has already been through + * {@link Activity#onDestroy}. This is guaranteed if you call this API + * during {@link Activity#onCreate}. + * + * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. + * + * @param callback callback, or null to disable + * @param activity activity for which the Uri(s) will be pushed + */ public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) { if (activity == null) { throw new NullPointerException("activity cannot be null"); @@ -663,6 +784,10 @@ public final class NfcAdapter { * {@link Activity#onDestroy}. This is guaranteed if you call this API * during {@link Activity#onCreate}. * + * <p class="note">For sending large content such as pictures and songs, + * consider using {@link #setBeamPushUris}, which switches to alternate transports + * such as Bluetooth to achieve a fast transfer rate. + * * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @param message NDEF message to push over NFC, or null to disable @@ -753,7 +878,9 @@ public final class NfcAdapter { * <p class="note">Do not pass in an Activity that has already been through * {@link Activity#onDestroy}. This is guaranteed if you call this API * during {@link Activity#onCreate}. - * + * <p class="note">For sending large content such as pictures and songs, + * consider using {@link #setBeamPushUris}, which switches to alternate transports + * such as Bluetooth to achieve a fast transfer rate. * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @param callback callback, or null to disable diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 97ed235..69e1de9 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -36,6 +36,13 @@ import java.util.concurrent.atomic.AtomicInteger; * perform background operations and publish results on the UI thread without * having to manipulate threads and/or handlers.</p> * + * <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} + * and does not constitute a generic threading framework. AsyncTasks should ideally be + * used for short operations (a few seconds at the most.) If you need to keep threads + * running for long periods of time, it is highly recommended you use the various APIs + * provided by the <code>java.util.concurrent</code> pacakge such as {@link Executor}, + * {@link ThreadPoolExecutor} and {@link FutureTask}.</p> + * * <p>An asynchronous task is defined by a computation that runs on a background thread and * whose result is published on the UI thread. An asynchronous task is defined by 3 generic * types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>, @@ -63,6 +70,8 @@ import java.util.concurrent.atomic.AtomicInteger; * for (int i = 0; i < count; i++) { * totalSize += Downloader.downloadFile(urls[i]); * publishProgress((int) ((i / (float) count) * 100)); + * // Escape early if cancel() is called + * if (isCancelled()) break; * } * return totalSize; * } @@ -154,6 +163,16 @@ import java.util.concurrent.atomic.AtomicInteger; * <li>Set member fields in {@link #doInBackground}, and refer to them in * {@link #onProgressUpdate} and {@link #onPostExecute}. * </ul> + * + * <h2>Order of execution</h2> + * <p>When first introduced, AsyncTasks were executed serially on a single background + * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting with + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single + * thread to avoid common application errors caused by parallel execution.</p> + * <p>If you truly want parallel execution, you can invoke + * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with + * {@link #THREAD_POOL_EXECUTOR}.</p> */ public abstract class AsyncTask<Params, Progress, Result> { private static final String LOG_TAG = "AsyncTask"; @@ -491,13 +510,13 @@ public abstract class AsyncTask<Params, Progress, Result> { * thread or pool of threads depending on the platform version. When first * introduced, AsyncTasks were executed serially on a single background thread. * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed - * to a pool of threads allowing multiple tasks to operate in parallel. After - * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, it is planned to change this - * back to a single thread to avoid common application errors caused + * to a pool of threads allowing multiple tasks to operate in parallel. Starting + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being + * executed on a single thread to avoid common application errors caused * by parallel execution. If you truly want parallel execution, you can use * the {@link #executeOnExecutor} version of this method - * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings on - * its use. + * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings + * on its use. * * <p>This method must be invoked on the UI thread. * @@ -507,6 +526,9 @@ public abstract class AsyncTask<Params, Progress, Result> { * * @throws IllegalStateException If {@link #getStatus()} returns either * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + * + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + * @see #execute(Runnable) */ public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); @@ -542,6 +564,8 @@ public abstract class AsyncTask<Params, Progress, Result> { * * @throws IllegalStateException If {@link #getStatus()} returns either * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + * + * @see #execute(Object[]) */ public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { @@ -569,7 +593,11 @@ public abstract class AsyncTask<Params, Progress, Result> { /** * Convenience version of {@link #execute(Object...)} for use with - * a simple Runnable object. + * a simple Runnable object. See {@link #execute(Object[])} for more + * information on the order of execution. + * + * @see #execute(Object[]) + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) */ public static void execute(Runnable runnable) { sDefaultExecutor.execute(runnable); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index f7f0263..679cf1a 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -94,6 +94,16 @@ public class Environment { } /** + * Return directory used for internal media storage, which is protected by + * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}. + * + * @hide + */ + public static File getMediaStorageDirectory() { + return MEDIA_STORAGE_DIRECTORY; + } + + /** * Returns whether the Encrypted File System feature is enabled on the device or not. * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code> * if disabled. @@ -112,6 +122,10 @@ public class Environment { private static final File SECURE_DATA_DIRECTORY = getDirectory("ANDROID_SECURE_DATA", "/data/secure"); + /** @hide */ + private static final File MEDIA_STORAGE_DIRECTORY + = getDirectory("MEDIA_STORAGE", "/data/media"); + private static final File EXTERNAL_STORAGE_DIRECTORY = getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"); diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index a06aadb..02135bc 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -127,29 +127,17 @@ public class Looper { return; } - long wallStart = 0; - long threadStart = 0; - // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); - wallStart = SystemClock.currentTimeMicro(); - threadStart = SystemClock.currentThreadTimeMicro(); } msg.target.dispatchMessage(msg); if (logging != null) { - long wallTime = SystemClock.currentTimeMicro() - wallStart; - long threadTime = SystemClock.currentThreadTimeMicro() - threadStart; - logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); - if (logging instanceof Profiler) { - ((Profiler) logging).profile(msg, wallStart, wallTime, - threadStart, threadTime); - } } // Make sure that during the course of dispatching the @@ -290,12 +278,4 @@ public class Looper { public String toString() { return "Looper{" + Integer.toHexString(System.identityHashCode(this)) + "}"; } - - /** - * @hide - */ - public static interface Profiler { - void profile(Message message, long wallStart, long wallTime, - long threadStart, long threadTime); - } } diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index c0240fe..d2050b7 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -43,7 +43,7 @@ public final class Trace { public static final int TRACE_FLAGS_START_BIT = 1; public static final String[] TRACE_TAGS = { "Graphics", "Input", "View", "WebView", "Window Manager", - "Activity Manager", "Sync Manager", "Audio" + "Activity Manager", "Sync Manager", "Audio", "Video", }; public static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags"; diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index f4abda6..ab64866 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -1360,7 +1360,14 @@ public interface IMountService extends IInterface { */ public Parcelable[] getVolumeList() throws RemoteException; - public String getSecureContainerFilesystemPath(String id) throws RemoteException; + /** + * Gets the path on the filesystem for the ASEC container itself. + * + * @param cid ASEC container ID + * @return path to filesystem or {@code null} if it's not found + * @throws RemoteException + */ + public String getSecureContainerFilesystemPath(String cid) throws RemoteException; /* * Fix permissions in a container which has just been created and populated. diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 2c4b863..79c8f3b 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -16,6 +16,7 @@ package android.os.storage; +import android.content.Context; import android.os.Parcel; import android.os.Parcelable; @@ -28,7 +29,7 @@ public class StorageVolume implements Parcelable { //private static final String TAG = "StorageVolume"; private final String mPath; - private final String mDescription; + private final int mDescriptionId; private final boolean mRemovable; private final boolean mEmulated; private final int mMtpReserveSpace; @@ -42,10 +43,10 @@ public class StorageVolume implements Parcelable { // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts. public static final String EXTRA_STORAGE_VOLUME = "storage_volume"; - public StorageVolume(String path, String description, boolean removable, + public StorageVolume(String path, int descriptionId, boolean removable, boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize) { mPath = path; - mDescription = description; + mDescriptionId = descriptionId; mRemovable = removable; mEmulated = emulated; mMtpReserveSpace = mtpReserveSpace; @@ -54,11 +55,11 @@ public class StorageVolume implements Parcelable { } // for parcelling only - private StorageVolume(String path, String description, boolean removable, + private StorageVolume(String path, int descriptionId, boolean removable, boolean emulated, int mtpReserveSpace, int storageId, boolean allowMassStorage, long maxFileSize) { mPath = path; - mDescription = description; + mDescriptionId = descriptionId; mRemovable = removable; mEmulated = emulated; mMtpReserveSpace = mtpReserveSpace; @@ -81,8 +82,12 @@ public class StorageVolume implements Parcelable { * * @return the volume description */ - public String getDescription() { - return mDescription; + public String getDescription(Context context) { + return context.getResources().getString(mDescriptionId); + } + + public int getDescriptionId() { + return mDescriptionId; } /** @@ -172,8 +177,8 @@ public class StorageVolume implements Parcelable { @Override public String toString() { - return "StorageVolume [mAllowMassStorage=" + mAllowMassStorage + ", mDescription=" - + mDescription + ", mEmulated=" + mEmulated + ", mMaxFileSize=" + mMaxFileSize + return "StorageVolume [mAllowMassStorage=" + mAllowMassStorage + ", mDescriptionId=" + + mDescriptionId + ", mEmulated=" + mEmulated + ", mMaxFileSize=" + mMaxFileSize + ", mMtpReserveSpace=" + mMtpReserveSpace + ", mPath=" + mPath + ", mRemovable=" + mRemovable + ", mStorageId=" + mStorageId + "]"; } @@ -182,14 +187,14 @@ public class StorageVolume implements Parcelable { new Parcelable.Creator<StorageVolume>() { public StorageVolume createFromParcel(Parcel in) { String path = in.readString(); - String description = in.readString(); + int descriptionId = in.readInt(); int removable = in.readInt(); int emulated = in.readInt(); int storageId = in.readInt(); int mtpReserveSpace = in.readInt(); int allowMassStorage = in.readInt(); long maxFileSize = in.readLong(); - return new StorageVolume(path, description, + return new StorageVolume(path, descriptionId, removable == 1, emulated == 1, mtpReserveSpace, storageId, allowMassStorage == 1, maxFileSize); } @@ -205,7 +210,7 @@ public class StorageVolume implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mPath); - parcel.writeString(mDescription); + parcel.writeInt(mDescriptionId); parcel.writeInt(mRemovable ? 1 : 0); parcel.writeInt(mEmulated ? 1 : 0); parcel.writeInt(mStorageId); diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java index c59ed18..a643c8a 100644 --- a/core/java/android/preference/DialogPreference.java +++ b/core/java/android/preference/DialogPreference.java @@ -261,6 +261,8 @@ public abstract class DialogPreference extends Preference implements @Override protected void onClick() { + if (mDialog != null && mDialog.isShowing()) return; + showDialog(null); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ea3cab4..8b7ee0e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4210,6 +4210,8 @@ public final class Settings { public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes"; /** {@hide} */ public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled"; + /** {@hide} */ + public static final String NETSTATS_REPORT_XT_OVER_DEV = "netstats_report_xt_over_dev"; /** {@hide} */ public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration"; @@ -4266,6 +4268,13 @@ public final class Settings { "contacts_preauth_uri_expiration"; /** + * Prefix for SMS short code regex patterns (country code is appended). + * @see com.android.internal.telephony.SmsUsageMonitor + * @hide + */ + public static final String SMS_SHORT_CODES_PREFIX = "sms_short_codes_"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 3cf207f..97c0209 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -530,7 +530,7 @@ public class BluetoothService extends IBluetooth.Stub { // Bluetooth stack needs a small delay here before adding // SDP records, otherwise dbus stalls for over 30 seconds 1 out of 50 runs try { - Thread.sleep(20); + Thread.sleep(50); } catch (InterruptedException e) {} updateSdpRecords(); return true; @@ -602,7 +602,7 @@ public class BluetoothService extends IBluetooth.Stub { // Bluetooth stack need some a small delay here before adding more // SDP records, otherwise dbus stalls for over 30 seconds 1 out of 50 runs try { - Thread.sleep(20); + Thread.sleep(50); } catch (InterruptedException e) {} if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) { diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java index fd709f2..457e66c 100644 --- a/core/java/android/speech/RecognizerIntent.java +++ b/core/java/android/speech/RecognizerIntent.java @@ -115,6 +115,45 @@ public class RecognizerIntent { public static final String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH"; /** + * Starts an activity that will prompt the user for speech without requiring the user's + * visual attention or touch input. It will send it through a speech recognizer, + * and either synthesize speech for a web search result or trigger + * another type of action based on the user's speech. + * + * This activity may be launched while device is locked in a secure mode. + * Special care must be taken to ensure that the voice actions that are performed while + * hands free cannot compromise the device's security. + * The activity should check the value of the {@link #EXTRA_SECURE} extra to determine + * whether the device has been securely locked. If so, the activity should either restrict + * the set of voice actions that are permitted or require some form of secure + * authentication before proceeding. + * + * To ensure that the activity's user interface is visible while the lock screen is showing, + * the activity should set the + * {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} window flag. + * Otherwise the activity's user interface may be hidden by the lock screen. The activity + * should take care not to leak private information when the device is securely locked. + * + * <p>Optional extras: + * <ul> + * <li>{@link #EXTRA_SECURE} + * </ul> + */ + public static final String ACTION_VOICE_SEARCH_HANDS_FREE = + "android.speech.action.VOICE_SEARCH_HANDS_FREE"; + + /** + * Optional boolean to indicate that a "hands free" voice search was performed while the device + * was in a secure mode. An example of secure mode is when the device's screen lock is active, + * and it requires some form of authentication to be unlocked. + * + * When the device is securely locked, the voice search activity should either restrict + * the set of voice actions that are permitted, or require some form of secure authentication + * before proceeding. + */ + public static final String EXTRA_SECURE = "android.speech.extras.EXTRA_SECURE"; + + /** * The minimum length of an utterance. We will not stop recording before this amount of time. * * Note that it is extremely rare you'd want to specify this value in an intent. If you don't diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java index 386c866..cd54f24 100644 --- a/core/java/android/view/AccessibilityIterators.java +++ b/core/java/android/view/AccessibilityIterators.java @@ -287,12 +287,12 @@ public final class AccessibilityIterators { } } } - while (start < textLength && mText.charAt(start) == '\n') { - start++; - } if (start < 0) { return null; } + while (start < textLength && mText.charAt(start) == '\n') { + start++; + } int end = start; for (int i = end + 1; i < textLength; i++) { end = i; diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 825f351..aaa081c 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -24,16 +24,46 @@ import android.os.SystemProperties; import android.util.Log; /** - * Coordinates animations and drawing for UI on a particular thread. - * - * This object is thread-safe. Other threads can post callbacks to run at a later time - * on the UI thread. - * - * Ensuring thread-safety is a little tricky because the {@link DisplayEventReceiver} - * can only be accessed from the UI thread so operations that touch the event receiver - * are posted to the UI thread if needed. - * - * @hide + * Coordinates the timing of animations, input and drawing. + * <p> + * The choreographer receives timing pulses (such as vertical synchronization) + * from the display subsystem then schedules work to occur as part of rendering + * the next display frame. + * </p><p> + * Applications typically interact with the choreographer indirectly using + * higher level abstractions in the animation framework or the view hierarchy. + * Here are some examples of things you can do using the higher-level APIs. + * </p> + * <ul> + * <li>To post an animation to be processed on a regular time basis synchronized with + * display frame rendering, use {@link android.animation.ValueAnimator#start}.</li> + * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display + * frame, use {@link View#postOnAnimation}.</li> + * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display + * frame after a delay, use {@link View#postOnAnimationDelayed}.</li> + * <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the + * next display frame, use {@link View#postInvalidateOnAnimation()} or + * {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li> + * <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in + * sync with display frame rendering, do nothing. This already happens automatically. + * {@link View#onDraw} will be called at the appropriate time.</li> + * </ul> + * <p> + * However, there are a few cases where you might want to use the functions of the + * choreographer directly in your application. Here are some examples. + * </p> + * <ul> + * <li>If your application does its rendering in a different thread, possibly using GL, + * or does not use the animation framework or view hierarchy at all + * and you want to ensure that it is appropriately synchronized with the display, then use + * {@link Choreographer#postFrameCallback}.</li> + * <li>... and that's about it.</li> + * </ul> + * <p> + * Each {@link Looper} thread has its own choreographer. Other threads can + * post callbacks to run on the choreographer but they will run on the {@link Looper} + * to which the choreographer belongs. + * </p> */ public final class Choreographer { private static final String TAG = "Choreographer"; @@ -79,13 +109,22 @@ public final class Choreographer { private static final int MSG_DO_SCHEDULE_VSYNC = 1; private static final int MSG_DO_SCHEDULE_CALLBACK = 2; + // All frame callbacks posted by applications have this token. + private static final Object FRAME_CALLBACK_TOKEN = new Object() { + public String toString() { return "FRAME_CALLBACK_TOKEN"; } + }; + private final Object mLock = new Object(); private final Looper mLooper; private final FrameHandler mHandler; + + // The display event receiver can only be accessed by the looper thread to which + // it is attached. We take care to ensure that we post message to the looper + // if appropriate when interacting with the display event receiver. private final FrameDisplayEventReceiver mDisplayEventReceiver; - private Callback mCallbackPool; + private CallbackRecord mCallbackPool; private final CallbackQueue[] mCallbackQueues; @@ -96,17 +135,20 @@ public final class Choreographer { /** * Callback type: Input callback. Runs first. + * @hide */ public static final int CALLBACK_INPUT = 0; /** * Callback type: Animation callback. Runs before traversals. + * @hide */ public static final int CALLBACK_ANIMATION = 1; /** * Callback type: Traversal callback. Handles layout and draw. Runs last * after all other asynchronous messages have been handled. + * @hide */ public static final int CALLBACK_TRAVERSAL = 2; @@ -138,32 +180,38 @@ public final class Choreographer { } /** - * The amount of time, in milliseconds, between each frame of the animation. This is a - * requested time that the animation will attempt to honor, but the actual delay between - * frames may be different, depending on system load and capabilities. This is a static + * The amount of time, in milliseconds, between each frame of the animation. + * <p> + * This is a requested time that the animation will attempt to honor, but the actual delay + * between frames may be different, depending on system load and capabilities. This is a static * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. - * + * </p><p> * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. + * </p> * * @return the requested time between frames, in milliseconds + * @hide */ public static long getFrameDelay() { return sFrameDelay; } /** - * The amount of time, in milliseconds, between each frame of the animation. This is a - * requested time that the animation will attempt to honor, but the actual delay between - * frames may be different, depending on system load and capabilities. This is a static + * The amount of time, in milliseconds, between each frame of the animation. + * <p> + * This is a requested time that the animation will attempt to honor, but the actual delay + * between frames may be different, depending on system load and capabilities. This is a static * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. - * + * </p><p> * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. + * </p> * * @param frameDelay the requested time between frames, in milliseconds + * @hide */ public static void setFrameDelay(long frameDelay) { sFrameDelay = frameDelay; @@ -171,23 +219,25 @@ public final class Choreographer { /** * Subtracts typical frame delay time from a delay interval in milliseconds. - * + * <p> * This method can be used to compensate for animation delay times that have baked * in assumptions about the frame delay. For example, it's quite common for code to * assume a 60Hz frame time and bake in a 16ms delay. When we call * {@link #postAnimationCallbackDelayed} we want to know how long to wait before * posting the animation callback but let the animation timer take care of the remaining * frame delay time. - * + * </p><p> * This method is somewhat conservative about how much of the frame delay it * subtracts. It uses the same value returned by {@link #getFrameDelay} which by * default is 10ms even though many parts of the system assume 16ms. Consequently, * we might still wait 6ms before posting an animation callback that we want to run * on the next frame, but this is much better than waiting a whole 16ms and likely * missing the deadline. + * </p> * * @param delayMillis The original delay time including an assumed frame delay. * @return The adjusted delay time with the assumed frame delay subtracted out. + * @hide */ public static long subtractFrameDelay(long delayMillis) { final long frameDelay = sFrameDelay; @@ -196,21 +246,26 @@ public final class Choreographer { /** * Posts a callback to run on the next frame. - * The callback only runs once and then is automatically removed. + * <p> + * The callback runs once then is automatically removed. + * </p> * * @param callbackType The callback type. * @param action The callback action to run during the next frame. * @param token The callback token, or null if none. * * @see #removeCallbacks + * @hide */ public void postCallback(int callbackType, Runnable action, Object token) { postCallbackDelayed(callbackType, action, token, 0); } /** - * Posts a callback to run on the next frame following the specified delay. - * The callback only runs once and then is automatically removed. + * Posts a callback to run on the next frame after the specified delay. + * <p> + * The callback runs once then is automatically removed. + * </p> * * @param callbackType The callback type. * @param action The callback action to run during the next frame after the specified delay. @@ -218,6 +273,7 @@ public final class Choreographer { * @param delayMillis The delay time in milliseconds. * * @see #removeCallback + * @hide */ public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { @@ -228,6 +284,11 @@ public final class Choreographer { throw new IllegalArgumentException("callbackType is invalid"); } + postCallbackDelayedInternal(callbackType, action, token, delayMillis); + } + + private void postCallbackDelayedInternal(int callbackType, + Object action, Object token, long delayMillis) { if (DEBUG) { Log.d(TAG, "PostCallback: type=" + callbackType + ", action=" + action + ", token=" + token @@ -261,12 +322,17 @@ public final class Choreographer { * * @see #postCallback * @see #postCallbackDelayed + * @hide */ public void removeCallbacks(int callbackType, Runnable action, Object token) { if (callbackType < 0 || callbackType > CALLBACK_LAST) { throw new IllegalArgumentException("callbackType is invalid"); } + removeCallbacksInternal(callbackType, action, token); + } + + private void removeCallbacksInternal(int callbackType, Object action, Object token) { if (DEBUG) { Log.d(TAG, "RemoveCallbacks: type=" + callbackType + ", action=" + action + ", token=" + token); @@ -281,17 +347,81 @@ public final class Choreographer { } /** - * Gets the time when the current frame started. The frame time should be used - * instead of {@link SystemClock#uptimeMillis()} to synchronize animations. - * This helps to reduce inter-frame jitter because the frame time is fixed at the - * time the frame was scheduled to start, regardless of when the animations or - * drawing code actually ran. + * Posts a frame callback to run on the next frame. + * <p> + * The callback runs once then is automatically removed. + * </p> + * + * @param callback The frame callback to run during the next frame. + * + * @see #postFrameCallbackDelayed + * @see #removeFrameCallback + */ + public void postFrameCallback(FrameCallback callback) { + postFrameCallbackDelayed(callback, 0); + } + + /** + * Posts a frame callback to run on the next frame after the specified delay. + * <p> + * The callback runs once then is automatically removed. + * </p> + * + * @param callback The frame callback to run during the next frame. + * @param delayMillis The delay time in milliseconds. + * + * @see #postFrameCallback + * @see #removeFrameCallback + */ + public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + postCallbackDelayedInternal(CALLBACK_ANIMATION, + callback, FRAME_CALLBACK_TOKEN, delayMillis); + } + + /** + * Removes a previously posted frame callback. + * + * @param callback The frame callback to remove. * + * @see #postFrameCallback + * @see #postFrameCallbackDelayed + */ + public void removeFrameCallback(FrameCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN); + } + + /** + * Gets the time when the current frame started. + * <p> + * This method provides the time in nanoseconds when the frame started being rendered. + * The frame time provides a stable time base for synchronizing animations + * and drawing. It should be used instead of {@link SystemClock#uptimeMillis()} + * or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame + * time helps to reduce inter-frame jitter because the frame time is fixed at the time + * the frame was scheduled to start, regardless of when the animations or drawing + * callback actually runs. All callbacks that run as part of rendering a frame will + * observe the same frame time so using the frame time also helps to synchronize effects + * that are performed by different callbacks. + * </p><p> + * Please note that the framework already takes care to process animations and + * drawing using the frame time as a stable time base. Most applications should + * not need to use the frame time information directly. + * </p><p> * This method should only be called from within a callback. + * </p> * * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base. * * @throws IllegalStateException if no frame is in progress. + * @hide */ public long getFrameTime() { return getFrameTimeNanos() / NANOS_PER_MS; @@ -303,6 +433,7 @@ public final class Choreographer { * @return The frame start time, in the {@link System#nanoTime()} time base. * * @throws IllegalStateException if no frame is in progress. + * @hide */ public long getFrameTimeNanos() { synchronized (mLock) { @@ -345,7 +476,7 @@ public final class Choreographer { } } - void doFrame(long timestampNanos, int frame) { + void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (!mFrameScheduled) { @@ -353,7 +484,7 @@ public final class Choreographer { } startNanos = System.nanoTime(); - final long jitterNanos = startNanos - timestampNanos; + final long jitterNanos = startNanos - frameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; if (DEBUG) { @@ -363,10 +494,10 @@ public final class Choreographer { + "Setting frame time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); } - timestampNanos = startNanos - lastFrameOffset; + frameTimeNanos = startNanos - lastFrameOffset; } - if (timestampNanos < mLastFrameTimeNanos) { + if (frameTimeNanos < mLastFrameTimeNanos) { if (DEBUG) { Log.d(TAG, "Frame time appears to be going backwards. May be due to a " + "previously skipped frame. Waiting for next vsync"); @@ -376,24 +507,27 @@ public final class Choreographer { } mFrameScheduled = false; - mLastFrameTimeNanos = timestampNanos; + mLastFrameTimeNanos = frameTimeNanos; } - doCallbacks(Choreographer.CALLBACK_INPUT); - doCallbacks(Choreographer.CALLBACK_ANIMATION); - doCallbacks(Choreographer.CALLBACK_TRAVERSAL); + doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); + doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); + doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); if (DEBUG) { final long endNanos = System.nanoTime(); Log.d(TAG, "Frame " + frame + ": Finished, took " + (endNanos - startNanos) * 0.000001f + " ms, latency " - + (startNanos - timestampNanos) * 0.000001f + " ms."); + + (startNanos - frameTimeNanos) * 0.000001f + " ms."); } } - void doCallbacks(int callbackType) { - Callback callbacks; + void doCallbacks(int callbackType, long frameTimeNanos) { + CallbackRecord callbacks; synchronized (mLock) { + // We use "now" to determine when callbacks become due because it's possible + // for earlier processing phases in a frame to post callbacks that should run + // in a following phase, such as an input event that causes an animation to start. final long now = SystemClock.uptimeMillis(); callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now); if (callbacks == null) { @@ -402,19 +536,19 @@ public final class Choreographer { mCallbacksRunning = true; } try { - for (Callback c = callbacks; c != null; c = c.next) { + for (CallbackRecord c = callbacks; c != null; c = c.next) { if (DEBUG) { Log.d(TAG, "RunCallback: type=" + callbackType + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); } - c.action.run(); + c.run(frameTimeNanos); } } finally { synchronized (mLock) { mCallbacksRunning = false; do { - final Callback next = callbacks.next; + final CallbackRecord next = callbacks.next; recycleCallbackLocked(callbacks); callbacks = next; } while (callbacks != null); @@ -449,10 +583,10 @@ public final class Choreographer { return Looper.myLooper() == mLooper; } - private Callback obtainCallbackLocked(long dueTime, Runnable action, Object token) { - Callback callback = mCallbackPool; + private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) { + CallbackRecord callback = mCallbackPool; if (callback == null) { - callback = new Callback(); + callback = new CallbackRecord(); } else { mCallbackPool = callback.next; callback.next = null; @@ -463,13 +597,44 @@ public final class Choreographer { return callback; } - private void recycleCallbackLocked(Callback callback) { + private void recycleCallbackLocked(CallbackRecord callback) { callback.action = null; callback.token = null; callback.next = mCallbackPool; mCallbackPool = callback; } + /** + * Implement this interface to receive a callback when a new display frame is + * being rendered. The callback is invoked on the {@link Looper} thread to + * which the {@link Choreographer} is attached. + */ + public interface FrameCallback { + /** + * Called when a new display frame is being rendered. + * <p> + * This method provides the time in nanoseconds when the frame started being rendered. + * The frame time provides a stable time base for synchronizing animations + * and drawing. It should be used instead of {@link SystemClock#uptimeMillis()} + * or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame + * time helps to reduce inter-frame jitter because the frame time is fixed at the time + * the frame was scheduled to start, regardless of when the animations or drawing + * callback actually runs. All callbacks that run as part of rendering a frame will + * observe the same frame time so using the frame time also helps to synchronize effects + * that are performed by different callbacks. + * </p><p> + * Please note that the framework already takes care to process animations and + * drawing using the frame time as a stable time base. Most applications should + * not need to use the frame time information directly. + * </p> + * + * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, + * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} + * to convert it to the {@link SystemClock#uptimeMillis()} time base. + */ + public void doFrame(long frameTimeNanos); + } + private final class FrameHandler extends Handler { public FrameHandler(Looper looper) { super(looper); @@ -491,39 +656,65 @@ public final class Choreographer { } } - private final class FrameDisplayEventReceiver extends DisplayEventReceiver { + private final class FrameDisplayEventReceiver extends DisplayEventReceiver + implements Runnable { + private long mTimestampNanos; + private int mFrame; + public FrameDisplayEventReceiver(Looper looper) { super(looper); } @Override public void onVsync(long timestampNanos, int frame) { - doFrame(timestampNanos, frame); + // Post the vsync event to the Handler. + // The idea is to prevent incoming vsync events from completely starving + // the message queue. If there are no messages in the queue with timestamps + // earlier than the frame time, then the vsync event will be processed immediately. + // Otherwise, messages that predate the vsync event will be handled first. + mTimestampNanos = timestampNanos; + mFrame = frame; + Message msg = Message.obtain(mHandler, this); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, timestampNanos / NANOS_PER_MS); + } + + @Override + public void run() { + doFrame(mTimestampNanos, mFrame); } } - private static final class Callback { - public Callback next; + private static final class CallbackRecord { + public CallbackRecord next; public long dueTime; - public Runnable action; + public Object action; // Runnable or FrameCallback public Object token; + + public void run(long frameTimeNanos) { + if (token == FRAME_CALLBACK_TOKEN) { + ((FrameCallback)action).doFrame(frameTimeNanos); + } else { + ((Runnable)action).run(); + } + } } private final class CallbackQueue { - private Callback mHead; + private CallbackRecord mHead; public boolean hasDueCallbacksLocked(long now) { return mHead != null && mHead.dueTime <= now; } - public Callback extractDueCallbacksLocked(long now) { - Callback callbacks = mHead; + public CallbackRecord extractDueCallbacksLocked(long now) { + CallbackRecord callbacks = mHead; if (callbacks == null || callbacks.dueTime > now) { return null; } - Callback last = callbacks; - Callback next = last.next; + CallbackRecord last = callbacks; + CallbackRecord next = last.next; while (next != null) { if (next.dueTime > now) { last.next = null; @@ -536,9 +727,9 @@ public final class Choreographer { return callbacks; } - public void addCallbackLocked(long dueTime, Runnable action, Object token) { - Callback callback = obtainCallbackLocked(dueTime, action, token); - Callback entry = mHead; + public void addCallbackLocked(long dueTime, Object action, Object token) { + CallbackRecord callback = obtainCallbackLocked(dueTime, action, token); + CallbackRecord entry = mHead; if (entry == null) { mHead = callback; return; @@ -558,10 +749,10 @@ public final class Choreographer { entry.next = callback; } - public void removeCallbacksLocked(Runnable action, Object token) { - Callback predecessor = null; - for (Callback callback = mHead; callback != null;) { - final Callback next = callback.next; + public void removeCallbacksLocked(Object action, Object token) { + CallbackRecord predecessor = null; + for (CallbackRecord callback = mHead; callback != null;) { + final CallbackRecord next = callback.next; if ((action == null || callback.action == action) && (token == null || callback.token == token)) { if (predecessor != null) { diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index 3dab174..da666b5 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -83,6 +83,12 @@ public abstract class DisplayList { public abstract void invalidate(); /** + * Clears additional resources held onto by this display list. You should + * only invoke this method after {@link #invalidate()}. + */ + public abstract void clear(); + + /** * Returns whether the display list is currently usable. If this returns false, * the display list should be re-recorded prior to replaying it. * diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index 0154556..08a5831 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -72,6 +72,13 @@ class GLES20DisplayList extends DisplayList { } @Override + public void clear() { + if (!mValid) { + mBitmaps.clear(); + } + } + + @Override public boolean isValid() { return mValid; } @@ -343,7 +350,6 @@ class GLES20DisplayList extends DisplayList { private static native void nSetPivotX(int displayList, float pivotX); private static native void nSetCaching(int displayList, boolean caching); private static native void nSetClipChildren(int displayList, boolean clipChildren); - private static native void nSetApplicationScale(int displayList, float scale); private static native void nSetAlpha(int displayList, float alpha); private static native void nSetHasOverlappingRendering(int displayList, boolean hasOverlappingRendering); diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 25d08ac..0114a41 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -585,8 +585,12 @@ public class GestureDetector { } // Hold the event we obtained above - listeners may have changed the original. mPreviousUpEvent = currentUpEvent; - mVelocityTracker.recycle(); - mVelocityTracker = null; + if (mVelocityTracker != null) { + // This may have been cleared when we called out to the + // application above. + mVelocityTracker.recycle(); + mVelocityTracker = null; + } mIsDoubleTapping = false; mHandler.removeMessages(SHOW_PRESS); mHandler.removeMessages(LONG_PRESS); diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index e25e2ef..f986d15 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -647,15 +647,10 @@ public abstract class HardwareRenderer { Log.d(LOG_TAG, "Disabling v-sync"); } - //noinspection PointlessBooleanExpression,ConstantConditions - if (!ViewDebug.DEBUG_LATENCY) { - property = SystemProperties.get(PROFILE_PROPERTY, "false"); - mProfileEnabled = "true".equalsIgnoreCase(property); - if (mProfileEnabled) { - Log.d(LOG_TAG, "Profiling hardware renderer"); - } - } else { - mProfileEnabled = true; + property = SystemProperties.get(PROFILE_PROPERTY, "false"); + mProfileEnabled = "true".equalsIgnoreCase(property); + if (mProfileEnabled) { + Log.d(LOG_TAG, "Profiling hardware renderer"); } if (mProfileEnabled) { @@ -1132,11 +1127,6 @@ public abstract class HardwareRenderer { float total = (now - getDisplayListStartTime) * 0.000001f; //noinspection PointlessArithmeticExpression mProfileData[mProfileCurrentFrame] = total; - - if (ViewDebug.DEBUG_LATENCY) { - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- getDisplayList() took " + - total + "ms"); - } } if (displayList != null) { @@ -1152,11 +1142,6 @@ public abstract class HardwareRenderer { long now = System.nanoTime(); float total = (now - drawDisplayListStartTime) * 0.000001f; mProfileData[mProfileCurrentFrame + 1] = total; - - if (ViewDebug.DEBUG_LATENCY) { - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- drawDisplayList() took " + - total + "ms, status=" + status); - } } handleFunctorStatus(attachInfo, status); @@ -1198,11 +1183,6 @@ public abstract class HardwareRenderer { long now = System.nanoTime(); float total = (now - eglSwapBuffersStartTime) * 0.000001f; mProfileData[mProfileCurrentFrame + 2] = total; - - if (ViewDebug.DEBUG_LATENCY) { - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- eglSwapBuffers() took " + - total + "ms"); - } } checkEglErrors(); diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index c46e43a..a7ee12b 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -65,6 +65,7 @@ public class MenuInflater { private final Object[] mActionProviderConstructorArguments; private Context mContext; + private Object mRealOwner; /** * Constructs a menu inflater. @@ -73,6 +74,20 @@ public class MenuInflater { */ public MenuInflater(Context context) { mContext = context; + mRealOwner = context; + mActionViewConstructorArguments = new Object[] {context}; + mActionProviderConstructorArguments = mActionViewConstructorArguments; + } + + /** + * Constructs a menu inflater. + * + * @see Activity#getMenuInflater() + * @hide + */ + public MenuInflater(Context context, Object realOwner) { + mContext = context; + mRealOwner = realOwner; mActionViewConstructorArguments = new Object[] {context}; mActionProviderConstructorArguments = mActionViewConstructorArguments; } @@ -190,12 +205,12 @@ public class MenuInflater { implements MenuItem.OnMenuItemClickListener { private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class }; - private Context mContext; + private Object mRealOwner; private Method mMethod; - public InflatedOnMenuItemClickListener(Context context, String methodName) { - mContext = context; - Class<?> c = context.getClass(); + public InflatedOnMenuItemClickListener(Object realOwner, String methodName) { + mRealOwner = realOwner; + Class<?> c = realOwner.getClass(); try { mMethod = c.getMethod(methodName, PARAM_TYPES); } catch (Exception e) { @@ -210,9 +225,9 @@ public class MenuInflater { public boolean onMenuItemClick(MenuItem item) { try { if (mMethod.getReturnType() == Boolean.TYPE) { - return (Boolean) mMethod.invoke(mContext, item); + return (Boolean) mMethod.invoke(mRealOwner, item); } else { - mMethod.invoke(mContext, item); + mMethod.invoke(mRealOwner, item); return true; } } catch (Exception e) { @@ -400,7 +415,7 @@ public class MenuInflater { + "be used within a restricted context"); } item.setOnMenuItemClickListener( - new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName)); + new InflatedOnMenuItemClickListener(mRealOwner, itemListenerMethodName)); } if (item instanceof MenuItemImpl) { diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 2048de2..a719a01 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -23,6 +23,7 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.os.Looper; import android.util.AttributeSet; import android.util.Log; @@ -353,7 +354,12 @@ public class TextureView extends View { synchronized (mLock) { mUpdateLayer = true; } - postInvalidate(); + + if (Looper.myLooper() == Looper.getMainLooper()) { + invalidate(); + } else { + postInvalidate(); + } } }; mSurface.setOnFrameAvailableListener(mUpdateListener); @@ -368,6 +374,14 @@ public class TextureView extends View { // tell mLayer about it and set the SurfaceTexture to use the // current view size. mUpdateSurface = false; + + // Since we are updating the layer, force an update to ensure its + // parameters are correct (width, height, transform, etc.) + synchronized (mLock) { + mUpdateLayer = true; + } + mMatrixChanged = true; + mAttachInfo.mHardwareRenderer.setSurfaceTexture(mLayer, mSurface); nSetDefaultBufferSize(mSurface, getWidth(), getHeight()); } @@ -465,7 +479,7 @@ public class TextureView extends View { } private void applyTransformMatrix() { - if (mMatrixChanged) { + if (mMatrixChanged && mLayer != null) { mLayer.setTransform(mMatrix); mMatrixChanged = false; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a4fcd41..8cb5c85 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2255,9 +2255,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * flags, we would like a stable view of the content insets given to * {@link #fitSystemWindows(Rect)}. This means that the insets seen there * will always represent the worst case that the application can expect - * as a continue state. In practice this means with any of system bar, - * nav bar, and status bar shown, but not the space that would be needed - * for an input method. + * as a continuous state. In the stock Android UI this is the space for + * the system bar, nav bar, and status bar, but not more transient elements + * such as an input method. + * + * The stable layout your UI sees is based on the system UI modes you can + * switch to. That is, if you specify {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} + * then you will get a stable layout for changes of the + * {@link #SYSTEM_UI_FLAG_FULLSCREEN} mode; if you specify + * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} and + * {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}, then you can transition + * to {@link #SYSTEM_UI_FLAG_FULLSCREEN} and {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} + * with a stable layout. (Note that you should avoid using + * {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION} by itself.) + * + * If you have set the window flag {@ WindowManager.LayoutParams#FLAG_FULLSCREEN} + * to hide the status bar (instead of using {@link #SYSTEM_UI_FLAG_FULLSCREEN}), + * then a hidden status bar will be considered a "stable" state for purposes + * here. This allows your UI to continually hide the status bar, while still + * using the system UI flags to hide the action bar while still retaining + * a stable layout. Note that changing the window fullscreen flag will never + * provide a stable layout for a clean transition. * * <p>If you are using ActionBar in * overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY @@ -4307,7 +4325,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (gainFocus) { if (AccessibilityManager.getInstance(mContext).isEnabled()) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - requestAccessibilityFocus(); } } @@ -4776,11 +4793,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } - if (isClickable()) { + if (isClickable() && isEnabled()) { info.addAction(AccessibilityNodeInfo.ACTION_CLICK); } - if (isLongClickable()) { + if (isLongClickable() && isEnabled()) { info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); } @@ -5237,12 +5254,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * which you would like to ensure are not being covered. * * <p>The default implementation of this method simply applies the content - * inset's to the view's padding. This can be enabled through - * {@link #setFitsSystemWindows(boolean)}. Alternatively, you can override - * the method and handle the insets however you would like. Note that the - * insets provided by the framework are always relative to the far edges - * of the window, not accounting for the location of the called view within - * that window. (In fact when this method is called you do not yet know + * inset's to the view's padding, consuming that content (modifying the + * insets to be 0), and returning true. This behavior is off by default, but can + * be enabled through {@link #setFitsSystemWindows(boolean)}. + * + * <p>This function's traversal down the hierarchy is depth-first. The same content + * insets object is propagated down the hierarchy, so any changes made to it will + * be seen by all following views (including potentially ones above in + * the hierarchy since this is a depth-first traversal). The first view + * that returns true will abort the entire traversal. + * + * <p>The default implementation works well for a situation where it is + * used with a container that covers the entire window, allowing it to + * apply the appropriate insets to its content on all edges. If you need + * a more complicated layout (such as two different views fitting system + * windows, one on the top of the window, and one on the bottom), + * you can override the method and handle the insets however you would like. + * Note that the insets provided by the framework are always relative to the + * far edges of the window, not accounting for the location of the called view + * within that window. (In fact when this method is called you do not yet know * where the layout will place the view, as it is done before layout happens.) * * <p>Note: unlike many View methods, there is no dispatch phase to this @@ -5263,6 +5293,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return Return true if this view applied the insets and it should not * continue propagating further down the hierarchy, false otherwise. + * @see #getFitsSystemWindows() + * @see #setFitsSystemWindows() + * @see #setSystemUiVisibility(int) */ protected boolean fitSystemWindows(Rect insets) { if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { @@ -5283,16 +5316,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Set whether or not this view should account for system screen decorations - * such as the status bar and inset its content. This allows this view to be - * positioned in absolute screen coordinates and remain visible to the user. + * Sets whether or not this view should account for system screen decorations + * such as the status bar and inset its content; that is, controlling whether + * the default implementation of {@link #fitSystemWindows(Rect)} will be + * executed. See that method for more details. * - * <p>This should only be used by top-level window decor views. + * <p>Note that if you are providing your own implementation of + * {@link #fitSystemWindows(Rect)}, then there is no need to set this + * flag to true -- your implementation will be overriding the default + * implementation that checks this flag. * - * @param fitSystemWindows true to inset content for system screen decorations, false for - * default behavior. + * @param fitSystemWindows If true, then the default implementation of + * {@link #fitSystemWindows(Rect)} will be executed. * * @attr ref android.R.styleable#View_fitsSystemWindows + * @see #getFitsSystemWindows() + * @see #fitSystemWindows(Rect) + * @see #setSystemUiVisibility(int) */ public void setFitsSystemWindows(boolean fitSystemWindows) { setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS); @@ -5300,14 +5340,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Check for state of {@link #setFitsSystemWindows(boolean). If this method - * returns true, this view - * will account for system screen decorations such as the status bar and inset its - * content. This allows the view to be positioned in absolute screen coordinates - * and remain visible to the user. + * returns true, the default implementation of {@link #fitSystemWindows(Rect)} + * will be executed. * - * @return true if this view will adjust its content bounds for system screen decorations. + * @return Returns true if the default implementation of + * {@link #fitSystemWindows(Rect)} will be executed. * * @attr ref android.R.styleable#View_fitsSystemWindows + * @see #setFitsSystemWindows() + * @see #fitSystemWindows(Rect) + * @see #setSystemUiVisibility(int) */ public boolean getFitsSystemWindows() { return (mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS; @@ -6140,8 +6182,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); notifyAccessibilityStateChanged(); - // Try to give input focus to this view - not a descendant. - requestFocusNoSearch(View.FOCUS_DOWN, null); return true; } return false; @@ -6156,29 +6196,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ public void clearAccessibilityFocus() { - ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl != null) { - View focusHost = viewRootImpl.getAccessibilityFocusedHost(); - if (focusHost != null && ViewRootImpl.isViewDescendantOf(focusHost, this)) { - viewRootImpl.setAccessibilityFocusedHost(null); - } - } if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) { mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED; invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); notifyAccessibilityStateChanged(); - // Clear the text navigation state. setAccessibilityCursorPosition(-1); - - // Try to move accessibility focus to the input focus. - View rootView = getRootView(); - if (rootView != null) { - View inputFocus = rootView.findFocus(); - if (inputFocus != null) { - inputFocus.requestAccessibilityFocus(); - } + } + // Clear the global reference of accessibility focus if this + // view or any of its descendants had accessibility focus. + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + View focusHost = viewRootImpl.getAccessibilityFocusedHost(); + if (focusHost != null && ViewRootImpl.isViewDescendantOf(focusHost, this)) { + viewRootImpl.setAccessibilityFocusedHost(null); } } } @@ -6186,11 +6218,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private void requestAccessibilityFocusFromHover() { if (includeForAccessibility() && isActionableForAccessibility()) { requestAccessibilityFocus(); + requestFocusNoSearch(View.FOCUS_DOWN, null); } else { if (mParent != null) { View nextFocus = mParent.findViewToTakeAccessibilityFocusFromHover(this, this); if (nextFocus != null) { nextFocus.requestAccessibilityFocus(); + nextFocus.requestFocusNoSearch(View.FOCUS_DOWN, null); } } } @@ -6637,7 +6671,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private boolean nextAtGranularity(int granularity) { CharSequence text = getIterableTextForAccessibility(); - if (text != null && text.length() > 0) { + if (text == null || text.length() == 0) { return false; } TextSegmentIterator iterator = getIteratorForGranularity(granularity); @@ -6661,7 +6695,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private boolean previousAtGranularity(int granularity) { CharSequence text = getIterableTextForAccessibility(); - if (text != null && text.length() > 0) { + if (text == null || text.length() == 0) { return false; } TextSegmentIterator iterator = getIteratorForGranularity(granularity); @@ -6766,6 +6800,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void dispatchStartTemporaryDetach() { clearAccessibilityFocus(); + clearDisplayList(); + onStartTemporaryDetach(); } @@ -9868,10 +9904,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param dirty the rectangle representing the bounds of the dirty region */ public void invalidate(Rect dirty) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE); - } - if (skipInvalidate()) { return; } @@ -9915,10 +9947,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param b the bottom position of the dirty region */ public void invalidate(int l, int t, int r, int b) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE); - } - if (skipInvalidate()) { return; } @@ -9971,10 +9999,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * View's contents or dimensions have not changed. */ void invalidate(boolean invalidateCache) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE); - } - if (skipInvalidate()) { return; } @@ -11433,10 +11457,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } mAttachInfo.mViewRootImpl.cancelInvalidate(this); } else { - if (mDisplayList != null) { - // Should never happen - mDisplayList.invalidate(); - } + // Should never happen + clearDisplayList(); } mCurrentAnimation = null; @@ -12214,6 +12236,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return mDisplayList; } + private void clearDisplayList() { + if (mDisplayList != null) { + mDisplayList.invalidate(); + mDisplayList.clear(); + } + } + /** * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p> * @@ -12347,10 +12376,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mDrawingCache == null : mUnscaledDrawingCache == null)) { mCachingFailed = false; - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE); - } - int width = mRight - mLeft; int height = mBottom - mTop; @@ -12470,9 +12495,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); - } mPrivateFlags &= ~DIRTY_MASK; dispatchDraw(canvas); } else { @@ -12710,6 +12732,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!initialized) { a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight()); a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop); + if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler); onAnimationStart(); } @@ -12723,6 +12746,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } else { invalidationTransform = parent.mChildTransformation; } + if (more) { if (!a.willChangeBounds()) { if ((flags & (parent.FLAG_OPTIMIZE_INVALIDATE | parent.FLAG_ANIMATION_DONE)) == @@ -13086,9 +13110,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!hasDisplayList) { // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(parent, ViewDebug.HierarchyTraceType.DRAW); - } mPrivateFlags &= ~DIRTY_MASK; dispatchDraw(canvas); } else { @@ -13161,10 +13182,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param canvas The Canvas to which the View is rendered. */ public void draw(Canvas canvas) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); - } - final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); @@ -13508,10 +13525,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int oldR = mRight; boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); - } - onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; @@ -14767,60 +14780,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * @param consistency The type of consistency. See ViewDebug for more information. - * - * @hide - */ - protected boolean dispatchConsistencyCheck(int consistency) { - return onConsistencyCheck(consistency); - } - - /** - * Method that subclasses should implement to check their consistency. The type of - * consistency check is indicated by the bit field passed as a parameter. - * - * @param consistency The type of consistency. See ViewDebug for more information. - * - * @throws IllegalStateException if the view is in an inconsistent state. - * - * @hide - */ - protected boolean onConsistencyCheck(int consistency) { - boolean result = true; - - final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0; - final boolean checkDrawing = (consistency & ViewDebug.CONSISTENCY_DRAWING) != 0; - - if (checkLayout) { - if (getParent() == null) { - result = false; - android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, - "View " + this + " does not have a parent."); - } - - if (mAttachInfo == null) { - result = false; - android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, - "View " + this + " is not attached to a window."); - } - } - - if (checkDrawing) { - // Do not check the DIRTY/DRAWN flags because views can call invalidate() - // from their draw() method - - if ((mPrivateFlags & DRAWN) != DRAWN && - (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) { - result = false; - android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, - "View " + this + " was invalidated but its drawing cache is valid."); - } - } - - return result; - } - - /** * Prints information about this view in the log output, with the tag * {@link #VIEW_LOG_TAG}. * @@ -14933,10 +14892,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * tree. */ public void requestLayout() { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT); - } - mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; @@ -14987,10 +14942,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // first clears the measured dimension flag mPrivateFlags &= ~MEASURED_DIMENSION_SET; - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE); - } - // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 9134966..823befb 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -193,19 +193,12 @@ public class ViewConfiguration { private static final int MAXIMUM_FLING_VELOCITY = 8000; /** - * Distance in dips between a touch up event denoting the end of a touch exploration - * gesture and the touch up event of a subsequent tap for the latter tap to be - * considered as a tap i.e. to perform a click. - */ - private static final int TOUCH_EXPLORE_TAP_SLOP = 80; - - /** * Delay before dispatching a recurring accessibility event in milliseconds. * This delay guarantees that a recurring event will be send at most once * during the {@link #SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS} time * frame. */ - private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 400; + private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 100; /** * The maximum size of View's drawing cache, expressed in bytes. This size @@ -238,7 +231,6 @@ public class ViewConfiguration { private final int mDoubleTapTouchSlop; private final int mPagingTouchSlop; private final int mDoubleTapSlop; - private final int mScaledTouchExploreTapSlop; private final int mWindowTouchSlop; private final int mMaximumDrawingCacheSize; private final int mOverscrollDistance; @@ -265,7 +257,6 @@ public class ViewConfiguration { mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP; mPagingTouchSlop = PAGING_TOUCH_SLOP; mDoubleTapSlop = DOUBLE_TAP_SLOP; - mScaledTouchExploreTapSlop = TOUCH_EXPLORE_TAP_SLOP; mWindowTouchSlop = WINDOW_TOUCH_SLOP; //noinspection deprecation mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE; @@ -302,7 +293,6 @@ public class ViewConfiguration { mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f); mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f); mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f); - mScaledTouchExploreTapSlop = (int) (density * TOUCH_EXPLORE_TAP_SLOP + 0.5f); mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f); final Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); @@ -553,17 +543,6 @@ public class ViewConfiguration { } /** - * @return Distance in pixels between a touch up event denoting the end of a touch exploration - * gesture and the touch up event of a subsequent tap for the latter tap to be - * considered as a tap i.e. to perform a click. - * - * @hide - */ - public int getScaledTouchExploreTapSlop() { - return mScaledTouchExploreTapSlop; - } - - /** * Interval for dispatching a recurring accessibility event in milliseconds. * This interval guarantees that a recurring event will be send at most once * during the {@link #getSendRecurringAccessibilityEventsInterval()} time frame. diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index cb37a1c..dd671dc 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -22,24 +22,14 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Debug; -import android.os.Environment; -import android.os.Looper; -import android.os.Message; -import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; -import android.util.Printer; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -51,14 +41,8 @@ import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -67,107 +51,24 @@ import java.util.concurrent.TimeUnit; */ public class ViewDebug { /** - * Log tag used to log errors related to the consistency of the view hierarchy. - * - * @hide - */ - public static final String CONSISTENCY_LOG_TAG = "ViewConsistency"; - - /** - * Flag indicating the consistency check should check layout-related properties. - * - * @hide - */ - public static final int CONSISTENCY_LAYOUT = 0x1; - - /** - * Flag indicating the consistency check should check drawing-related properties. - * - * @hide - */ - public static final int CONSISTENCY_DRAWING = 0x2; - - /** - * Enables or disables view hierarchy tracing. Any invoker of - * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first - * check that this value is set to true as not to affect performance. + * @deprecated This flag is now unused */ + @Deprecated public static final boolean TRACE_HIERARCHY = false; /** - * Enables or disables view recycler tracing. Any invoker of - * {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first - * check that this value is set to true as not to affect performance. + * @deprecated This flag is now unused */ + @Deprecated public static final boolean TRACE_RECYCLER = false; /** - * Profiles drawing times in the events log. - * - * @hide - */ - public static final boolean DEBUG_PROFILE_DRAWING = false; - - /** - * Profiles layout times in the events log. - * - * @hide - */ - public static final boolean DEBUG_PROFILE_LAYOUT = false; - - /** * Enables detailed logging of drag/drop operations. * @hide */ public static final boolean DEBUG_DRAG = false; /** - * Enables logging of factors that affect the latency and responsiveness of an application. - * - * Logs the relative difference between the time an event was created and the time it - * was delivered. - * - * Logs the time spent waiting for Surface.lockCanvas(), Surface.unlockCanvasAndPost() - * or eglSwapBuffers(). This is time that the event loop spends blocked and unresponsive. - * Ideally, drawing and animations should be perfectly synchronized with VSYNC so that - * dequeuing and queueing buffers is instantaneous. - * - * Logs the time spent in ViewRoot.performTraversals() and ViewRoot.performDraw(). - * @hide - */ - public static final boolean DEBUG_LATENCY = false; - - /** @hide */ - public static final String DEBUG_LATENCY_TAG = "ViewLatency"; - - /** - * Enables detailed logging of accessibility focus operations. - * @hide - */ - public static final boolean DEBUG_ACCESSIBILITY_FOCUS = false; - - /** - * Tag for logging of accessibility focus operations - * @hide - */ - public static final String DEBUG_ACCESSIBILITY_FOCUS_TAG = "AccessibilityFocus"; - - /** - * <p>Enables or disables views consistency check. Even when this property is enabled, - * view consistency checks happen only if {@link false} is set - * to true. The value of this property can be configured externally in one of the - * following files:</p> - * <ul> - * <li>/system/debug.prop</li> - * <li>/debug.prop</li> - * <li>/data/debug.prop</li> - * </ul> - * @hide - */ - @Debug.DebugProperty - public static boolean consistencyCheckEnabled = false; - - /** * This annotation can be used to mark fields and methods to be dumped by * the view server. Only non-void methods with no arguments can be annotated * by this annotation. @@ -373,8 +274,9 @@ public class ViewDebug { private static HashMap<AccessibleObject, ExportedProperty> sAnnotations; /** - * Defines the type of hierarhcy trace to output to the hierarchy traces file. + * @deprecated This enum is now unused */ + @Deprecated public enum HierarchyTraceType { INVALIDATE, INVALIDATE_CHILD, @@ -386,13 +288,10 @@ public class ViewDebug { BUILD_CACHE } - private static BufferedWriter sHierarchyTraces; - private static ViewRootImpl sHierarchyRoot; - private static String sHierarchyTracePrefix; - /** - * Defines the type of recycler trace to output to the recycler traces file. + * @deprecated This enum is now unused */ + @Deprecated public enum RecyclerTraceType { NEW_VIEW, BIND_VIEW, @@ -402,21 +301,6 @@ public class ViewDebug { MOVE_FROM_ACTIVE_TO_SCRAP_HEAP } - private static class RecyclerTrace { - public int view; - public RecyclerTraceType type; - public int position; - public int indexOnScreen; - } - - private static View sRecyclerOwnerView; - private static List<View> sRecyclerViews; - private static List<RecyclerTrace> sRecyclerTraces; - private static String sRecyclerTracePrefix; - - private static final ThreadLocal<LooperProfiler> sLooperProfilerStorage = - new ThreadLocal<LooperProfiler>(); - /** * Returns the number of instanciated Views. * @@ -440,511 +324,50 @@ public class ViewDebug { } /** - * Starts profiling the looper associated with the current thread. - * You must call {@link #stopLooperProfiling} to end profiling - * and obtain the traces. Both methods must be invoked on the - * same thread. - * - * @hide - */ - public static void startLooperProfiling(String path, FileDescriptor fileDescriptor) { - if (sLooperProfilerStorage.get() == null) { - LooperProfiler profiler = new LooperProfiler(path, fileDescriptor); - sLooperProfilerStorage.set(profiler); - Looper.myLooper().setMessageLogging(profiler); - } - } - - /** - * Stops profiling the looper associated with the current thread. - * - * @see #startLooperProfiling(String, java.io.FileDescriptor) - * - * @hide - */ - public static void stopLooperProfiling() { - LooperProfiler profiler = sLooperProfilerStorage.get(); - if (profiler != null) { - sLooperProfilerStorage.remove(); - Looper.myLooper().setMessageLogging(null); - profiler.save(); - } - } - - private static class LooperProfiler implements Looper.Profiler, Printer { - private static final String LOG_TAG = "LooperProfiler"; - - private static final int TRACE_VERSION_NUMBER = 3; - private static final int ACTION_EXIT_METHOD = 0x1; - private static final int HEADER_SIZE = 32; - private static final String HEADER_MAGIC = "SLOW"; - private static final short HEADER_RECORD_SIZE = (short) 14; - - private final long mTraceWallStart; - private final long mTraceThreadStart; - - private final ArrayList<Entry> mTraces = new ArrayList<Entry>(512); - - private final HashMap<String, Integer> mTraceNames = new HashMap<String, Integer>(32); - private int mTraceId = 0; - - private final String mPath; - private ParcelFileDescriptor mFileDescriptor; - - LooperProfiler(String path, FileDescriptor fileDescriptor) { - mPath = path; - try { - mFileDescriptor = ParcelFileDescriptor.dup(fileDescriptor); - } catch (IOException e) { - Log.e(LOG_TAG, "Could not write trace file " + mPath, e); - throw new RuntimeException(e); - } - mTraceWallStart = SystemClock.currentTimeMicro(); - mTraceThreadStart = SystemClock.currentThreadTimeMicro(); - } - - @Override - public void println(String x) { - // Ignore messages - } - - @Override - public void profile(Message message, long wallStart, long wallTime, - long threadStart, long threadTime) { - Entry entry = new Entry(); - entry.traceId = getTraceId(message); - entry.wallStart = wallStart; - entry.wallTime = wallTime; - entry.threadStart = threadStart; - entry.threadTime = threadTime; - - mTraces.add(entry); - } - - private int getTraceId(Message message) { - String name = message.getTarget().getMessageName(message); - Integer traceId = mTraceNames.get(name); - if (traceId == null) { - traceId = mTraceId++ << 4; - mTraceNames.put(name, traceId); - } - return traceId; - } - - void save() { - // Don't block the UI thread - new Thread(new Runnable() { - @Override - public void run() { - saveTraces(); - } - }, "LooperProfiler[" + mPath + "]").start(); - } - - private void saveTraces() { - FileOutputStream fos = new FileOutputStream(mFileDescriptor.getFileDescriptor()); - DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); - - try { - writeHeader(out, mTraceWallStart, mTraceNames, mTraces); - out.flush(); - - writeTraces(fos, out.size(), mTraceWallStart, mTraceThreadStart, mTraces); - - Log.d(LOG_TAG, "Looper traces ready: " + mPath); - } catch (IOException e) { - Log.e(LOG_TAG, "Could not write trace file " + mPath, e); - } finally { - try { - out.close(); - } catch (IOException e) { - Log.e(LOG_TAG, "Could not write trace file " + mPath, e); - } - try { - mFileDescriptor.close(); - } catch (IOException e) { - Log.e(LOG_TAG, "Could not write trace file " + mPath, e); - } - } - } - - private static void writeTraces(FileOutputStream out, long offset, long wallStart, - long threadStart, ArrayList<Entry> entries) throws IOException { - - FileChannel channel = out.getChannel(); - - // Header - ByteBuffer buffer = ByteBuffer.allocateDirect(HEADER_SIZE); - buffer.put(HEADER_MAGIC.getBytes()); - buffer = buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putShort((short) TRACE_VERSION_NUMBER); // version - buffer.putShort((short) HEADER_SIZE); // offset to data - buffer.putLong(wallStart); // start time in usec - buffer.putShort(HEADER_RECORD_SIZE); // size of a record in bytes - // padding to 32 bytes - for (int i = 0; i < HEADER_SIZE - 18; i++) { - buffer.put((byte) 0); - } - - buffer.flip(); - channel.position(offset).write(buffer); - - buffer = ByteBuffer.allocateDirect(14).order(ByteOrder.LITTLE_ENDIAN); - for (Entry entry : entries) { - buffer.putShort((short) 1); // we simulate only one thread - buffer.putInt(entry.traceId); // entering method - buffer.putInt((int) (entry.threadStart - threadStart)); - buffer.putInt((int) (entry.wallStart - wallStart)); - - buffer.flip(); - channel.write(buffer); - buffer.clear(); - - buffer.putShort((short) 1); - buffer.putInt(entry.traceId | ACTION_EXIT_METHOD); // exiting method - buffer.putInt((int) (entry.threadStart + entry.threadTime - threadStart)); - buffer.putInt((int) (entry.wallStart + entry.wallTime - wallStart)); - - buffer.flip(); - channel.write(buffer); - buffer.clear(); - } - - channel.close(); - } - - private static void writeHeader(DataOutputStream out, long start, - HashMap<String, Integer> names, ArrayList<Entry> entries) throws IOException { - - Entry last = entries.get(entries.size() - 1); - long wallTotal = (last.wallStart + last.wallTime) - start; - - startSection("version", out); - addValue(null, Integer.toString(TRACE_VERSION_NUMBER), out); - addValue("data-file-overflow", "false", out); - addValue("clock", "dual", out); - addValue("elapsed-time-usec", Long.toString(wallTotal), out); - addValue("num-method-calls", Integer.toString(entries.size()), out); - addValue("clock-call-overhead-nsec", "1", out); - addValue("vm", "dalvik", out); - - startSection("threads", out); - addThreadId(1, "main", out); - - startSection("methods", out); - addMethods(names, out); - - startSection("end", out); - } - - private static void addMethods(HashMap<String, Integer> names, DataOutputStream out) - throws IOException { - - for (Map.Entry<String, Integer> name : names.entrySet()) { - out.writeBytes(String.format("0x%08x\tEventQueue\t%s\t()V\tLooper\t-2\n", - name.getValue(), name.getKey())); - } - } - - private static void addThreadId(int id, String name, DataOutputStream out) - throws IOException { - - out.writeBytes(Integer.toString(id) + '\t' + name + '\n'); - } - - private static void addValue(String name, String value, DataOutputStream out) - throws IOException { - - if (name != null) { - out.writeBytes(name + "="); - } - out.writeBytes(value + '\n'); - } - - private static void startSection(String name, DataOutputStream out) throws IOException { - out.writeBytes("*" + name + '\n'); - } - - static class Entry { - int traceId; - long wallStart; - long wallTime; - long threadStart; - long threadTime; - } - } - - /** - * Outputs a trace to the currently opened recycler traces. The trace records the type of - * recycler action performed on the supplied view as well as a number of parameters. - * - * @param view the view to trace - * @param type the type of the trace - * @param parameters parameters depending on the type of the trace + * @deprecated This method is now unused and invoking it is a no-op */ + @Deprecated + @SuppressWarnings({ "UnusedParameters", "deprecation" }) public static void trace(View view, RecyclerTraceType type, int... parameters) { - if (sRecyclerOwnerView == null || sRecyclerViews == null) { - return; - } - - if (!sRecyclerViews.contains(view)) { - sRecyclerViews.add(view); - } - - final int index = sRecyclerViews.indexOf(view); - - RecyclerTrace trace = new RecyclerTrace(); - trace.view = index; - trace.type = type; - trace.position = parameters[0]; - trace.indexOnScreen = parameters[1]; - - sRecyclerTraces.add(trace); } /** - * Starts tracing the view recycler of the specified view. The trace is identified by a prefix, - * used to build the traces files names: <code>/EXTERNAL/view-recycler/PREFIX.traces</code> and - * <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>. - * - * Only one view recycler can be traced at the same time. After calling this method, any - * other invocation will result in a <code>IllegalStateException</code> unless - * {@link #stopRecyclerTracing()} is invoked before. - * - * Traces files are created only after {@link #stopRecyclerTracing()} is invoked. - * - * This method will return immediately if TRACE_RECYCLER is false. - * - * @param prefix the traces files name prefix - * @param view the view whose recycler must be traced - * - * @see #stopRecyclerTracing() - * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[]) + * @deprecated This method is now unused and invoking it is a no-op */ + @Deprecated + @SuppressWarnings("UnusedParameters") public static void startRecyclerTracing(String prefix, View view) { - //noinspection PointlessBooleanExpression,ConstantConditions - if (!TRACE_RECYCLER) { - return; - } - - if (sRecyclerOwnerView != null) { - throw new IllegalStateException("You must call stopRecyclerTracing() before running" + - " a new trace!"); - } - - sRecyclerTracePrefix = prefix; - sRecyclerOwnerView = view; - sRecyclerViews = new ArrayList<View>(); - sRecyclerTraces = new LinkedList<RecyclerTrace>(); } /** - * Stops the current view recycer tracing. - * - * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.traces</code> - * containing all the traces (or method calls) relative to the specified view's recycler. - * - * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.recycler</code> - * containing all of the views used by the recycler of the view supplied to - * {@link #startRecyclerTracing(String, View)}. - * - * This method will return immediately if TRACE_RECYCLER is false. - * - * @see #startRecyclerTracing(String, View) - * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[]) + * @deprecated This method is now unused and invoking it is a no-op */ + @Deprecated + @SuppressWarnings("UnusedParameters") public static void stopRecyclerTracing() { - //noinspection PointlessBooleanExpression,ConstantConditions - if (!TRACE_RECYCLER) { - return; - } - - if (sRecyclerOwnerView == null || sRecyclerViews == null) { - throw new IllegalStateException("You must call startRecyclerTracing() before" + - " stopRecyclerTracing()!"); - } - - File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/"); - //noinspection ResultOfMethodCallIgnored - recyclerDump.mkdirs(); - - recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler"); - try { - final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024); - - for (View view : sRecyclerViews) { - final String name = view.getClass().getName(); - out.write(name); - out.newLine(); - } - - out.close(); - } catch (IOException e) { - Log.e("View", "Could not dump recycler content"); - return; - } - - recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/"); - recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces"); - try { - if (recyclerDump.exists()) { - //noinspection ResultOfMethodCallIgnored - recyclerDump.delete(); - } - final FileOutputStream file = new FileOutputStream(recyclerDump); - final DataOutputStream out = new DataOutputStream(file); - - for (RecyclerTrace trace : sRecyclerTraces) { - out.writeInt(trace.view); - out.writeInt(trace.type.ordinal()); - out.writeInt(trace.position); - out.writeInt(trace.indexOnScreen); - out.flush(); - } - - out.close(); - } catch (IOException e) { - Log.e("View", "Could not dump recycler traces"); - return; - } - - sRecyclerViews.clear(); - sRecyclerViews = null; - - sRecyclerTraces.clear(); - sRecyclerTraces = null; - - sRecyclerOwnerView = null; } /** - * Outputs a trace to the currently opened traces file. The trace contains the class name - * and instance's hashcode of the specified view as well as the supplied trace type. - * - * @param view the view to trace - * @param type the type of the trace + * @deprecated This method is now unused and invoking it is a no-op */ + @Deprecated + @SuppressWarnings({ "UnusedParameters", "deprecation" }) public static void trace(View view, HierarchyTraceType type) { - if (sHierarchyTraces == null) { - return; - } - - try { - sHierarchyTraces.write(type.name()); - sHierarchyTraces.write(' '); - sHierarchyTraces.write(view.getClass().getName()); - sHierarchyTraces.write('@'); - sHierarchyTraces.write(Integer.toHexString(view.hashCode())); - sHierarchyTraces.newLine(); - } catch (IOException e) { - Log.w("View", "Error while dumping trace of type " + type + " for view " + view); - } } /** - * Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix, - * used to build the traces files names: <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and - * <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>. - * - * Only one view hierarchy can be traced at the same time. After calling this method, any - * other invocation will result in a <code>IllegalStateException</code> unless - * {@link #stopHierarchyTracing()} is invoked before. - * - * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> - * containing all the traces (or method calls) relative to the specified view's hierarchy. - * - * This method will return immediately if TRACE_HIERARCHY is false. - * - * @param prefix the traces files name prefix - * @param view the view whose hierarchy must be traced - * - * @see #stopHierarchyTracing() - * @see #trace(View, android.view.ViewDebug.HierarchyTraceType) + * @deprecated This method is now unused and invoking it is a no-op */ + @Deprecated + @SuppressWarnings("UnusedParameters") public static void startHierarchyTracing(String prefix, View view) { - //noinspection PointlessBooleanExpression,ConstantConditions - if (!TRACE_HIERARCHY) { - return; - } - - if (sHierarchyRoot != null) { - throw new IllegalStateException("You must call stopHierarchyTracing() before running" + - " a new trace!"); - } - - File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/"); - //noinspection ResultOfMethodCallIgnored - hierarchyDump.mkdirs(); - - hierarchyDump = new File(hierarchyDump, prefix + ".traces"); - sHierarchyTracePrefix = prefix; - - try { - sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024); - } catch (IOException e) { - Log.e("View", "Could not dump view hierarchy"); - return; - } - - sHierarchyRoot = view.getViewRootImpl(); } /** - * Stops the current view hierarchy tracing. This method closes the file - * <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>. - * - * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code> - * containing the view hierarchy of the view supplied to - * {@link #startHierarchyTracing(String, View)}. - * - * This method will return immediately if TRACE_HIERARCHY is false. - * - * @see #startHierarchyTracing(String, View) - * @see #trace(View, android.view.ViewDebug.HierarchyTraceType) + * @deprecated This method is now unused and invoking it is a no-op */ + @Deprecated public static void stopHierarchyTracing() { - //noinspection PointlessBooleanExpression,ConstantConditions - if (!TRACE_HIERARCHY) { - return; - } - - if (sHierarchyRoot == null || sHierarchyTraces == null) { - throw new IllegalStateException("You must call startHierarchyTracing() before" + - " stopHierarchyTracing()!"); - } - - try { - sHierarchyTraces.close(); - } catch (IOException e) { - Log.e("View", "Could not write view traces"); - } - sHierarchyTraces = null; - - File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/"); - //noinspection ResultOfMethodCallIgnored - hierarchyDump.mkdirs(); - hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree"); - - BufferedWriter out; - try { - out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024); - } catch (IOException e) { - Log.e("View", "Could not dump view hierarchy"); - return; - } - - View view = sHierarchyRoot.getView(); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - dumpViewHierarchy(group, out, 0); - try { - out.close(); - } catch (IOException e) { - Log.e("View", "Could not dump view hierarchy"); - } - } - - sHierarchyRoot = null; } static void dispatchCommand(View view, String command, String parameters, @@ -1725,38 +1148,6 @@ public class ViewDebug { } } - private static void dumpViewHierarchy(ViewGroup group, BufferedWriter out, int level) { - if (!dumpView(group, out, level)) { - return; - } - - final int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - final View view = group.getChildAt(i); - if (view instanceof ViewGroup) { - dumpViewHierarchy((ViewGroup) view, out, level + 1); - } else { - dumpView(view, out, level + 1); - } - } - } - - private static boolean dumpView(Object view, BufferedWriter out, int level) { - try { - for (int i = 0; i < level; i++) { - out.write(' '); - } - out.write(view.getClass().getName()); - out.write('@'); - out.write(Integer.toHexString(view.hashCode())); - out.newLine(); - } catch (IOException e) { - Log.w("View", "Error while dumping hierarchy tree"); - return false; - } - return true; - } - private static Field[] capturedViewGetPropertyFields(Class<?> klass) { if (mCapturedViewFieldsForClasses == null) { mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>(); @@ -1863,7 +1254,6 @@ public class ViewDebug { } private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) { - if (obj == null) { return "null"; } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index b95ca5e..a243c73 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3154,8 +3154,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Adds a child view. If no layout parameters are already set on the child, the - * default parameters for this ViewGroup are set on the child. + * <p>Adds a child view. If no layout parameters are already set on the child, the + * default parameters for this ViewGroup are set on the child.</p> + * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> * * @param child the child view to add * @@ -3168,6 +3172,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Adds a child view. If no layout parameters are already set on the child, the * default parameters for this ViewGroup are set on the child. + * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> * * @param child the child view to add * @param index the position at which to add the child @@ -3189,6 +3197,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * Adds a child view with this ViewGroup's default layout parameters and the * specified width and height. * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> + * * @param child the child view to add */ public void addView(View child, int width, int height) { @@ -3201,6 +3213,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Adds a child view with the specified layout parameters. * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> + * * @param child the child view to add * @param params the layout parameters to set on the child */ @@ -3211,6 +3227,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Adds a child view with the specified layout parameters. * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> + * * @param child the child view to add * @param index the position at which to add the child * @param params the layout parameters to set on the child @@ -3528,6 +3548,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * {@inheritDoc} + * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> */ public void removeView(View view) { removeViewInternal(view); @@ -3539,6 +3563,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * Removes a view during layout. This is useful if in your onLayout() method, * you need to remove more views. * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> + * * @param view the view to remove from the group */ public void removeViewInLayout(View view) { @@ -3549,6 +3577,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * Removes a range of views during layout. This is useful if in your onLayout() method, * you need to remove more views. * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> + * * @param start the index of the first view to remove from the group * @param count the number of views to remove from the group */ @@ -3559,6 +3591,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Removes the view at the specified position in the group. * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> + * * @param index the position in the group of the view to remove */ public void removeViewAt(int index) { @@ -3570,6 +3606,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Removes the specified range of views from the group. * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> + * * @param start the first position in the group of the range of views to remove * @param count the number of views to remove */ @@ -3715,6 +3755,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Call this method to remove all child views from the * ViewGroup. + * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> */ public void removeAllViews() { removeAllViewsInLayout(); @@ -3730,6 +3774,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * that can currently fit inside the object on screen. Do not call * this method unless you are extending ViewGroup and understand the * view measuring and layout pipeline. + * + * <p><strong>Note:</strong> do not invoke this method from + * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)}, + * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> */ public void removeAllViewsInLayout() { final int count = mChildrenCount; @@ -3949,10 +3997,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * the view hierarchy. */ public final void invalidateChild(View child, final Rect dirty) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD); - } - ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; @@ -4045,10 +4089,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * does not intersect with this ViewGroup's bounds. */ public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { - if (ViewDebug.TRACE_HIERARCHY) { - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD_IN_PARENT); - } - if ((mPrivateFlags & DRAWN) == DRAWN || (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) { if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != @@ -4631,61 +4671,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * @hide - */ - @Override - protected boolean dispatchConsistencyCheck(int consistency) { - boolean result = super.dispatchConsistencyCheck(consistency); - - final int count = mChildrenCount; - final View[] children = mChildren; - for (int i = 0; i < count; i++) { - if (!children[i].dispatchConsistencyCheck(consistency)) result = false; - } - - return result; - } - - /** - * @hide - */ - @Override - protected boolean onConsistencyCheck(int consistency) { - boolean result = super.onConsistencyCheck(consistency); - - final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0; - final boolean checkDrawing = (consistency & ViewDebug.CONSISTENCY_DRAWING) != 0; - - if (checkLayout) { - final int count = mChildrenCount; - final View[] children = mChildren; - for (int i = 0; i < count; i++) { - if (children[i].getParent() != this) { - result = false; - android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, - "View " + children[i] + " has no parent/a parent that is not " + this); - } - } - } - - if (checkDrawing) { - // If this group is dirty, check that the parent is dirty as well - if ((mPrivateFlags & DIRTY_MASK) != 0) { - final ViewParent parent = getParent(); - if (parent != null && !(parent instanceof ViewRootImpl)) { - if ((((View) parent).mPrivateFlags & DIRTY_MASK) == 0) { - result = false; - android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, - "ViewGroup " + this + " is dirty but its parent is not: " + this); - } - } - } - } - - return result; - } - - /** * {@inheritDoc} */ @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index bcd336d..551b6cc 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -55,7 +55,6 @@ import android.os.SystemProperties; import android.os.Trace; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; -import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.TypedValue; @@ -218,8 +217,6 @@ public final class ViewRootImpl implements ViewParent, boolean mTraversalScheduled; int mTraversalBarrier; - long mLastTraversalFinishedTimeNanos; - long mLastDrawFinishedTimeNanos; boolean mWillDrawSoon; boolean mFitSystemWindowsRequested; boolean mLayoutRequested; @@ -987,18 +984,6 @@ public final class ViewRootImpl implements ViewParent, Debug.startMethodTracing("ViewAncestor"); } - final long traversalStartTime; - if (ViewDebug.DEBUG_LATENCY) { - traversalStartTime = System.nanoTime(); - if (mLastTraversalFinishedTimeNanos != 0) { - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals(); it has been " - + ((traversalStartTime - mLastTraversalFinishedTimeNanos) * 0.000001f) - + "ms since the last traversals finished."); - } else { - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals()."); - } - } - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals"); try { performTraversals(); @@ -1006,14 +991,6 @@ public final class ViewRootImpl implements ViewParent, Trace.traceEnd(Trace.TRACE_TAG_VIEW); } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "performTraversals() took " - + ((now - traversalStartTime) * 0.000001f) - + "ms."); - mLastTraversalFinishedTimeNanos = now; - } - if (mProfile) { Debug.stopMethodTracing(); mProfile = false; @@ -1076,7 +1053,7 @@ public final class ViewRootImpl implements ViewParent, if (baseSize != 0 && desiredWindowWidth > baseSize) { childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); - host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { @@ -1087,7 +1064,7 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize=" + baseSize); childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); - host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { @@ -1101,7 +1078,7 @@ public final class ViewRootImpl implements ViewParent, if (!goodMeasure) { childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); - host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { windowSizeMayChange = true; } @@ -1650,7 +1627,7 @@ public final class ViewRootImpl implements ViewParent, + " coveredInsetsChanged=" + contentInsetsChanged); // Ask host how big it wants to be - host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // Implementation of weights from WindowManager.LayoutParams // We just grow the dimensions as needed and re-measure if @@ -1676,7 +1653,7 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_LAYOUT) Log.v(TAG, "And hey let's measure once more: width=" + width + " height=" + height); - host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } layoutRequested = true; @@ -1688,28 +1665,7 @@ public final class ViewRootImpl implements ViewParent, boolean triggerGlobalLayoutListener = didLayout || attachInfo.mRecomputeGlobalAttributes; if (didLayout) { - mLayoutRequested = false; - mScrollMayChange = true; - if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v( - TAG, "Laying out " + host + " to (" + - host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); - long startTime = 0L; - if (ViewDebug.DEBUG_PROFILE_LAYOUT) { - startTime = SystemClock.elapsedRealtime(); - } - host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); - - if (false && ViewDebug.consistencyCheckEnabled) { - if (!host.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_LAYOUT)) { - throw new IllegalStateException("The view hierarchy is an inconsistent state," - + "please refer to the logs with the tag " - + ViewDebug.CONSISTENCY_LOG_TAG + " for more infomation."); - } - } - - if (ViewDebug.DEBUG_PROFILE_LAYOUT) { - EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime); - } + performLayout(); // By this point all views have been sized and positionned // We can compute the transparent area @@ -1867,6 +1823,33 @@ public final class ViewRootImpl implements ViewParent, } } + private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); + try { + mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + + private void performLayout() { + mLayoutRequested = false; + mScrollMayChange = true; + + final View host = mView; + if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { + Log.v(TAG, "Laying out " + host + " to (" + + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); + } + + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); + try { + host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + public void requestTransparentRegion(View child) { // the test below should not fail unless someone is messing with us checkThread(); @@ -2008,18 +1991,6 @@ public final class ViewRootImpl implements ViewParent, return; } - final long drawStartTime; - if (ViewDebug.DEBUG_LATENCY) { - drawStartTime = System.nanoTime(); - if (mLastDrawFinishedTimeNanos != 0) { - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting draw(); it has been " - + ((drawStartTime - mLastDrawFinishedTimeNanos) * 0.000001f) - + "ms since the last draw finished."); - } else { - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting draw()."); - } - } - final boolean fullRedrawNeeded = mFullRedrawNeeded; mFullRedrawNeeded = false; @@ -2032,14 +2003,6 @@ public final class ViewRootImpl implements ViewParent, Trace.traceEnd(Trace.TRACE_TAG_VIEW); } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "performDraw() took " - + ((now - drawStartTime) * 0.000001f) - + "ms."); - mLastDrawFinishedTimeNanos = now; - } - if (mReportNextDraw) { mReportNextDraw = false; @@ -2203,19 +2166,8 @@ public final class ViewRootImpl implements ViewParent, int right = dirty.right; int bottom = dirty.bottom; - final long lockCanvasStartTime; - if (ViewDebug.DEBUG_LATENCY) { - lockCanvasStartTime = System.nanoTime(); - } - canvas = mSurface.lockCanvas(dirty); - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- lockCanvas() took " - + ((now - lockCanvasStartTime) * 0.000001f) + "ms"); - } - if (left != dirty.left || top != dirty.top || right != dirty.right || bottom != dirty.bottom) { attachInfo.mIgnoreDirtyState = true; @@ -2235,7 +2187,7 @@ public final class ViewRootImpl implements ViewParent, mLayoutRequested = true; // ask wm for a new surface next time. return false; } catch (IllegalArgumentException e) { - Log.e(TAG, "IllegalArgumentException locking surface", e); + Log.e(TAG, "Could not lock surface", e); // Don't assume this is due to out of memory, it could be // something else, and if it is something else then we could // kill stuff (or ourself) for no reason. @@ -2250,11 +2202,6 @@ public final class ViewRootImpl implements ViewParent, //canvas.drawARGB(255, 255, 0, 0); } - long startTime = 0L; - if (ViewDebug.DEBUG_PROFILE_DRAWING) { - startTime = SystemClock.elapsedRealtime(); - } - // If this bitmap's format includes an alpha channel, we // need to clear it before drawing so that the child will // properly re-composite its drawing on a transparent @@ -2287,46 +2234,23 @@ public final class ViewRootImpl implements ViewParent, ? DisplayMetrics.DENSITY_DEVICE : 0); attachInfo.mSetIgnoreDirtyState = false; - final long drawStartTime; - if (ViewDebug.DEBUG_LATENCY) { - drawStartTime = System.nanoTime(); - } - mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); - - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took " - + ((now - drawStartTime) * 0.000001f) + "ms"); - } } finally { if (!attachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call attachInfo.mIgnoreDirtyState = false; } } - - if (false && ViewDebug.consistencyCheckEnabled) { - mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); - } - - if (ViewDebug.DEBUG_PROFILE_DRAWING) { - EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); - } } finally { - final long unlockCanvasAndPostStartTime; - if (ViewDebug.DEBUG_LATENCY) { - unlockCanvasAndPostStartTime = System.nanoTime(); - } - - surface.unlockCanvasAndPost(canvas); - - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took " - + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms"); + try { + surface.unlockCanvasAndPost(canvas); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Could not unlock surface", e); + mLayoutRequested = true; // ask wm for a new surface next time. + //noinspection ReturnInsideFinallyBlock + return false; } if (LOCAL_LOGV) { @@ -2411,7 +2335,9 @@ public final class ViewRootImpl implements ViewParent, final int count = displayLists.size(); for (int i = 0; i < count; i++) { - displayLists.get(i).invalidate(); + final DisplayList displayList = displayLists.get(i); + displayList.invalidate(); + displayList.clear(); } displayLists.clear(); @@ -2971,20 +2897,6 @@ public final class ViewRootImpl implements ViewParent, if (hasWindowFocus) { mView.sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - // Give accessibility focus to the view that has input - // focus if such, otherwise to the first one. - if (mView instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) mView; - View focused = viewGroup.findFocus(); - if (focused != null) { - focused.requestAccessibilityFocus(); - } - } - // There is no accessibility focus, despite our effort - // above, now just give it to the first view. - if (mAccessibilityFocusedHost == null) { - mView.requestAccessibilityFocus(); - } } } } @@ -3187,10 +3099,6 @@ public final class ViewRootImpl implements ViewParent, } private void deliverInputEvent(QueuedInputEvent q) { - if (ViewDebug.DEBUG_LATENCY) { - q.mDeliverTimeNanos = System.nanoTime(); - } - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent"); try { if (q.mEvent instanceof KeyEvent) { @@ -3638,9 +3546,6 @@ public final class ViewRootImpl implements ViewParent, private void deliverKeyEventPostIme(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; - if (ViewDebug.DEBUG_LATENCY) { - q.mDeliverPostImeTimeNanos = System.nanoTime(); - } // If the view went away, then the event will not be handled. if (mView == null || !mAdded) { @@ -4163,11 +4068,6 @@ public final class ViewRootImpl implements ViewParent, public InputEvent mEvent; public InputEventReceiver mReceiver; public int mFlags; - - // Used for latency calculations. - public long mReceiveTimeNanos; - public long mDeliverTimeNanos; - public long mDeliverPostImeTimeNanos; } private QueuedInputEvent obtainQueuedInputEvent(InputEvent event, @@ -4206,12 +4106,6 @@ public final class ViewRootImpl implements ViewParent, InputEventReceiver receiver, int flags, boolean processImmediately) { QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); - if (ViewDebug.DEBUG_LATENCY) { - q.mReceiveTimeNanos = System.nanoTime(); - q.mDeliverTimeNanos = 0; - q.mDeliverPostImeTimeNanos = 0; - } - // Always enqueue the input event in order, regardless of its time stamp. // We do this because the application or the IME may inject key events // in response to touch events and we want to ensure that the injected keys @@ -4265,42 +4159,6 @@ public final class ViewRootImpl implements ViewParent, throw new IllegalStateException("finished input event out of order"); } - if (ViewDebug.DEBUG_LATENCY) { - final long now = System.nanoTime(); - final long eventTime = q.mEvent.getEventTimeNano(); - final StringBuilder msg = new StringBuilder(); - msg.append("Spent "); - msg.append((now - q.mReceiveTimeNanos) * 0.000001f); - msg.append("ms processing "); - if (q.mEvent instanceof KeyEvent) { - final KeyEvent keyEvent = (KeyEvent)q.mEvent; - msg.append("key event, action="); - msg.append(KeyEvent.actionToString(keyEvent.getAction())); - } else { - final MotionEvent motionEvent = (MotionEvent)q.mEvent; - msg.append("motion event, action="); - msg.append(MotionEvent.actionToString(motionEvent.getAction())); - msg.append(", historySize="); - msg.append(motionEvent.getHistorySize()); - } - msg.append(", handled="); - msg.append(handled); - msg.append(", received at +"); - msg.append((q.mReceiveTimeNanos - eventTime) * 0.000001f); - if (q.mDeliverTimeNanos != 0) { - msg.append("ms, delivered at +"); - msg.append((q.mDeliverTimeNanos - eventTime) * 0.000001f); - } - if (q.mDeliverPostImeTimeNanos != 0) { - msg.append("ms, delivered post IME at +"); - msg.append((q.mDeliverPostImeTimeNanos - eventTime) * 0.000001f); - } - msg.append("ms, finished at +"); - msg.append((now - eventTime) * 0.000001f); - msg.append("ms."); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, msg.toString()); - } - if (q.mReceiver != null) { q.mReceiver.finishInputEvent(q.mEvent, handled); } else { @@ -4447,6 +4305,7 @@ public final class ViewRootImpl implements ViewParent, for (int i = 0; i < viewCount; i++) { mTempViews[i].invalidate(); + mTempViews[i] = null; } for (int i = 0; i < viewRectCount; i++) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index d62f513..d94275b 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -691,13 +691,6 @@ public interface WindowManager extends ViewManager { */ public static final int FLAG_NEEDS_MENU_KEY = 0x08000000; - /** Window flag: *sigh* The lock screen wants to continue running its - * animation while it is fading. A kind-of hack to allow this. Maybe - * in the future we just make this the default behavior. - * - * {@hide} */ - public static final int FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000; - /** Window flag: special flag to limit the size of the window to be * original size ([320x480] x density). Used to create window for applications * running under compatibility mode. diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 0c5d6ea..ceb9fe6 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -386,6 +386,12 @@ public interface WindowManagerPolicy { */ public InputChannel monitorInput(String name); + /** + * Switch the keyboard layout for the given device. + * Direction should be +1 or -1 to go to the next or previous keyboard layout. + */ + public void switchKeyboardLayout(int deviceId, int direction); + public void shutdown(); public void rebootSafeMode(); } diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index d92ebcd..85d77cb 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -19,6 +19,7 @@ package android.view.animation; import android.content.Context; import android.content.res.TypedArray; import android.graphics.RectF; +import android.os.Handler; import android.os.SystemProperties; import android.util.AttributeSet; import android.util.TypedValue; @@ -207,6 +208,11 @@ public abstract class Animation implements Cloneable { private final CloseGuard guard = CloseGuard.get(); + private Handler mListenerHandler; + private Runnable mOnStart; + private Runnable mOnRepeat; + private Runnable mOnEnd; + /** * Creates a new animation with a duration of 0ms, the default interpolator, with * fillBefore set to true and fillAfter set to false @@ -275,6 +281,7 @@ public abstract class Animation implements Cloneable { mRepeated = 0; mMore = true; mOneMoreTime = true; + mListenerHandler = null; } /** @@ -290,7 +297,7 @@ public abstract class Animation implements Cloneable { */ public void cancel() { if (mStarted && !mEnded) { - if (mListener != null) mListener.onAnimationEnd(this); + fireAnimationEnd(); mEnded = true; guard.close(); } @@ -306,7 +313,7 @@ public abstract class Animation implements Cloneable { if (mStarted && !mEnded) { mEnded = true; guard.close(); - if (mListener != null) mListener.onAnimationEnd(this); + fireAnimationEnd(); } } @@ -341,6 +348,38 @@ public abstract class Animation implements Cloneable { } /** + * Sets the handler used to invoke listeners. + * + * @hide + */ + public void setListenerHandler(Handler handler) { + if (mListenerHandler == null) { + mOnStart = new Runnable() { + public void run() { + if (mListener != null) { + mListener.onAnimationStart(Animation.this); + } + } + }; + mOnRepeat = new Runnable() { + public void run() { + if (mListener != null) { + mListener.onAnimationRepeat(Animation.this); + } + } + }; + mOnEnd = new Runnable() { + public void run() { + if (mListener != null) { + mListener.onAnimationEnd(Animation.this); + } + } + }; + } + mListenerHandler = handler; + } + + /** * Sets the acceleration curve for this animation. The interpolator is loaded as * a resource from the specified context. * @@ -792,7 +831,6 @@ public abstract class Animation implements Cloneable { * @return True if the animation is still running */ public boolean getTransformation(long currentTime, Transformation outTransformation) { - if (mStartTime == -1) { mStartTime = currentTime; } @@ -815,9 +853,7 @@ public abstract class Animation implements Cloneable { if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { if (!mStarted) { - if (mListener != null) { - mListener.onAnimationStart(this); - } + fireAnimationStart(); mStarted = true; if (USE_CLOSEGUARD) { guard.open("cancel or detach or getTransformation"); @@ -839,9 +875,7 @@ public abstract class Animation implements Cloneable { if (!mEnded) { mEnded = true; guard.close(); - if (mListener != null) { - mListener.onAnimationEnd(this); - } + fireAnimationEnd(); } } else { if (mRepeatCount > 0) { @@ -855,9 +889,7 @@ public abstract class Animation implements Cloneable { mStartTime = -1; mMore = true; - if (mListener != null) { - mListener.onAnimationRepeat(this); - } + fireAnimationRepeat(); } } @@ -868,7 +900,28 @@ public abstract class Animation implements Cloneable { return mMore; } - + + private void fireAnimationStart() { + if (mListener != null) { + if (mListenerHandler == null) mListener.onAnimationStart(this); + else mListenerHandler.postAtFrontOfQueue(mOnStart); + } + } + + private void fireAnimationRepeat() { + if (mListener != null) { + if (mListenerHandler == null) mListener.onAnimationRepeat(this); + else mListenerHandler.postAtFrontOfQueue(mOnRepeat); + } + } + + private void fireAnimationEnd() { + if (mListener != null) { + if (mListenerHandler == null) mListener.onAnimationEnd(this); + else mListenerHandler.postAtFrontOfQueue(mOnEnd); + } + } + /** * Gets the transformation to apply at a specified point in time. Implementations of this * method should always replace the specified Transformation or document they are doing diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 1803352..d2cc2d8 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -283,6 +283,7 @@ public final class InputMethodManager { * The InputConnection that was last retrieved from the served view. */ InputConnection mServedInputConnection; + ControlledInputConnectionWrapper mServedInputConnectionWrapper; /** * The completions that were last provided by the served view. */ @@ -418,16 +419,22 @@ public final class InputMethodManager { private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper { private final InputMethodManager mParentInputMethodManager; + private boolean mActive; public ControlledInputConnectionWrapper(final Looper mainLooper, final InputConnection conn, final InputMethodManager inputMethodManager) { super(mainLooper, conn); mParentInputMethodManager = inputMethodManager; + mActive = true; } @Override public boolean isActive() { - return mParentInputMethodManager.mActive; + return mParentInputMethodManager.mActive && mActive; + } + + void deactivate() { + mActive = false; } } @@ -666,6 +673,10 @@ public final class InputMethodManager { void clearConnectionLocked() { mCurrentTextBoxAttribute = null; mServedInputConnection = null; + if (mServedInputConnectionWrapper != null) { + mServedInputConnectionWrapper.deactivate(); + mServedInputConnectionWrapper = null; + } } /** @@ -1060,7 +1071,7 @@ public final class InputMethodManager { // Notify the served view that its previous input connection is finished notifyInputConnectionFinished(); mServedInputConnection = ic; - IInputContext servedContext; + ControlledInputConnectionWrapper servedContext; if (ic != null) { mCursorSelStart = tba.initialSelStart; mCursorSelEnd = tba.initialSelEnd; @@ -1071,6 +1082,10 @@ public final class InputMethodManager { } else { servedContext = null; } + if (mServedInputConnectionWrapper != null) { + mServedInputConnectionWrapper.deactivate(); + } + mServedInputConnectionWrapper = servedContext; try { if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic=" @@ -1286,6 +1301,7 @@ public final class InputMethodManager { // we'll just do a window focus gain and call it a day. synchronized (mH) { try { + if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput"); mService.windowGainedFocus(mClient, rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null, null); } catch (RemoteException e) { diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java index f235295..c753188 100644 --- a/core/java/android/view/textservice/SpellCheckerSubtype.java +++ b/core/java/android/view/textservice/SpellCheckerSubtype.java @@ -146,7 +146,10 @@ public final class SpellCheckerSubtype implements Parcelable { return false; } - private static Locale constructLocaleFromString(String localeStr) { + /** + * @hide + */ + public static Locale constructLocaleFromString(String localeStr) { if (TextUtils.isEmpty(localeStr)) return null; String[] localeParams = localeStr.split("_", 3); diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java index 11bd815..cc490bd 100644 --- a/core/java/android/webkit/AccessibilityInjector.java +++ b/core/java/android/webkit/AccessibilityInjector.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,484 +16,615 @@ package android.webkit; +import android.content.Context; +import android.os.Bundle; +import android.os.SystemClock; import android.provider.Settings; -import android.text.TextUtils; -import android.text.TextUtils.SimpleStringSplitter; -import android.util.Log; +import android.speech.tts.TextToSpeech; import android.view.KeyEvent; -import android.view.accessibility.AccessibilityEvent; +import android.view.View; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.webkit.WebViewCore.EventHub; -import java.util.ArrayList; -import java.util.Stack; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; /** - * This class injects accessibility into WebViews with disabled JavaScript or - * WebViews with enabled JavaScript but for which we have no accessibility - * script to inject. - * </p> - * Note: To avoid changes in the framework upon changing the available - * navigation axis, or reordering the navigation axis, or changing - * the key bindings, or defining sequence of actions to be bound to - * a given key this class is navigation axis agnostic. It is only - * aware of one navigation axis which is in fact the default behavior - * of webViews while using the DPAD/TrackBall. - * </p> - * In general a key binding is a mapping from modifiers + key code to - * a sequence of actions. For more detail how to specify key bindings refer to - * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}. - * </p> - * The possible actions are invocations to - * {@link #setCurrentAxis(int, boolean, String)}, or - * {@link #traverseCurrentAxis(int, boolean, String)} - * {@link #traverseGivenAxis(int, int, boolean, String)} - * {@link #prefromAxisTransition(int, int, boolean, String)} - * referred via the values of: - * {@link #ACTION_SET_CURRENT_AXIS}, - * {@link #ACTION_TRAVERSE_CURRENT_AXIS}, - * {@link #ACTION_TRAVERSE_GIVEN_AXIS}, - * {@link #ACTION_PERFORM_AXIS_TRANSITION}, - * respectively. - * The arguments for the action invocation are specified as offset - * hexademical pairs. Note the last argument of the invocation - * should NOT be specified in the binding as it is provided by - * this class. For details about the key binding implementation - * refer to {@link AccessibilityWebContentKeyBinding}. + * Handles injecting accessibility JavaScript and related JavaScript -> Java + * APIs. */ class AccessibilityInjector { - private static final String LOG_TAG = "AccessibilityInjector"; + // Default result returned from AndroidVox. Using true here means if the + // script fails, an accessibility service will always think that traversal + // has succeeded. + private static final String DEFAULT_ANDROIDVOX_RESULT = "true"; + + // The WebViewClassic this injector is responsible for managing. + private final WebViewClassic mWebViewClassic; - private static final boolean DEBUG = true; + // Cached reference to mWebViewClassic.getContext(), for convenience. + private final Context mContext; - private static final int ACTION_SET_CURRENT_AXIS = 0; - private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1; - private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2; - private static final int ACTION_PERFORM_AXIS_TRANSITION = 3; - private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4; + // Cached reference to mWebViewClassic.getWebView(), for convenience. + private final WebView mWebView; - // the default WebView behavior abstracted as a navigation axis - private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7; + // The Java objects that are exposed to JavaScript. + private TextToSpeech mTextToSpeech; + private CallbackHandler mCallback; - // these are the same for all instances so make them process wide - private static ArrayList<AccessibilityWebContentKeyBinding> sBindings = - new ArrayList<AccessibilityWebContentKeyBinding>(); + // Lazily loaded helper objects. + private AccessibilityManager mAccessibilityManager; + private AccessibilityInjectorFallback mAccessibilityInjectorFallback; + private JSONObject mAccessibilityJSONObject; - // handle to the WebViewClassic this injector is associated with. - private final WebViewClassic mWebView; + // Whether the accessibility script has been injected into the current page. + private boolean mAccessibilityScriptInjected; - // events scheduled for sending as soon as we receive the selected text - private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>(); + // Constants for determining script injection strategy. + private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1; + private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0; + @SuppressWarnings("unused") + private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1; - // the current traversal axis - private int mCurrentAxis = 2; // sentence + // Alias for TTS API exposed to JavaScript. + private static final String ALIAS_TTS_JS_INTERFACE = "accessibility"; - // we need to consume the up if we have handled the last down - private boolean mLastDownEventHandled; + // Alias for traversal callback exposed to JavaScript. + private static final String ALIAS_TRAVERSAL_JS_INTERFACE = "accessibilityTraversal"; - // getting two empty selection strings in a row we let the WebView handle the event - private boolean mIsLastSelectionStringNull; + // Template for JavaScript that injects a screen-reader. + private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE = + "javascript:(function() {" + + " var chooser = document.createElement('script');" + + " chooser.type = 'text/javascript';" + + " chooser.src = '%1s';" + + " document.getElementsByTagName('head')[0].appendChild(chooser);" + + " })();"; - // keep track of last direction - private int mLastDirection; + // Template for JavaScript that performs AndroidVox actions. + private static final String ACCESSIBILITY_ANDROIDVOX_TEMPLATE = + "cvox.AndroidVox.performAction('%1s')"; /** - * Creates a new injector associated with a given {@link WebViewClassic}. + * Creates an instance of the AccessibilityInjector based on + * {@code webViewClassic}. * - * @param webView The associated WebViewClassic. + * @param webViewClassic The WebViewClassic that this AccessibilityInjector + * manages. */ - public AccessibilityInjector(WebViewClassic webView) { - mWebView = webView; - ensureWebContentKeyBindings(); + public AccessibilityInjector(WebViewClassic webViewClassic) { + mWebViewClassic = webViewClassic; + mWebView = webViewClassic.getWebView(); + mContext = webViewClassic.getContext(); + mAccessibilityManager = AccessibilityManager.getInstance(mContext); } /** - * Processes a key down <code>event</code>. - * - * @return True if the event was processed. + * Attempts to load scripting interfaces for accessibility. + * <p> + * This should be called when the window is attached. + * </p> */ - public boolean onKeyEvent(KeyEvent event) { - // We do not handle ENTER in any circumstances. - if (isEnterActionKey(event.getKeyCode())) { - return false; + public void addAccessibilityApisIfNecessary() { + if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { + return; } - if (event.getAction() == KeyEvent.ACTION_UP) { - return mLastDownEventHandled; + addTtsApis(); + addCallbackApis(); + } + + /** + * Attempts to unload scripting interfaces for accessibility. + * <p> + * This should be called when the window is detached. + * </p> + */ + public void removeAccessibilityApisIfNecessary() { + removeTtsApis(); + removeCallbackApis(); + } + + /** + * Initializes an {@link AccessibilityNodeInfo} with the actions and + * movement granularity levels supported by this + * {@link AccessibilityInjector}. + * <p> + * If an action identifier is added in this method, this + * {@link AccessibilityInjector} should also return {@code true} from + * {@link #supportsAccessibilityAction(int)}. + * </p> + * + * @param info The info to initialize. + * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) + */ + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); + info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); + info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); + info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT); + info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT); + info.addAction(AccessibilityNodeInfo.ACTION_CLICK); + info.setClickable(true); + } + + /** + * Returns {@code true} if this {@link AccessibilityInjector} should handle + * the specified action. + * + * @param action An accessibility action identifier. + * @return {@code true} if this {@link AccessibilityInjector} should handle + * the specified action. + */ + public boolean supportsAccessibilityAction(int action) { + switch (action) { + case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: + case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: + case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT: + case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: + case AccessibilityNodeInfo.ACTION_CLICK: + return true; + default: + return false; } + } - mLastDownEventHandled = false; + /** + * Performs the specified accessibility action. + * + * @param action The identifier of the action to perform. + * @param arguments The action arguments, or {@code null} if no arguments. + * @return {@code true} if the action was successful. + * @see View#performAccessibilityAction(int, Bundle) + */ + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (!isAccessibilityEnabled()) { + mAccessibilityScriptInjected = false; + toggleFallbackAccessibilityInjector(false); + return false; + } - AccessibilityWebContentKeyBinding binding = null; - for (AccessibilityWebContentKeyBinding candidate : sBindings) { - if (event.getKeyCode() == candidate.getKeyCode() - && event.hasModifiers(candidate.getModifiers())) { - binding = candidate; - break; - } + if (mAccessibilityScriptInjected) { + return sendActionToAndroidVox(action, arguments); } + + if (mAccessibilityInjectorFallback != null) { + return mAccessibilityInjectorFallback.performAccessibilityAction(action, arguments); + } + + return false; + } - if (binding == null) { + /** + * Attempts to handle key events when accessibility is turned on. + * + * @param event The key event to handle. + * @return {@code true} if the event was handled. + */ + public boolean handleKeyEventIfNecessary(KeyEvent event) { + if (!isAccessibilityEnabled()) { + mAccessibilityScriptInjected = false; + toggleFallbackAccessibilityInjector(false); return false; } - for (int i = 0, count = binding.getActionCount(); i < count; i++) { - int actionCode = binding.getActionCode(i); - String contentDescription = Integer.toHexString(binding.getAction(i)); - switch (actionCode) { - case ACTION_SET_CURRENT_AXIS: - int axis = binding.getFirstArgument(i); - boolean sendEvent = (binding.getSecondArgument(i) == 1); - setCurrentAxis(axis, sendEvent, contentDescription); - mLastDownEventHandled = true; - break; - case ACTION_TRAVERSE_CURRENT_AXIS: - int direction = binding.getFirstArgument(i); - // on second null selection string in same direction - WebView handles the event - if (direction == mLastDirection && mIsLastSelectionStringNull) { - mIsLastSelectionStringNull = false; - return false; - } - mLastDirection = direction; - sendEvent = (binding.getSecondArgument(i) == 1); - mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent, - contentDescription); - break; - case ACTION_TRAVERSE_GIVEN_AXIS: - direction = binding.getFirstArgument(i); - // on second null selection string in same direction => WebView handle the event - if (direction == mLastDirection && mIsLastSelectionStringNull) { - mIsLastSelectionStringNull = false; - return false; - } - mLastDirection = direction; - axis = binding.getSecondArgument(i); - sendEvent = (binding.getThirdArgument(i) == 1); - traverseGivenAxis(direction, axis, sendEvent, contentDescription); - mLastDownEventHandled = true; - break; - case ACTION_PERFORM_AXIS_TRANSITION: - int fromAxis = binding.getFirstArgument(i); - int toAxis = binding.getSecondArgument(i); - sendEvent = (binding.getThirdArgument(i) == 1); - prefromAxisTransition(fromAxis, toAxis, sendEvent, contentDescription); - mLastDownEventHandled = true; - break; - case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS: - // This is a special case since we treat the default WebView navigation - // behavior as one of the possible navigation axis the user can use. - // If we are not on the default WebView navigation axis this is NOP. - if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) { - // While WebVew handles navigation we do not get null selection - // strings so do not check for that here as the cases above. - mLastDirection = binding.getFirstArgument(i); - sendEvent = (binding.getSecondArgument(i) == 1); - traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR, - sendEvent, contentDescription); - mLastDownEventHandled = false; - } else { - mLastDownEventHandled = true; - } - break; - default: - Log.w(LOG_TAG, "Unknown action code: " + actionCode); + if (mAccessibilityScriptInjected) { + // if an accessibility script is injected we delegate to it the key + // handling. this script is a screen reader which is a fully fledged + // solution for blind users to navigate in and interact with web + // pages. + if (event.getAction() == KeyEvent.ACTION_UP) { + mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event); + } else if (event.getAction() == KeyEvent.ACTION_DOWN) { + mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event); + } else { + return false; } + + return true; } - return mLastDownEventHandled; + if (mAccessibilityInjectorFallback != null) { + // if an accessibility injector is present (no JavaScript enabled or + // the site opts out injecting our JavaScript screen reader) we let + // it decide whether to act on and consume the event. + return mAccessibilityInjectorFallback.onKeyEvent(event); + } + + return false; } /** - * Set the current navigation axis which will be used while - * calling {@link #traverseCurrentAxis(int, boolean, String)}. + * Attempts to handle selection change events when accessibility is using a + * non-JavaScript method. * - * @param axis The axis to set. - * @param sendEvent Whether to send an accessibility event to - * announce the change. + * @param selectionString The selection string. */ - private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) { - mCurrentAxis = axis; - if (sendEvent) { - AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(); - event.getText().add(String.valueOf(axis)); - event.setContentDescription(contentDescription); - sendAccessibilityEvent(event); + public void handleSelectionChangedIfNecessary(String selectionString) { + if (mAccessibilityInjectorFallback != null) { + mAccessibilityInjectorFallback.onSelectionStringChange(selectionString); } } /** - * Performs conditional transition one axis to another. + * Prepares for injecting accessibility scripts into a new page. + * + * @param url The URL that will be loaded. + */ + public void onPageStarted(String url) { + mAccessibilityScriptInjected = false; + } + + /** + * Attempts to inject the accessibility script using a {@code <script>} tag. + * <p> + * This should be called after a page has finished loading. + * </p> * - * @param fromAxis The axis which must be the current for the transition to occur. - * @param toAxis The axis to which to transition. - * @param sendEvent Flag if to send an event to announce successful transition. - * @param contentDescription A description of the performed action. + * @param url The URL that just finished loading. */ - private void prefromAxisTransition(int fromAxis, int toAxis, boolean sendEvent, - String contentDescription) { - if (mCurrentAxis == fromAxis) { - setCurrentAxis(toAxis, sendEvent, contentDescription); + public void onPageFinished(String url) { + if (!isAccessibilityEnabled()) { + mAccessibilityScriptInjected = false; + toggleFallbackAccessibilityInjector(false); + return; } + + if (!shouldInjectJavaScript(url)) { + toggleFallbackAccessibilityInjector(true); + return; + } + + toggleFallbackAccessibilityInjector(false); + + final String injectionUrl = getScreenReaderInjectionUrl(); + mWebView.loadUrl(injectionUrl); + + mAccessibilityScriptInjected = true; } /** - * Traverse the document along the current navigation axis. + * Toggles the non-JavaScript method for handling accessibility. * - * @param direction The direction of traversal. - * @param sendEvent Whether to send an accessibility event to - * announce the change. - * @param contentDescription A description of the performed action. - * @see #setCurrentAxis(int, boolean, String) + * @param enabled {@code true} to enable the non-JavaScript method, or + * {@code false} to disable it. */ - private boolean traverseCurrentAxis(int direction, boolean sendEvent, - String contentDescription) { - return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription); + private void toggleFallbackAccessibilityInjector(boolean enabled) { + if (enabled && (mAccessibilityInjectorFallback == null)) { + mAccessibilityInjectorFallback = new AccessibilityInjectorFallback(mWebViewClassic); + } else { + mAccessibilityInjectorFallback = null; + } } /** - * Traverse the document along the given navigation axis. + * Determines whether it's okay to inject JavaScript into a given URL. * - * @param direction The direction of traversal. - * @param axis The axis along which to traverse. - * @param sendEvent Whether to send an accessibility event to - * announce the change. - * @param contentDescription A description of the performed action. + * @param url The URL to check. + * @return {@code true} if JavaScript should be injected, {@code false} if a + * non-JavaScript method should be used. */ - private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent, - String contentDescription) { - WebViewCore webViewCore = mWebView.getWebViewCore(); - if (webViewCore == null) { + private boolean shouldInjectJavaScript(String url) { + // Respect the WebView's JavaScript setting. + if (!isJavaScriptEnabled()) { return false; } - AccessibilityEvent event = null; - if (sendEvent) { - event = getPartialyPopulatedAccessibilityEvent(); - // the text will be set upon receiving the selection string - event.setContentDescription(contentDescription); + // Allow the page to opt out of Accessibility script injection. + if (getAxsUrlParameterValue(url) == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) { + return false; } - mScheduledEventStack.push(event); - // if the axis is the default let WebView handle the event which will - // result in cursor ring movement and selection of its content - if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) { + // The user must explicitly enable Accessibility script injection. + if (!isScriptInjectionEnabled()) { return false; } - webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis); return true; } /** - * Called when the <code>selectionString</code> has changed. + * @return {@code true} if the user has explicitly enabled Accessibility + * script injection. */ - public void onSelectionStringChange(String selectionString) { - if (DEBUG) { - Log.d(LOG_TAG, "Selection string: " + selectionString); - } - mIsLastSelectionStringNull = (selectionString == null); - if (mScheduledEventStack.isEmpty()) { + private boolean isScriptInjectionEnabled() { + final int injectionSetting = Settings.Secure.getInt( + mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0); + return (injectionSetting == 1); + } + + /** + * Attempts to initialize and add interfaces for TTS, if that hasn't already + * been done. + */ + private void addTtsApis() { + if (mTextToSpeech != null) { return; } - AccessibilityEvent event = mScheduledEventStack.pop(); - if (event != null) { - event.getText().add(selectionString); - sendAccessibilityEvent(event); - } + + final String pkgName = mContext.getPackageName(); + + mTextToSpeech = new TextToSpeech(mContext, null, null, pkgName + ".**webview**", true); + mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE); } /** - * Sends an {@link AccessibilityEvent}. - * - * @param event The event to send. + * Attempts to shutdown and remove interfaces for TTS, if that hasn't + * already been done. */ - private void sendAccessibilityEvent(AccessibilityEvent event) { - if (DEBUG) { - Log.d(LOG_TAG, "Dispatching: " + event); + private void removeTtsApis() { + if (mTextToSpeech == null) { + return; } - // accessibility may be disabled while waiting for the selection string - AccessibilityManager accessibilityManager = - AccessibilityManager.getInstance(mWebView.getContext()); - if (accessibilityManager.isEnabled()) { - accessibilityManager.sendAccessibilityEvent(event); + + mWebView.removeJavascriptInterface(ALIAS_TTS_JS_INTERFACE); + mTextToSpeech.stop(); + mTextToSpeech.shutdown(); + mTextToSpeech = null; + } + + private void addCallbackApis() { + if (mCallback != null) { + return; } + + mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE); + mWebView.addJavascriptInterface(mCallback, ALIAS_TRAVERSAL_JS_INTERFACE); } - /** - * @return An accessibility event whose members are populated except its - * text and content description. - */ - private AccessibilityEvent getPartialyPopulatedAccessibilityEvent() { - AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SELECTED); - event.setClassName(mWebView.getClass().getName()); - event.setPackageName(mWebView.getContext().getPackageName()); - event.setEnabled(mWebView.getWebView().isEnabled()); - return event; + private void removeCallbackApis() { + if (mCallback == null) { + return; + } + + mWebView.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE); + mCallback = null; } /** - * Ensures that the Web content key bindings are loaded. + * Returns the script injection preference requested by the URL, or + * {@link #ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED} if the page has no + * preference. + * + * @param url The URL to check. + * @return A script injection preference. */ - private void ensureWebContentKeyBindings() { - if (sBindings.size() > 0) { - return; + private int getAxsUrlParameterValue(String url) { + if (url == null) { + return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED; } - String webContentKeyBindingsString = Settings.Secure.getString( - mWebView.getContext().getContentResolver(), - Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS); + try { + final List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), null); - SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';'); - semiColonSplitter.setString(webContentKeyBindingsString); - - while (semiColonSplitter.hasNext()) { - String bindingString = semiColonSplitter.next(); - if (TextUtils.isEmpty(bindingString)) { - Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " - + webContentKeyBindingsString); - continue; - } - String[] keyValueArray = bindingString.split("="); - if (keyValueArray.length != 2) { - Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString); - continue; - } - try { - long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim()); - String[] actionStrings = keyValueArray[1].split(":"); - int[] actions = new int[actionStrings.length]; - for (int i = 0, count = actions.length; i < count; i++) { - actions[i] = Integer.decode(actionStrings[i].trim()); + for (NameValuePair param : params) { + if ("axs".equals(param.getName())) { + return verifyInjectionValue(param.getValue()); } - sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions)); - } catch (NumberFormatException nfe) { - Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString); } + } catch (URISyntaxException e) { + // Do nothing. } + + return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED; } - private boolean isEnterActionKey(int keyCode) { - return keyCode == KeyEvent.KEYCODE_DPAD_CENTER - || keyCode == KeyEvent.KEYCODE_ENTER - || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER; + private int verifyInjectionValue(String value) { + try { + final int parsed = Integer.parseInt(value); + + switch (parsed) { + case ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT: + return ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT; + case ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED: + return ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED; + } + } catch (NumberFormatException e) { + // Do nothing. + } + + return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED; } /** - * Represents a web content key-binding. + * @return The URL for injecting the screen reader. */ - private static final class AccessibilityWebContentKeyBinding { + private String getScreenReaderInjectionUrl() { + final String screenReaderUrl = Settings.Secure.getString( + mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL); + return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl); + } - private static final int MODIFIERS_OFFSET = 32; - private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L; + /** + * @return {@code true} if JavaScript is enabled in the {@link WebView} + * settings. + */ + private boolean isJavaScriptEnabled() { + return mWebView.getSettings().getJavaScriptEnabled(); + } - private static final int KEY_CODE_OFFSET = 0; - private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL; + /** + * @return {@code true} if accessibility is enabled. + */ + private boolean isAccessibilityEnabled() { + return mAccessibilityManager.isEnabled(); + } - private static final int ACTION_OFFSET = 24; - private static final int ACTION_MASK = 0xFF000000; + /** + * Packs an accessibility action into a JSON object and sends it to AndroidVox. + * + * @param action The action identifier. + * @param arguments The action arguments, if applicable. + * @return The result of the action. + */ + private boolean sendActionToAndroidVox(int action, Bundle arguments) { + if (mAccessibilityJSONObject == null) { + mAccessibilityJSONObject = new JSONObject(); + } else { + // Remove all keys from the object. + final Iterator<?> keys = mAccessibilityJSONObject.keys(); + while (keys.hasNext()) { + keys.next(); + keys.remove(); + } + } - private static final int FIRST_ARGUMENT_OFFSET = 16; - private static final int FIRST_ARGUMENT_MASK = 0x00FF0000; + try { + mAccessibilityJSONObject.accumulate("action", action); - private static final int SECOND_ARGUMENT_OFFSET = 8; - private static final int SECOND_ARGUMENT_MASK = 0x0000FF00; + switch (action) { + case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: + case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: + final int granularity = arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT); + mAccessibilityJSONObject.accumulate("granularity", granularity); + break; + case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT: + case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: + final String element = arguments.getString( + AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING); + mAccessibilityJSONObject.accumulate("element", element); + break; + } + } catch (JSONException e) { + return false; + } - private static final int THIRD_ARGUMENT_OFFSET = 0; - private static final int THIRD_ARGUMENT_MASK = 0x000000FF; + final String jsonString = mAccessibilityJSONObject.toString(); + final String jsCode = String.format(ACCESSIBILITY_ANDROIDVOX_TEMPLATE, jsonString); + final String result = mCallback.performAction(mWebView, jsCode, DEFAULT_ANDROIDVOX_RESULT); - private final long mKeyCodeAndModifiers; + return ("true".equalsIgnoreCase(result)); + } - private final int [] mActionSequence; + /** + * Exposes result interface to JavaScript. + */ + private static class CallbackHandler { + private static final String JAVASCRIPT_ACTION_TEMPLATE = + "javascript:(function() { %s.onResult(%d, %s); })();"; - /** - * @return The key code of the binding key. - */ - public int getKeyCode() { - return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET); - } + // Time in milliseconds to wait for a result before failing. + private static final long RESULT_TIMEOUT = 200; - /** - * @return The meta state of the binding key. - */ - public int getModifiers() { - return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET); - } + private final AtomicInteger mResultIdCounter = new AtomicInteger(); + private final Object mResultLock = new Object(); + private final String mInterfaceName; - /** - * @return The number of actions in the key binding. - */ - public int getActionCount() { - return mActionSequence.length; - } + private String mResult = null; + private long mResultId = -1; - /** - * @param index The action for a given action <code>index</code>. - */ - public int getAction(int index) { - return mActionSequence[index]; + private CallbackHandler(String interfaceName) { + mInterfaceName = interfaceName; } /** - * @param index The action code for a given action <code>index</code>. + * Performs an action and attempts to wait for a result. + * + * @param webView The WebView to perform the action on. + * @param code JavaScript code that evaluates to a result. + * @param defaultResult The result to return if the action times out. + * @return The result of the action, or false if it timed out. */ - public int getActionCode(int index) { - return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET; + private String performAction(WebView webView, String code, String defaultResult) { + final int resultId = mResultIdCounter.getAndIncrement(); + final String url = String.format( + JAVASCRIPT_ACTION_TEMPLATE, mInterfaceName, resultId, code); + webView.loadUrl(url); + + return getResultAndClear(resultId, defaultResult); } /** - * @param index The first argument for a given action <code>index</code>. + * Gets the result of a request to perform an accessibility action. + * + * @param resultId The result id to match the result with the request. + * @param defaultResult The default result to return on timeout. + * @return The result of the request. */ - public int getFirstArgument(int index) { - return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET; + private String getResultAndClear(int resultId, String defaultResult) { + synchronized (mResultLock) { + final boolean success = waitForResultTimedLocked(resultId); + final String result = success ? mResult : defaultResult; + clearResultLocked(); + return result; + } } /** - * @param index The second argument for a given action <code>index</code>. + * Clears the result state. */ - public int getSecondArgument(int index) { - return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET; + private void clearResultLocked() { + mResultId = -1; + mResult = null; } /** - * @param index The third argument for a given action <code>index</code>. + * Waits up to a given bound for a result of a request and returns it. + * + * @param resultId The result id to match the result with the request. + * @return Whether the result was received. */ - public int getThirdArgument(int index) { - return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET; + private boolean waitForResultTimedLocked(int resultId) { + long waitTimeMillis = RESULT_TIMEOUT; + final long startTimeMillis = SystemClock.uptimeMillis(); + while (true) { + try { + if (mResultId == resultId) { + return true; + } + if (mResultId > resultId) { + return false; + } + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + waitTimeMillis = RESULT_TIMEOUT - elapsedTimeMillis; + if (waitTimeMillis <= 0) { + return false; + } + mResultLock.wait(waitTimeMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } } /** - * Creates a new instance. - * @param keyCodeAndModifiers The key for the binding (key and modifiers). - * @param actionSequence The sequence of action for the binding. + * Callback exposed to JavaScript. Handles returning the result of a + * request to a waiting (or potentially timed out) thread. + * + * @param id The result id of the request as a {@link String}. + * @param result The result of the request as a {@link String}. */ - public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) { - mKeyCodeAndModifiers = keyCodeAndModifiers; - mActionSequence = actionSequence; - } + @SuppressWarnings("unused") + public void onResult(String id, String result) { + final long resultId; - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("modifiers: "); - builder.append(getModifiers()); - builder.append(", keyCode: "); - builder.append(getKeyCode()); - builder.append(", actions["); - for (int i = 0, count = getActionCount(); i < count; i++) { - builder.append("{actionCode"); - builder.append(i); - builder.append(": "); - builder.append(getActionCode(i)); - builder.append(", firstArgument: "); - builder.append(getFirstArgument(i)); - builder.append(", secondArgument: "); - builder.append(getSecondArgument(i)); - builder.append(", thirdArgument: "); - builder.append(getThirdArgument(i)); - builder.append("}"); + try { + resultId = Long.parseLong(id); + } catch (NumberFormatException e) { + return; + } + + synchronized (mResultLock) { + if (resultId > mResultId) { + mResult = result; + mResultId = resultId; + } + mResultLock.notifyAll(); } - builder.append("]"); - return builder.toString(); } } } diff --git a/core/java/android/webkit/AccessibilityInjectorFallback.java b/core/java/android/webkit/AccessibilityInjectorFallback.java new file mode 100644 index 0000000..4d9c26c --- /dev/null +++ b/core/java/android/webkit/AccessibilityInjectorFallback.java @@ -0,0 +1,575 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.TextUtils.SimpleStringSplitter; +import android.util.Log; +import android.view.KeyEvent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.webkit.WebViewCore.EventHub; + +import java.util.ArrayList; +import java.util.Stack; + +/** + * This class injects accessibility into WebViews with disabled JavaScript or + * WebViews with enabled JavaScript but for which we have no accessibility + * script to inject. + * </p> + * Note: To avoid changes in the framework upon changing the available + * navigation axis, or reordering the navigation axis, or changing + * the key bindings, or defining sequence of actions to be bound to + * a given key this class is navigation axis agnostic. It is only + * aware of one navigation axis which is in fact the default behavior + * of webViews while using the DPAD/TrackBall. + * </p> + * In general a key binding is a mapping from modifiers + key code to + * a sequence of actions. For more detail how to specify key bindings refer to + * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}. + * </p> + * The possible actions are invocations to + * {@link #setCurrentAxis(int, boolean, String)}, or + * {@link #traverseCurrentAxis(int, boolean, String)} + * {@link #traverseGivenAxis(int, int, boolean, String)} + * {@link #performAxisTransition(int, int, boolean, String)} + * referred via the values of: + * {@link #ACTION_SET_CURRENT_AXIS}, + * {@link #ACTION_TRAVERSE_CURRENT_AXIS}, + * {@link #ACTION_TRAVERSE_GIVEN_AXIS}, + * {@link #ACTION_PERFORM_AXIS_TRANSITION}, + * respectively. + * The arguments for the action invocation are specified as offset + * hexademical pairs. Note the last argument of the invocation + * should NOT be specified in the binding as it is provided by + * this class. For details about the key binding implementation + * refer to {@link AccessibilityWebContentKeyBinding}. + */ +class AccessibilityInjectorFallback { + private static final String LOG_TAG = "AccessibilityInjector"; + + private static final boolean DEBUG = true; + + private static final int ACTION_SET_CURRENT_AXIS = 0; + private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1; + private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2; + private static final int ACTION_PERFORM_AXIS_TRANSITION = 3; + private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4; + + // WebView navigation axes from WebViewCore.h, plus an additional axis for + // the default behavior. + private static final int NAVIGATION_AXIS_CHARACTER = 0; + private static final int NAVIGATION_AXIS_WORD = 1; + private static final int NAVIGATION_AXIS_SENTENCE = 2; + @SuppressWarnings("unused") + private static final int NAVIGATION_AXIS_HEADING = 3; + private static final int NAVIGATION_AXIS_SIBLING = 5; + @SuppressWarnings("unused") + private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5; + private static final int NAVIGATION_AXIS_DOCUMENT = 6; + private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7; + + // WebView navigation directions from WebViewCore.h. + private static final int NAVIGATION_DIRECTION_BACKWARD = 0; + private static final int NAVIGATION_DIRECTION_FORWARD = 1; + + // these are the same for all instances so make them process wide + private static ArrayList<AccessibilityWebContentKeyBinding> sBindings = + new ArrayList<AccessibilityWebContentKeyBinding>(); + + // handle to the WebViewClassic this injector is associated with. + private final WebViewClassic mWebView; + private final WebView mWebViewInternal; + + // events scheduled for sending as soon as we receive the selected text + private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>(); + + // the current traversal axis + private int mCurrentAxis = 2; // sentence + + // we need to consume the up if we have handled the last down + private boolean mLastDownEventHandled; + + // getting two empty selection strings in a row we let the WebView handle the event + private boolean mIsLastSelectionStringNull; + + // keep track of last direction + private int mLastDirection; + + /** + * Creates a new injector associated with a given {@link WebViewClassic}. + * + * @param webView The associated WebViewClassic. + */ + public AccessibilityInjectorFallback(WebViewClassic webView) { + mWebView = webView; + mWebViewInternal = mWebView.getWebView(); + ensureWebContentKeyBindings(); + } + + /** + * Processes a key down <code>event</code>. + * + * @return True if the event was processed. + */ + public boolean onKeyEvent(KeyEvent event) { + // We do not handle ENTER in any circumstances. + if (isEnterActionKey(event.getKeyCode())) { + return false; + } + + if (event.getAction() == KeyEvent.ACTION_UP) { + return mLastDownEventHandled; + } + + mLastDownEventHandled = false; + + AccessibilityWebContentKeyBinding binding = null; + for (AccessibilityWebContentKeyBinding candidate : sBindings) { + if (event.getKeyCode() == candidate.getKeyCode() + && event.hasModifiers(candidate.getModifiers())) { + binding = candidate; + break; + } + } + + if (binding == null) { + return false; + } + + for (int i = 0, count = binding.getActionCount(); i < count; i++) { + int actionCode = binding.getActionCode(i); + String contentDescription = Integer.toHexString(binding.getAction(i)); + switch (actionCode) { + case ACTION_SET_CURRENT_AXIS: + int axis = binding.getFirstArgument(i); + boolean sendEvent = (binding.getSecondArgument(i) == 1); + setCurrentAxis(axis, sendEvent, contentDescription); + mLastDownEventHandled = true; + break; + case ACTION_TRAVERSE_CURRENT_AXIS: + int direction = binding.getFirstArgument(i); + // on second null selection string in same direction - WebView handles the event + if (direction == mLastDirection && mIsLastSelectionStringNull) { + mIsLastSelectionStringNull = false; + return false; + } + mLastDirection = direction; + sendEvent = (binding.getSecondArgument(i) == 1); + mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent, + contentDescription); + break; + case ACTION_TRAVERSE_GIVEN_AXIS: + direction = binding.getFirstArgument(i); + // on second null selection string in same direction => WebView handle the event + if (direction == mLastDirection && mIsLastSelectionStringNull) { + mIsLastSelectionStringNull = false; + return false; + } + mLastDirection = direction; + axis = binding.getSecondArgument(i); + sendEvent = (binding.getThirdArgument(i) == 1); + traverseGivenAxis(direction, axis, sendEvent, contentDescription); + mLastDownEventHandled = true; + break; + case ACTION_PERFORM_AXIS_TRANSITION: + int fromAxis = binding.getFirstArgument(i); + int toAxis = binding.getSecondArgument(i); + sendEvent = (binding.getThirdArgument(i) == 1); + performAxisTransition(fromAxis, toAxis, sendEvent, contentDescription); + mLastDownEventHandled = true; + break; + case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS: + // This is a special case since we treat the default WebView navigation + // behavior as one of the possible navigation axis the user can use. + // If we are not on the default WebView navigation axis this is NOP. + if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) { + // While WebVew handles navigation we do not get null selection + // strings so do not check for that here as the cases above. + mLastDirection = binding.getFirstArgument(i); + sendEvent = (binding.getSecondArgument(i) == 1); + traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR, + sendEvent, contentDescription); + mLastDownEventHandled = false; + } else { + mLastDownEventHandled = true; + } + break; + default: + Log.w(LOG_TAG, "Unknown action code: " + actionCode); + } + } + + return mLastDownEventHandled; + } + + /** + * Set the current navigation axis which will be used while + * calling {@link #traverseCurrentAxis(int, boolean, String)}. + * + * @param axis The axis to set. + * @param sendEvent Whether to send an accessibility event to + * announce the change. + */ + private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) { + mCurrentAxis = axis; + if (sendEvent) { + final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent( + AccessibilityEvent.TYPE_ANNOUNCEMENT); + event.getText().add(String.valueOf(axis)); + event.setContentDescription(contentDescription); + sendAccessibilityEvent(event); + } + } + + /** + * Performs conditional transition one axis to another. + * + * @param fromAxis The axis which must be the current for the transition to occur. + * @param toAxis The axis to which to transition. + * @param sendEvent Flag if to send an event to announce successful transition. + * @param contentDescription A description of the performed action. + */ + private void performAxisTransition(int fromAxis, int toAxis, boolean sendEvent, + String contentDescription) { + if (mCurrentAxis == fromAxis) { + setCurrentAxis(toAxis, sendEvent, contentDescription); + } + } + + /** + * Traverse the document along the current navigation axis. + * + * @param direction The direction of traversal. + * @param sendEvent Whether to send an accessibility event to + * announce the change. + * @param contentDescription A description of the performed action. + * @see #setCurrentAxis(int, boolean, String) + */ + private boolean traverseCurrentAxis(int direction, boolean sendEvent, + String contentDescription) { + return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription); + } + + boolean performAccessibilityAction(int action, Bundle arguments) { + switch (action) { + case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: + case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: + final int direction = getDirectionForAction(action); + final int axis = getAxisForGranularity(arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT)); + return traverseGivenAxis(direction, axis, true, null); + default: + return false; + } + } + + /** + * Returns the {@link WebView}-defined direction for the given + * {@link AccessibilityNodeInfo}-defined action. + * + * @param action An accessibility action identifier. + * @return A web view navigation direction. + */ + private static int getDirectionForAction(int action) { + switch (action) { + case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: + return NAVIGATION_DIRECTION_FORWARD; + case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: + return NAVIGATION_DIRECTION_BACKWARD; + default: + return -1; + } + } + + /** + * Returns the {@link WebView}-defined axis for the given + * {@link AccessibilityNodeInfo}-defined granularity. + * + * @param granularity An accessibility granularity identifier. + * @return A web view navigation axis. + */ + private static int getAxisForGranularity(int granularity) { + switch (granularity) { + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: + return NAVIGATION_AXIS_CHARACTER; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD: + return NAVIGATION_AXIS_WORD; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: + return NAVIGATION_AXIS_SENTENCE; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH: + // TODO: Figure out what nextSibling() actually means. + return NAVIGATION_AXIS_SIBLING; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: + return NAVIGATION_AXIS_DOCUMENT; + default: + return -1; + } + } + + /** + * Traverse the document along the given navigation axis. + * + * @param direction The direction of traversal. + * @param axis The axis along which to traverse. + * @param sendEvent Whether to send an accessibility event to + * announce the change. + * @param contentDescription A description of the performed action. + */ + private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent, + String contentDescription) { + WebViewCore webViewCore = mWebView.getWebViewCore(); + if (webViewCore == null) { + return false; + } + + AccessibilityEvent event = null; + if (sendEvent) { + event = getPartialyPopulatedAccessibilityEvent( + AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY); + // the text will be set upon receiving the selection string + event.setContentDescription(contentDescription); + } + mScheduledEventStack.push(event); + + // if the axis is the default let WebView handle the event which will + // result in cursor ring movement and selection of its content + if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) { + return false; + } + + webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis); + return true; + } + + /** + * Called when the <code>selectionString</code> has changed. + */ + public void onSelectionStringChange(String selectionString) { + if (DEBUG) { + Log.d(LOG_TAG, "Selection string: " + selectionString); + } + mIsLastSelectionStringNull = (selectionString == null); + if (mScheduledEventStack.isEmpty()) { + return; + } + AccessibilityEvent event = mScheduledEventStack.pop(); + if ((event != null) && (selectionString != null)) { + event.getText().add(selectionString); + event.setFromIndex(0); + event.setToIndex(selectionString.length()); + sendAccessibilityEvent(event); + } + } + + /** + * Sends an {@link AccessibilityEvent}. + * + * @param event The event to send. + */ + private void sendAccessibilityEvent(AccessibilityEvent event) { + if (DEBUG) { + Log.d(LOG_TAG, "Dispatching: " + event); + } + // accessibility may be disabled while waiting for the selection string + AccessibilityManager accessibilityManager = + AccessibilityManager.getInstance(mWebView.getContext()); + if (accessibilityManager.isEnabled()) { + accessibilityManager.sendAccessibilityEvent(event); + } + } + + /** + * @return An accessibility event whose members are populated except its + * text and content description. + */ + private AccessibilityEvent getPartialyPopulatedAccessibilityEvent(int eventType) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + mWebViewInternal.onInitializeAccessibilityEvent(event); + return event; + } + + /** + * Ensures that the Web content key bindings are loaded. + */ + private void ensureWebContentKeyBindings() { + if (sBindings.size() > 0) { + return; + } + + String webContentKeyBindingsString = Settings.Secure.getString( + mWebView.getContext().getContentResolver(), + Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS); + + SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';'); + semiColonSplitter.setString(webContentKeyBindingsString); + + while (semiColonSplitter.hasNext()) { + String bindingString = semiColonSplitter.next(); + if (TextUtils.isEmpty(bindingString)) { + Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + + webContentKeyBindingsString); + continue; + } + String[] keyValueArray = bindingString.split("="); + if (keyValueArray.length != 2) { + Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString); + continue; + } + try { + long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim()); + String[] actionStrings = keyValueArray[1].split(":"); + int[] actions = new int[actionStrings.length]; + for (int i = 0, count = actions.length; i < count; i++) { + actions[i] = Integer.decode(actionStrings[i].trim()); + } + sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions)); + } catch (NumberFormatException nfe) { + Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString); + } + } + } + + private boolean isEnterActionKey(int keyCode) { + return keyCode == KeyEvent.KEYCODE_DPAD_CENTER + || keyCode == KeyEvent.KEYCODE_ENTER + || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER; + } + + /** + * Represents a web content key-binding. + */ + private static final class AccessibilityWebContentKeyBinding { + + private static final int MODIFIERS_OFFSET = 32; + private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L; + + private static final int KEY_CODE_OFFSET = 0; + private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL; + + private static final int ACTION_OFFSET = 24; + private static final int ACTION_MASK = 0xFF000000; + + private static final int FIRST_ARGUMENT_OFFSET = 16; + private static final int FIRST_ARGUMENT_MASK = 0x00FF0000; + + private static final int SECOND_ARGUMENT_OFFSET = 8; + private static final int SECOND_ARGUMENT_MASK = 0x0000FF00; + + private static final int THIRD_ARGUMENT_OFFSET = 0; + private static final int THIRD_ARGUMENT_MASK = 0x000000FF; + + private final long mKeyCodeAndModifiers; + + private final int [] mActionSequence; + + /** + * @return The key code of the binding key. + */ + public int getKeyCode() { + return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET); + } + + /** + * @return The meta state of the binding key. + */ + public int getModifiers() { + return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET); + } + + /** + * @return The number of actions in the key binding. + */ + public int getActionCount() { + return mActionSequence.length; + } + + /** + * @param index The action for a given action <code>index</code>. + */ + public int getAction(int index) { + return mActionSequence[index]; + } + + /** + * @param index The action code for a given action <code>index</code>. + */ + public int getActionCode(int index) { + return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET; + } + + /** + * @param index The first argument for a given action <code>index</code>. + */ + public int getFirstArgument(int index) { + return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET; + } + + /** + * @param index The second argument for a given action <code>index</code>. + */ + public int getSecondArgument(int index) { + return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET; + } + + /** + * @param index The third argument for a given action <code>index</code>. + */ + public int getThirdArgument(int index) { + return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET; + } + + /** + * Creates a new instance. + * @param keyCodeAndModifiers The key for the binding (key and modifiers). + * @param actionSequence The sequence of action for the binding. + */ + public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) { + mKeyCodeAndModifiers = keyCodeAndModifiers; + mActionSequence = actionSequence; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("modifiers: "); + builder.append(getModifiers()); + builder.append(", keyCode: "); + builder.append(getKeyCode()); + builder.append(", actions["); + for (int i = 0, count = getActionCount(); i < count; i++) { + builder.append("{actionCode"); + builder.append(i); + builder.append(": "); + builder.append(getActionCode(i)); + builder.append(", firstArgument: "); + builder.append(getFirstArgument(i)); + builder.append(", secondArgument: "); + builder.append(getSecondArgument(i)); + builder.append(", thirdArgument: "); + builder.append(getThirdArgument(i)); + builder.append("}"); + } + builder.append("]"); + return builder.toString(); + } + } +} diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java index 6b7263c..1a4ccfa 100644 --- a/core/java/android/webkit/FindActionModeCallback.java +++ b/core/java/android/webkit/FindActionModeCallback.java @@ -34,7 +34,7 @@ import android.widget.EditText; import android.widget.TextView; class FindActionModeCallback implements ActionMode.Callback, TextWatcher, - View.OnLongClickListener, View.OnClickListener { + View.OnClickListener { private View mCustomView; private EditText mEditText; private TextView mMatches; @@ -51,9 +51,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher, com.android.internal.R.layout.webview_find, null); mEditText = (EditText) mCustomView.findViewById( com.android.internal.R.id.edit); - // Override long click so that select ActionMode is not opened, which - // would exit find ActionMode. - mEditText.setOnLongClickListener(this); + mEditText.setCustomSelectionActionModeCallback(new NoAction()); mEditText.setOnClickListener(this); setText(""); mMatches = (TextView) mCustomView.findViewById( @@ -174,11 +172,6 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher, mMatches.setVisibility(View.VISIBLE); } - // OnLongClickListener implementation - - @Override - public boolean onLongClick(View v) { return true; } - // OnClickListener implementation @Override @@ -280,4 +273,24 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher, return mGlobalVisibleRect.bottom; } + public static class NoAction implements ActionMode.Callback { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + } + } } diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java index 689884f..fc5df2d 100644 --- a/core/java/android/webkit/HTML5Audio.java +++ b/core/java/android/webkit/HTML5Audio.java @@ -67,6 +67,8 @@ class HTML5Audio extends Handler private String mUrl; private boolean mAskToPlay = false; + private boolean mLoopEnabled = false; + private boolean mProcessingOnEnd = false; private Context mContext; // Timer thread -> UI thread @@ -143,7 +145,13 @@ class HTML5Audio extends Handler // MediaPlayer.OnCompletionListener; public void onCompletion(MediaPlayer mp) { mState = COMPLETE; + mProcessingOnEnd = true; nativeOnEnded(mNativePointer); + mProcessingOnEnd = false; + if (mLoopEnabled == true) { + nativeOnRequestPlay(mNativePointer); + mLoopEnabled = false; + } } // MediaPlayer.OnErrorListener @@ -264,14 +272,10 @@ class HTML5Audio extends Handler private void play() { - if (mState == COMPLETE) { + if (mState == COMPLETE && mLoopEnabled == true) { // Play it again, Sam - mTimer.cancel(); - mTimer = new Timer(); - mAskToPlay = true; - mMediaPlayer.stop(); - mState = STOPPED; - mMediaPlayer.prepareAsync(); + mMediaPlayer.start(); + mState = STARTED; return; } @@ -304,14 +308,11 @@ class HTML5Audio extends Handler } private void seek(int msec) { + if (mProcessingOnEnd == true && mState == COMPLETE && msec == 0) { + mLoopEnabled = true; + } if (mState >= PREPARED) { mMediaPlayer.seekTo(msec); - if (mState == COMPLETE) { - // Seeking after the stream had completed will - // cause us to start playing again. This is to - // support audio tags that specify loop=true. - play(); - } } } @@ -336,6 +337,7 @@ class HTML5Audio extends Handler private native void nativeOnBuffering(int percent, int nativePointer); private native void nativeOnEnded(int nativePointer); + private native void nativeOnRequestPlay(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 57628d3..f9f5b03 100644 --- a/core/java/android/webkit/SelectActionModeCallback.java +++ b/core/java/android/webkit/SelectActionModeCallback.java @@ -16,6 +16,7 @@ package android.webkit; +import android.app.Activity; import android.app.SearchManager; import android.content.ClipboardManager; import android.content.Context; @@ -122,6 +123,9 @@ class SelectActionModeCallback implements ActionMode.Callback { Intent i = new Intent(Intent.ACTION_WEB_SEARCH); i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true); i.putExtra(SearchManager.QUERY, mWebView.getSelection()); + if (!(mWebView.getContext() instanceof Activity)) { + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } mWebView.getContext().startActivity(i); break; diff --git a/core/java/android/webkit/WebCoreThreadWatchdog.java b/core/java/android/webkit/WebCoreThreadWatchdog.java index 655db31..a22e6e8 100644 --- a/core/java/android/webkit/WebCoreThreadWatchdog.java +++ b/core/java/android/webkit/WebCoreThreadWatchdog.java @@ -26,6 +26,10 @@ import android.os.Message; import android.os.Process; import android.webkit.WebViewCore.EventHub; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + // A Runnable that will monitor if the WebCore thread is still // processing messages by pinging it every so often. It is safe // to call the public methods of this class from any thread. @@ -51,25 +55,31 @@ class WebCoreThreadWatchdog implements Runnable { // After the first timeout, use a shorter period before re-prompting the user. private static final int SUBSEQUENT_TIMEOUT_PERIOD = 15 * 1000; - private Context mContext; private Handler mWebCoreThreadHandler; private Handler mHandler; private boolean mPaused; + private Set<WebViewClassic> mWebViews; + private static WebCoreThreadWatchdog sInstance; - public synchronized static WebCoreThreadWatchdog start(Context context, - Handler webCoreThreadHandler) { + public synchronized static WebCoreThreadWatchdog start(Handler webCoreThreadHandler) { if (sInstance == null) { - sInstance = new WebCoreThreadWatchdog(context, webCoreThreadHandler); + sInstance = new WebCoreThreadWatchdog(webCoreThreadHandler); new Thread(sInstance, "WebCoreThreadWatchdog").start(); } return sInstance; } - public synchronized static void updateContext(Context context) { + public synchronized static void registerWebView(WebViewClassic w) { if (sInstance != null) { - sInstance.setContext(context); + sInstance.addWebView(w); + } + } + + public synchronized static void unregisterWebView(WebViewClassic w) { + if (sInstance != null) { + sInstance.removeWebView(w); } } @@ -85,12 +95,18 @@ class WebCoreThreadWatchdog implements Runnable { } } - private void setContext(Context context) { - mContext = context; + private void addWebView(WebViewClassic w) { + if (mWebViews == null) { + mWebViews = new HashSet<WebViewClassic>(); + } + mWebViews.add(w); } - private WebCoreThreadWatchdog(Context context, Handler webCoreThreadHandler) { - mContext = context; + private void removeWebView(WebViewClassic w) { + mWebViews.remove(w); + } + + private WebCoreThreadWatchdog(Handler webCoreThreadHandler) { mWebCoreThreadHandler = webCoreThreadHandler; } @@ -147,39 +163,41 @@ class WebCoreThreadWatchdog implements Runnable { break; case TIMED_OUT: - if ((mContext == null) || !(mContext instanceof Activity)) return; - new AlertDialog.Builder(mContext) - .setMessage(com.android.internal.R.string.webpage_unresponsive) - .setPositiveButton(com.android.internal.R.string.force_close, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // User chose to force close. - Process.killProcess(Process.myPid()); - } - }) - .setNegativeButton(com.android.internal.R.string.wait, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // The user chose to wait. The last HEARTBEAT message - // will still be in the WebCore thread's queue, so all - // we need to do is post another TIMED_OUT so that the - // user will get prompted again if the WebCore thread - // doesn't sort itself out. - sendMessageDelayed(obtainMessage(TIMED_OUT), - SUBSEQUENT_TIMEOUT_PERIOD); - } - }) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - sendMessageDelayed(obtainMessage(TIMED_OUT), - SUBSEQUENT_TIMEOUT_PERIOD); + boolean postedDialog = false; + synchronized (WebCoreThreadWatchdog.class) { + Iterator<WebViewClassic> it = mWebViews.iterator(); + // Check each WebView we are aware of and find one that is capable of + // showing the user a prompt dialog. + while (it.hasNext()) { + WebView activeView = it.next().getWebView(); + + if (activeView.getWindowToken() != null && + activeView.getViewRootImpl() != null) { + postedDialog = activeView.post(new PageNotRespondingRunnable( + activeView.getContext(), this)); + + if (postedDialog) { + // We placed the message into the UI thread for an attached + // WebView so we've made our best attempt to display the + // "page not responding" dialog to the user. Although the + // message is in the queue, there is no guarantee when/if + // the runnable will execute. In the case that the runnable + // never executes, the user will need to terminate the + // process manually. + break; } - }) - .setIcon(android.R.drawable.ic_dialog_alert) - .show(); + } + } + + if (!postedDialog) { + // There's no active webview we can use to show the dialog, so + // wait again. If we never get a usable view, the user will + // never get the chance to terminate the process, and will + // need to do it manually. + sendMessageDelayed(obtainMessage(TIMED_OUT), + SUBSEQUENT_TIMEOUT_PERIOD); + } + } break; } } @@ -205,4 +223,55 @@ class WebCoreThreadWatchdog implements Runnable { Looper.loop(); } + + private class PageNotRespondingRunnable implements Runnable { + Context mContext; + private Handler mWatchdogHandler; + + public PageNotRespondingRunnable(Context context, Handler watchdogHandler) { + mContext = context; + mWatchdogHandler = watchdogHandler; + } + + @Override + public void run() { + // This must run on the UI thread as it is displaying an AlertDialog. + assert Looper.getMainLooper().getThread() == Thread.currentThread(); + new AlertDialog.Builder(mContext) + .setMessage(com.android.internal.R.string.webpage_unresponsive) + .setPositiveButton(com.android.internal.R.string.force_close, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // User chose to force close. + Process.killProcess(Process.myPid()); + } + }) + .setNegativeButton(com.android.internal.R.string.wait, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // The user chose to wait. The last HEARTBEAT message + // will still be in the WebCore thread's queue, so all + // we need to do is post another TIMED_OUT so that the + // user will get prompted again if the WebCore thread + // doesn't sort itself out. + mWatchdogHandler.sendMessageDelayed( + mWatchdogHandler.obtainMessage(TIMED_OUT), + SUBSEQUENT_TIMEOUT_PERIOD); + } + }) + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + mWatchdogHandler.sendMessageDelayed( + mWatchdogHandler.obtainMessage(TIMED_OUT), + SUBSEQUENT_TIMEOUT_PERIOD); + } + }) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } + } } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index ba5a417..cbb3011 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1686,6 +1686,10 @@ public class WebView extends AbsoluteLayout WebView.super.computeScroll(); } + public boolean super_performAccessibilityAction(int action, Bundle arguments) { + return WebView.super.performAccessibilityAction(action, arguments); + } + public boolean super_performLongClick() { return WebView.super.performLongClick(); } @@ -1938,6 +1942,11 @@ public class WebView extends AbsoluteLayout mProvider.getViewDelegate().onInitializeAccessibilityEvent(event); } + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + return mProvider.getViewDelegate().performAccessibilityAction(action, arguments); + } + /** @hide */ @Override protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index f1f3db2..65fd59a 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -60,9 +60,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; -import android.provider.Settings; import android.security.KeyChain; -import android.speech.tts.TextToSpeech; import android.text.Editable; import android.text.InputType; import android.text.Selection; @@ -102,7 +100,6 @@ import android.webkit.WebViewCore.DrawData; import android.webkit.WebViewCore.EventHub; import android.webkit.WebViewCore.TextFieldInitData; import android.webkit.WebViewCore.TextSelectionData; -import android.webkit.WebViewCore.TouchHighlightData; import android.webkit.WebViewCore.WebKitHitTest; import android.widget.AbsoluteLayout; import android.widget.Adapter; @@ -276,6 +273,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc newCursorPosition -= text.length() - limitedText.length(); } super.setComposingText(limitedText, newCursorPosition); + updateSelection(); if (limitedText != text) { restartInput(); int lastCaret = start + limitedText.length(); @@ -288,19 +286,44 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override public boolean commitText(CharSequence text, int newCursorPosition) { setComposingText(text, newCursorPosition); - int cursorPosition = Selection.getSelectionEnd(getEditable()); - setComposingRegion(cursorPosition, cursorPosition); + finishComposingText(); return true; } @Override public boolean deleteSurroundingText(int leftLength, int rightLength) { - Editable editable = getEditable(); - int cursorPosition = Selection.getSelectionEnd(editable); - int startDelete = Math.max(0, cursorPosition - leftLength); - int endDelete = Math.min(editable.length(), - cursorPosition + rightLength); - setNewText(startDelete, endDelete, ""); + // This code is from BaseInputConnection#deleteSurroundText. + // We have to delete the same text in webkit. + Editable content = getEditable(); + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + if (a > b) { + int tmp = a; + a = b; + b = tmp; + } + + int ca = getComposingSpanStart(content); + int cb = getComposingSpanEnd(content); + if (cb < ca) { + int tmp = ca; + ca = cb; + cb = tmp; + } + if (ca != -1 && cb != -1) { + if (ca < a) a = ca; + if (cb > b) b = cb; + } + + int endDelete = Math.min(content.length(), b + rightLength); + if (endDelete > b) { + setNewText(b, endDelete, ""); + } + int startDelete = Math.max(0, a - leftLength); + if (startDelete < a) { + setNewText(startDelete, a, ""); + } return super.deleteSurroundingText(leftLength, rightLength); } @@ -413,6 +436,46 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc outAttrs.imeOptions = mImeOptions; outAttrs.hintText = mHint; outAttrs.initialCapsMode = getCursorCapsMode(InputType.TYPE_CLASS_TEXT); + + Editable editable = getEditable(); + int selectionStart = Selection.getSelectionStart(editable); + int selectionEnd = Selection.getSelectionEnd(editable); + if (selectionStart < 0 || selectionEnd < 0) { + selectionStart = editable.length(); + selectionEnd = selectionStart; + } + outAttrs.initialSelStart = selectionStart; + outAttrs.initialSelEnd = selectionEnd; + } + + @Override + public boolean setSelection(int start, int end) { + boolean result = super.setSelection(start, end); + updateSelection(); + return result; + } + + @Override + public boolean setComposingRegion(int start, int end) { + boolean result = super.setComposingRegion(start, end); + updateSelection(); + return result; + } + + /** + * Send the selection and composing spans to the IME. + */ + private void updateSelection() { + Editable editable = getEditable(); + int selectionStart = Selection.getSelectionStart(editable); + int selectionEnd = Selection.getSelectionEnd(editable); + int composingStart = getComposingSpanStart(editable); + int composingEnd = getComposingSpanEnd(editable); + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.updateSelection(mWebView, selectionStart, selectionEnd, + composingStart, composingEnd); + } } /** @@ -431,14 +494,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc boolean isCharacterDelete = false; int textLength = text.length(); int originalLength = original.length(); - if (textLength > originalLength) { - isCharacterAdd = (textLength == originalLength + 1) - && TextUtils.regionMatches(text, 0, original, 0, - originalLength); - } else if (originalLength > textLength) { - isCharacterDelete = (textLength == originalLength - 1) - && TextUtils.regionMatches(text, 0, original, 0, - textLength); + int selectionStart = Selection.getSelectionStart(editable); + int selectionEnd = Selection.getSelectionEnd(editable); + if (selectionStart == selectionEnd) { + if (textLength > originalLength) { + isCharacterAdd = (textLength == originalLength + 1) + && TextUtils.regionMatches(text, 0, original, 0, + originalLength); + } else if (originalLength > textLength) { + isCharacterDelete = (textLength == originalLength - 1) + && TextUtils.regionMatches(text, 0, original, 0, + textLength); + } } if (isCharacterAdd) { sendCharacter(text.charAt(textLength - 1)); @@ -867,15 +934,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private static final int MOTIONLESS_IGNORE = 3; private int mHeldMotionless; - // An instance for injecting accessibility in WebViews with disabled - // JavaScript or ones for which no accessibility script exists + // Lazily-instantiated instance for injecting accessibility. private AccessibilityInjector mAccessibilityInjector; - // flag indicating if accessibility script is injected so we - // know to handle Shift and arrows natively first - private boolean mAccessibilityScriptInjected; - - /** * How long the caret handle will last without being touched. */ @@ -934,7 +995,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private static final int RELEASE_SINGLE_TAP = 5; private static final int REQUEST_FORM_DATA = 6; private static final int DRAG_HELD_MOTIONLESS = 8; - private static final int AWAKEN_SCROLL_BARS = 9; private static final int PREVENT_DEFAULT_TIMEOUT = 10; private static final int SCROLL_SELECT_TEXT = 11; @@ -1002,7 +1062,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc "REQUEST_FORM_DATA", // = 6; "RESUME_WEBCORE_PRIORITY", // = 7; "DRAG_HELD_MOTIONLESS", // = 8; - "AWAKEN_SCROLL_BARS", // = 9; + "", // = 9; "PREVENT_DEFAULT_TIMEOUT", // = 10; "SCROLL_SELECT_TEXT" // = 11; }; @@ -1085,34 +1145,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private int mHorizontalScrollBarMode = SCROLLBAR_AUTO; private int mVerticalScrollBarMode = SCROLLBAR_AUTO; - // constants for determining script injection strategy - private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1; - private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0; - private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1; - - // the alias via which accessibility JavaScript interface is exposed - private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility"; - - // Template for JavaScript that injects a screen-reader. - private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE = - "javascript:(function() {" + - " var chooser = document.createElement('script');" + - " chooser.type = 'text/javascript';" + - " chooser.src = '%1s';" + - " document.getElementsByTagName('head')[0].appendChild(chooser);" + - " })();"; - - // Regular expression that matches the "axs" URL parameter. - // The value of 0 means the accessibility script is opted out - // The value of 1 means the accessibility script is already injected - private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))"; - - // TextToSpeech instance exposed to JavaScript to the injected screenreader. - private TextToSpeech mTextToSpeech; - - // variable to cache the above pattern in case accessibility is enabled. - private Pattern mMatchAxsUrlParameterPattern; - /** * Max distance to overscroll by in pixels. * This how far content can be pulled beyond its normal bounds by the user. @@ -1598,8 +1630,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private void init() { OnTrimMemoryListener.init(mContext); mWebView.setWillNotDraw(false); - mWebView.setFocusable(true); - mWebView.setFocusableInTouchMode(true); mWebView.setClickable(true); mWebView.setLongClickable(true); @@ -1633,43 +1663,66 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return true; } - /** - * Adds accessibility APIs to JavaScript. - * - * Note: This method is responsible to performing the necessary - * check if the accessibility APIs should be exposed. - */ - private void addAccessibilityApisToJavaScript() { - if (AccessibilityManager.getInstance(mContext).isEnabled() - && getSettings().getJavaScriptEnabled()) { - // exposing the TTS for now ... - final Context ctx = mContext; - if (ctx != null) { - final String packageName = ctx.getPackageName(); - if (packageName != null) { - mTextToSpeech = new TextToSpeech(ctx, null, null, - packageName + ".**webview**", true); - addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE); + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (!mWebView.isEnabled()) { + // Only default actions are supported while disabled. + return mWebViewPrivate.super_performAccessibilityAction(action, arguments); + } + + if (getAccessibilityInjector().supportsAccessibilityAction(action)) { + return getAccessibilityInjector().performAccessibilityAction(action, arguments); + } + + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + final int convertedContentHeight = contentToViewY(getContentHeight()); + final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop() + - mWebView.getPaddingBottom(); + final int maxScrollY = Math.max(convertedContentHeight - adjustedViewHeight, 0); + final boolean canScrollBackward = (getScrollY() > 0); + final boolean canScrollForward = ((getScrollY() - maxScrollY) > 0); + if ((action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) && canScrollBackward) { + mWebView.scrollBy(0, adjustedViewHeight); + return true; } + if ((action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) && canScrollForward) { + mWebView.scrollBy(0, -adjustedViewHeight); + return true; + } + return false; } } - } - /** - * Removes accessibility APIs from JavaScript. - */ - private void removeAccessibilityApisFromJavaScript() { - // exposing the TTS for now ... - if (mTextToSpeech != null) { - removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE); - mTextToSpeech.shutdown(); - mTextToSpeech = null; - } + return mWebViewPrivate.super_performAccessibilityAction(action, arguments); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + if (!mWebView.isEnabled()) { + // Only default actions are supported while disabled. + return; + } + info.setScrollable(isScrollableForAccessibility()); + + final int convertedContentHeight = contentToViewY(getContentHeight()); + final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop() + - mWebView.getPaddingBottom(); + final int maxScrollY = Math.max(convertedContentHeight - adjustedViewHeight, 0); + final boolean canScrollBackward = (getScrollY() > 0); + final boolean canScrollForward = ((getScrollY() - maxScrollY) > 0); + + if (canScrollForward) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + + if (canScrollForward) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + + getAccessibilityInjector().onInitializeAccessibilityNodeInfo(info); } @Override @@ -1687,6 +1740,17 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0)); } + private boolean isAccessibilityEnabled() { + return AccessibilityManager.getInstance(mContext).isEnabled(); + } + + private AccessibilityInjector getAccessibilityInjector() { + if (mAccessibilityInjector == null) { + mAccessibilityInjector = new AccessibilityInjector(this); + } + return mAccessibilityInjector; + } + private boolean isScrollableForAccessibility() { return (contentToViewX(getContentWidth()) > getWidth() - mWebView.getPaddingLeft() - mWebView.getPaddingRight() @@ -2005,6 +2069,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } private void destroyImpl() { + ViewRootImpl viewRoot = mWebView.getViewRootImpl(); + if (viewRoot != null) { + Log.e(LOGTAG, "Error: WebView.destroy() called while still attached!"); + } + + if (mWebView.isHardwareAccelerated()) { + int drawGLFunction = nativeGetDrawGLFunction(mNativeClass); + if (drawGLFunction != 0 && viewRoot != null) { + // functor should have been detached in onDetachedFromWindow, do + // additionally here for safety + viewRoot.detachFunctor(drawGLFunction); + } + } + mCallbackProxy.blockMessages(); clearHelpers(); if (mListBoxDialog != null) { @@ -3368,11 +3446,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc nativeSetPauseDrawing(mNativeClass, false); } } - // Ensure that the watchdog has a currently valid Context to be able to display - // a prompt dialog. For example, if the Activity was finished whilst the WebCore - // thread was blocked and the Activity is started again, we may reuse the blocked - // thread, but we'll have a new Activity. - WebCoreThreadWatchdog.updateContext(mContext); // We get a call to onResume for new WebViews (i.e. mIsPaused will be false). We need // to ensure that the Watchdog thread is running for the new WebView, so call // it outside the if block above. @@ -3775,7 +3848,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, animationDuration > 0 ? animationDuration : computeDuration(dx, dy)); - mWebViewPrivate.awakenScrollBars(mScroller.getDuration()); invalidate(); } else { mWebView.scrollTo(x, y); @@ -3828,7 +3900,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // reset the flag since we set to true in if need after // loading is see onPageFinished(Url) - mAccessibilityScriptInjected = false; + if (isAccessibilityEnabled()) { + getAccessibilityInjector().onPageStarted(url); + } // Don't start out editing. mIsEditingText = false; @@ -3840,116 +3914,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ /* package */ void onPageFinished(String url) { mZoomManager.onPageFinished(url); - injectAccessibilityForUrl(url); - } - - /** - * This method injects accessibility in the loaded document if accessibility - * is enabled. If JavaScript is enabled we try to inject a URL specific script. - * If no URL specific script is found or JavaScript is disabled we fallback to - * the default {@link AccessibilityInjector} implementation. - * </p> - * If the URL has the "axs" paramter set to 1 it has already done the - * script injection so we do nothing. If the parameter is set to 0 - * the URL opts out accessibility script injection so we fall back to - * the default {@link AccessibilityInjector}. - * </p> - * Note: If the user has not opted-in the accessibility script injection no scripts - * are injected rather the default {@link AccessibilityInjector} implementation - * is used. - * - * @param url The URL loaded by this {@link WebView}. - */ - private void injectAccessibilityForUrl(String url) { - if (mWebViewCore == null) { - return; - } - AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); - - if (!accessibilityManager.isEnabled()) { - // it is possible that accessibility was turned off between reloads - ensureAccessibilityScriptInjectorInstance(false); - return; - } - - if (!getSettings().getJavaScriptEnabled()) { - // no JS so we fallback to the basic buil-in support - ensureAccessibilityScriptInjectorInstance(true); - return; - } - // check the URL "axs" parameter to choose appropriate action - int axsParameterValue = getAxsUrlParameterValue(url); - if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) { - boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext - .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1); - if (onDeviceScriptInjectionEnabled) { - ensureAccessibilityScriptInjectorInstance(false); - // neither script injected nor script injection opted out => we inject - mWebView.loadUrl(getScreenReaderInjectingJs()); - // TODO: Set this flag after successfull script injection. Maybe upon injection - // the chooser should update the meta tag and we check it to declare success - mAccessibilityScriptInjected = true; - } else { - // injection disabled so we fallback to the basic built-in support - ensureAccessibilityScriptInjectorInstance(true); - } - } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) { - // injection opted out so we fallback to the basic buil-in support - ensureAccessibilityScriptInjectorInstance(true); - } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) { - ensureAccessibilityScriptInjectorInstance(false); - // the URL provides accessibility but we still need to add our generic script - mWebView.loadUrl(getScreenReaderInjectingJs()); - } else { - Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue); + if (isAccessibilityEnabled()) { + getAccessibilityInjector().onPageFinished(url); } } - /** - * Ensures the instance of the {@link AccessibilityInjector} to be present ot not. - * - * @param present True to ensure an insance, false to ensure no instance. - */ - private void ensureAccessibilityScriptInjectorInstance(boolean present) { - if (present) { - if (mAccessibilityInjector == null) { - mAccessibilityInjector = new AccessibilityInjector(this); - } - } else { - mAccessibilityInjector = null; - } - } - - /** - * Gets JavaScript that injects a screen-reader. - * - * @return The JavaScript snippet. - */ - private String getScreenReaderInjectingJs() { - String screenReaderUrl = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL); - return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl); - } - - /** - * Gets the "axs" URL parameter value. - * - * @param url A url to fetch the paramter from. - * @return The parameter value if such, -1 otherwise. - */ - private int getAxsUrlParameterValue(String url) { - if (mMatchAxsUrlParameterPattern == null) { - mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER); - } - Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url); - if (matcher.find()) { - String keyValuePair = url.substring(matcher.start(), matcher.end()); - return Integer.parseInt(keyValuePair.split("=")[1]); - } - return -1; - } - // scale from content to view coordinates, and pin private void contentScrollTo(int cx, int cy, boolean animate) { if (mDrawHistory) { @@ -4158,15 +4128,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (mTouchMode == TOUCH_DRAG_MODE) { if (mHeldMotionless == MOTIONLESS_PENDING) { mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); - mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); mHeldMotionless = MOTIONLESS_FALSE; } if (mHeldMotionless == MOTIONLESS_FALSE) { mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME); - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(AWAKEN_SCROLL_BARS), - ViewConfiguration.getScrollDefaultDelay()); mHeldMotionless = MOTIONLESS_PENDING; } } @@ -4437,8 +4403,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (mNativeClass == 0) return; boolean queueFull; + final int scrollingLayer = (mTouchMode == TOUCH_DRAG_LAYER_MODE) + ? mCurrentScrollingLayerId : 0; queueFull = nativeSetBaseLayer(mNativeClass, layer, - showVisualIndicator, isPictureAfterFirstLayout); + showVisualIndicator, isPictureAfterFirstLayout, + scrollingLayer); if (queueFull) { mWebViewCore.pauseWebKitDraw(); @@ -4909,30 +4878,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return false; } - // accessibility support - if (accessibilityScriptInjected()) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - // if an accessibility script is injected we delegate to it the key handling. - // this script is a screen reader which is a fully fledged solution for blind - // users to navigate in and interact with web pages. - sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event); - return true; - } else { - // Clean up if accessibility was disabled after loading the current URL. - mAccessibilityScriptInjected = false; - } - } else if (mAccessibilityInjector != null) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - if (mAccessibilityInjector.onKeyEvent(event)) { - // if an accessibility injector is present (no JavaScript enabled or the site - // opts out injecting our JavaScript screen reader) we let it decide whether - // to act on and consume the event. - return true; - } - } else { - // Clean up if accessibility was disabled after loading the current URL. - mAccessibilityInjector = null; - } + // See if the accessibility injector needs to handle this event. + if (isAccessibilityEnabled() + && getAccessibilityInjector().handleKeyEventIfNecessary(event)) { + return true; } if (keyCode == KeyEvent.KEYCODE_PAGE_UP) { @@ -5036,30 +4985,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return false; } - // accessibility support - if (accessibilityScriptInjected()) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - // if an accessibility script is injected we delegate to it the key handling. - // this script is a screen reader which is a fully fledged solution for blind - // users to navigate in and interact with web pages. - sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event); - return true; - } else { - // Clean up if accessibility was disabled after loading the current URL. - mAccessibilityScriptInjected = false; - } - } else if (mAccessibilityInjector != null) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - if (mAccessibilityInjector.onKeyEvent(event)) { - // if an accessibility injector is present (no JavaScript enabled or the site - // opts out injecting our JavaScript screen reader) we let it decide whether to - // act on and consume the event. - return true; - } - } else { - // Clean up if accessibility was disabled after loading the current URL. - mAccessibilityInjector = null; - } + // See if the accessibility injector needs to handle this event. + if (isAccessibilityEnabled() + && getAccessibilityInjector().handleKeyEventIfNecessary(event)) { + return true; } if (isEnterActionKey(keyCode)) { @@ -5175,6 +5104,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private void adjustSelectionCursors() { if (mIsCaretSelection) { + syncSelectionCursors(); return; // no need to swap left and right handles. } @@ -5360,7 +5290,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc public void onAttachedToWindow() { if (mWebView.hasWindowFocus()) setActive(true); - addAccessibilityApisToJavaScript(); + if (isAccessibilityEnabled()) { + getAccessibilityInjector().addAccessibilityApisIfNecessary(); + } updateHwAccelerated(); } @@ -5371,7 +5303,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mZoomManager.dismissZoomPicker(); if (mWebView.hasWindowFocus()) setActive(false); - removeAccessibilityApisFromJavaScript(); + if (isAccessibilityEnabled()) { + getAccessibilityInjector().removeAccessibilityApisIfNecessary(); + } else { + // Ensure the injector is cleared if we're detaching from the window + // and accessibility is disabled. + mAccessibilityInjector = null; + } + updateHwAccelerated(); if (mWebView.isHardwareAccelerated()) { @@ -5583,6 +5522,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return; // no need to scroll } syncSelectionCursors(); + nativeFindMaxVisibleRect(mNativeClass, mEditTextLayerId, visibleRect); final int buffer = Math.max(1, viewToContentDimension(EDIT_RECT_BUFFER)); Rect showRect = new Rect( Math.max(0, mEditTextContentBounds.left - buffer), @@ -5615,17 +5555,19 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return; // no need to scroll } - int scrollX = visibleRect.left; + int scrollX = viewToContentX(getScrollX()); if (visibleRect.left > showRect.left) { - scrollX = showRect.left; + // We are scrolled too far + scrollX += showRect.left - visibleRect.left; } else if (visibleRect.right < showRect.right) { - scrollX = Math.max(0, showRect.right - visibleRect.width()); + // We aren't scrolled enough to include the right + scrollX += showRect.right - visibleRect.right; } - int scrollY = visibleRect.top; + int scrollY = viewToContentY(getScrollY()); if (visibleRect.top > showRect.top) { - scrollY = showRect.top; + scrollY += showRect.top - visibleRect.top; } else if (visibleRect.bottom < showRect.bottom) { - scrollY = Math.max(0, showRect.bottom - visibleRect.height()); + scrollY += showRect.bottom - visibleRect.bottom; } contentScrollTo(scrollX, scrollY, false); @@ -5767,14 +5709,15 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return false; } - if (!mWebView.isFocused()) { - mWebView.requestFocus(); - } - if (mInputDispatcher == null) { return false; } + if (mWebView.isFocusable() && mWebView.isFocusableInTouchMode() + && !mWebView.isFocused()) { + mWebView.requestFocus(); + } + if (mInputDispatcher.postPointerEvent(ev, getScrollX(), getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) { mInputDispatcher.dispatchUiEvents(); @@ -6048,27 +5991,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } - // Turn off scrollbars when dragging a layer. - if (keepScrollBarsVisible && - mTouchMode != TOUCH_DRAG_LAYER_MODE && - mTouchMode != TOUCH_DRAG_TEXT_MODE) { - if (mHeldMotionless != MOTIONLESS_TRUE) { - mHeldMotionless = MOTIONLESS_TRUE; - invalidate(); - } - // keep the scrollbar on the screen even there is no scroll - mWebViewPrivate.awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(), - false); - // Post a message so that we'll keep them alive while we're not scrolling. - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(AWAKEN_SCROLL_BARS), - ViewConfiguration.getScrollDefaultDelay()); - // return false to indicate that we can't pan out of the - // view space - return; - } else { - mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); - } break; } case MotionEvent.ACTION_UP: { @@ -6116,7 +6038,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc case TOUCH_DRAG_LAYER_MODE: case TOUCH_DRAG_TEXT_MODE: mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); - mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); // if the user waits a while w/o moving before the // up, we don't want to do a fling if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { @@ -6382,7 +6303,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); - mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); removeTouchHighlight(); mHeldMotionless = MOTIONLESS_TRUE; mTouchMode = TOUCH_DONE_MODE; @@ -6814,17 +6734,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // no horizontal overscroll if the content just fits mScroller.fling(scrollX, scrollY, -vx, -vy, 0, maxX, 0, maxY, maxX == 0 ? 0 : overflingDistance, overflingDistance); - // Duration is calculated based on velocity. With range boundaries and overscroll - // we may not know how long the final animation will take. (Hence the deprecation - // warning on the call below.) It's not a big deal for scroll bars but if webcore - // resumes during this effect we will take a performance hit. See computeScroll; - // we resume webcore there when the animation is finished. - final int time = mScroller.getDuration(); - - // Suppress scrollbars for layer scrolling. - if (mTouchMode != TOUCH_DRAG_LAYER_MODE && mTouchMode != TOUCH_DRAG_TEXT_MODE) { - mWebViewPrivate.awakenScrollBars(time); - } invalidate(); } @@ -7362,17 +7271,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc case DRAG_HELD_MOTIONLESS: mHeldMotionless = MOTIONLESS_TRUE; invalidate(); - // fall through to keep scrollbars awake - - case AWAKEN_SCROLL_BARS: - if (mTouchMode == TOUCH_DRAG_MODE - && mHeldMotionless == MOTIONLESS_TRUE) { - mWebViewPrivate.awakenScrollBars(ViewConfiguration - .getScrollDefaultDelay(), false); - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(AWAKEN_SCROLL_BARS), - ViewConfiguration.getScrollDefaultDelay()); - } break; case SCREEN_ON: @@ -7467,9 +7365,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc break; case SELECTION_STRING_CHANGED: - if (mAccessibilityInjector != null) { - String selectionString = (String) msg.obj; - mAccessibilityInjector.onSelectionStringChange(selectionString); + if (isAccessibilityEnabled()) { + getAccessibilityInjector() + .handleSelectionChangedIfNecessary((String) msg.obj); } break; @@ -7538,6 +7436,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc int cursorPosition = start + text.length(); replaceTextfieldText(start, end, text, cursorPosition, cursorPosition); + selectionDone(); break; } @@ -7564,7 +7463,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } case CLEAR_CARET_HANDLE: - selectionDone(); + if (mIsCaretSelection) { + selectionDone(); + } break; case KEY_PRESS: @@ -7652,6 +7553,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc invalidate(); } } + + @Override + public void clearPreviousHitTest() { + setHitTestResult(null); + } } private void setHitTestTypeFromUrl(String url) { @@ -7969,7 +7875,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc nativeSetTextSelection(mNativeClass, data.mSelectTextPtr); if ((data.mSelectionReason == TextSelectionData.REASON_ACCESSIBILITY_INJECTOR) - || (!mSelectingText + || (!mSelectingText && data.mStart != data.mEnd && data.mSelectionReason != TextSelectionData.REASON_SELECT_WORD)) { selectionDone(); mShowTextSelectionExtra = true; @@ -8025,7 +7931,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mIsBatchingTextChanges = false; } - private void sendBatchableInputMessage(int what, int arg1, int arg2, + void sendBatchableInputMessage(int what, int arg1, int arg2, Object obj) { if (mWebViewCore == null) { return; @@ -8446,16 +8352,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * @return Whether accessibility script has been injected. - */ - private boolean accessibilityScriptInjected() { - // TODO: Maybe the injected script should announce its presence in - // the page meta-tag so the nativePageShouldHandleShiftAndArrows - // will check that as one of the conditions it looks for - return mAccessibilityScriptInjected; - } - - /** * See {@link WebView#setBackgroundColor(int)} */ @Override @@ -8601,6 +8497,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mWebView.postInvalidate(); } + // Note: must be called before first WebViewClassic is created. + public static void setShouldMonitorWebCoreThread() { + WebViewCore.setShouldMonitorWebCoreThread(); + } + private native void nativeCreate(int ptr, String drawableDir, boolean isHighEndGfx); private native void nativeDebugDump(); private native void nativeDestroy(); @@ -8617,7 +8518,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private native String nativeGetSelection(); private native void nativeSetHeightCanMeasure(boolean measure); private native boolean nativeSetBaseLayer(int nativeInstance, - int layer, boolean showVisualIndicator, boolean isPictureAfterFirstLayout); + int layer, boolean showVisualIndicator, boolean isPictureAfterFirstLayout, + int scrollingLayer); private native int nativeGetBaseLayer(int nativeInstance); private native void nativeCopyBaseContentToPicture(Picture pict); private native boolean nativeHasContent(); @@ -8661,4 +8563,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc Rect rect); // Returns 1 if a layer sync is needed, else 0 private static native int nativeSetHwAccelerated(int instance, boolean hwAccelerated); + private static native void nativeFindMaxVisibleRect(int instance, int layerId, + Rect visibleContentRect); } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 76cd1c9..1164649 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -144,6 +144,11 @@ public final class WebViewCore { private int mChromeCanFocusDirection; private int mTextSelectionChangeReason = TextSelectionData.REASON_UNKNOWN; + // Used to determine if we should monitor the WebCore thread for responsiveness. + // If it "hangs", for example a web page enters a while(true) loop, we will + // prompt the user with a dialog allowing them to terminate the process. + private static boolean sShouldMonitorWebCoreThread; + // The thread name used to identify the WebCore thread and for use in // debugging other classes that require operation within the WebCore thread. /* package */ static final String THREAD_NAME = "WebViewCoreThread"; @@ -176,10 +181,16 @@ public final class WebViewCore { Log.e(LOGTAG, Log.getStackTraceString(e)); } - // Start the singleton watchdog which will monitor the WebCore thread - // to verify it's still processing messages. - WebCoreThreadWatchdog.start(context, sWebCoreHandler); + if (sShouldMonitorWebCoreThread) { + // Start the singleton watchdog which will monitor the WebCore thread + // to verify it's still processing messages. Note that this is the only + // time we need to check the value as all the other public methods on + // the WebCoreThreadWatchdog are no-ops if start() is not called. + WebCoreThreadWatchdog.start(sWebCoreHandler); + } } + // Make sure the Watchdog is aware of this new WebView. + WebCoreThreadWatchdog.registerWebView(w); } // Create an EventHub to handle messages before and after the thread is // ready. @@ -658,7 +669,7 @@ public final class WebViewCore { int x, int y); private native boolean nativeMouseClick(int nativeClass); - private native boolean nativeHandleTouchEvent(int nativeClass, int action, + private native int nativeHandleTouchEvent(int nativeClass, int action, int[] idArray, int[] xArray, int[] yArray, int count, int actionIndex, int metaState); @@ -957,6 +968,9 @@ public final class WebViewCore { static final int ACTION_LONGPRESS = 0x100; static final int ACTION_DOUBLETAP = 0x200; + private static final int TOUCH_FLAG_HIT_HANDLER = 0x1; + private static final int TOUCH_FLAG_PREVENT_DEFAULT = 0x2; + static class TouchEventData { int mAction; int[] mIds; // Ids of the touch points @@ -1773,7 +1787,8 @@ public final class WebViewCore { } @Override - public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) { + public boolean dispatchWebKitEvent(WebViewInputDispatcher dispatcher, + MotionEvent event, int eventType, int flags) { if (mNativeClass == 0) { return false; } @@ -1800,10 +1815,16 @@ public final class WebViewCore { xArray[i] = (int) event.getX(i); yArray[i] = (int) event.getY(i); } - return nativeHandleTouchEvent(mNativeClass, + int touchFlags = nativeHandleTouchEvent(mNativeClass, event.getActionMasked(), idArray, xArray, yArray, count, event.getActionIndex(), event.getMetaState()); + if (touchFlags == 0 + && event.getActionMasked() != MotionEvent.ACTION_CANCEL + && (flags & WebViewInputDispatcher.FLAG_PRIVATE) == 0) { + dispatcher.skipWebkitForRemainingTouchStream(); + } + return (touchFlags & TOUCH_FLAG_PREVENT_DEFAULT) > 0; } default: @@ -1979,6 +2000,7 @@ public final class WebViewCore { mEventHub.sendMessageAtFrontOfQueue( Message.obtain(null, EventHub.DESTROY)); mEventHub.blockMessages(); + WebCoreThreadWatchdog.unregisterWebView(mWebViewClassic); } } @@ -3058,6 +3080,10 @@ public final class WebViewCore { return mDeviceOrientationService; } + static void setShouldMonitorWebCoreThread() { + sShouldMonitorWebCoreThread = true; + } + private native void nativeSetIsPaused(int nativeClass, boolean isPaused); private native void nativePause(int nativeClass); private native void nativeResume(int nativeClass); diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java index 9eeb311..d8065e9 100644 --- a/core/java/android/webkit/WebViewInputDispatcher.java +++ b/core/java/android/webkit/WebViewInputDispatcher.java @@ -399,7 +399,6 @@ final class WebViewInputDispatcher { unscheduleHideTapHighlightLocked(); unscheduleShowTapHighlightLocked(); mUiCallbacks.showTapHighlight(true); - scheduleHideTapHighlightLocked(); } private void scheduleShowTapHighlightLocked() { @@ -466,13 +465,13 @@ final class WebViewInputDispatcher { return; } mPostClickScheduled = false; - showTapCandidateLocked(); MotionEvent event = mPostTouchStream.getLastEvent(); if (event == null || event.getAction() != MotionEvent.ACTION_UP) { return; } + showTapCandidateLocked(); MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event); DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0, mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale); @@ -511,6 +510,7 @@ final class WebViewInputDispatcher { } private void enqueueHitTestLocked(MotionEvent event) { + mUiCallbacks.clearPreviousHitTest(); MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event); DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_HIT_TEST, 0, mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale); @@ -553,12 +553,17 @@ final class WebViewInputDispatcher { mIsTapCandidate = true; mInitialDownX = event.getX(); mInitialDownY = event.getY(); - scheduleShowTapHighlightLocked(); enqueueHitTestLocked(event); + if (mIsDoubleTapCandidate) { + hideTapCandidateLocked(); + } else { + scheduleShowTapHighlightLocked(); + } } else if (action == MotionEvent.ACTION_UP) { unscheduleLongPressLocked(); if (isClickCandidateLocked(event)) { if (mIsDoubleTapCandidate) { + hideTapCandidateLocked(); enqueueDoubleTapLocked(event); } else { scheduleClickLocked(); @@ -666,6 +671,10 @@ final class WebViewInputDispatcher { if (event != null && recycleEvent) { event.recycle(); } + + if (eventType == EVENT_TYPE_CLICK) { + scheduleHideTapHighlightLocked(); + } } } } @@ -677,7 +686,7 @@ final class WebViewInputDispatcher { + ", eventType=" + eventType + ", flags=" + flags); } boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent( - event, eventType, flags); + this, event, eventType, flags); if (DEBUG) { Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault); } @@ -701,6 +710,12 @@ final class WebViewInputDispatcher { mWebKitDispatchEventQueue.mHead = d; } + // Called by WebKit when it doesn't care about the rest of the touch stream + public void skipWebkitForRemainingTouchStream() { + // Just treat this like a timeout + handleWebKitTimeout(); + } + // Runs on UI thread in response to the web kit thread appearing to be unresponsive. private void handleWebKitTimeout() { synchronized (mLock) { @@ -802,6 +817,10 @@ final class WebViewInputDispatcher { d.mEvent = null; // retain ownership of event, don't recycle it yet } recycleDispatchEventLocked(d); + + if (eventType == EVENT_TYPE_CLICK) { + scheduleHideTapHighlightLocked(); + } } // Handle the event. @@ -822,21 +841,31 @@ final class WebViewInputDispatcher { } private void enqueueEventLocked(DispatchEvent d) { - if (!shouldSkipWebKit(d.mEventType)) { + if (!shouldSkipWebKit(d)) { enqueueWebKitEventLocked(d); } else { enqueueUiEventLocked(d); } } - private boolean shouldSkipWebKit(int eventType) { - switch (eventType) { + private boolean shouldSkipWebKit(DispatchEvent d) { + switch (d.mEventType) { case EVENT_TYPE_CLICK: case EVENT_TYPE_HOVER: case EVENT_TYPE_SCROLL: case EVENT_TYPE_HIT_TEST: return false; case EVENT_TYPE_TOUCH: + // TODO: This should be cleaned up. We now have WebViewInputDispatcher + // and WebViewClassic both checking for slop and doing their own + // thing - they should be consolidated. And by consolidated, I mean + // WebViewClassic's version should just be deleted. + // The reason this is done is because webpages seem to expect + // that they only get an ontouchmove if the slop has been exceeded. + if (mIsTapCandidate && d.mEvent != null + && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE) { + return true; + } return !mPostSendTouchEventsToWebKit || mPostDoNotSendTouchEventsToWebKitUntilNextGesture; } @@ -1040,6 +1069,12 @@ final class WebViewInputDispatcher { * @param show True if it should show the highlight, false if it should hide it */ public void showTapHighlight(boolean show); + + /** + * Called when we are sending a new EVENT_TYPE_HIT_TEST to WebKit, so + * previous hit tests should be cleared as they are obsolete. + */ + public void clearPreviousHitTest(); } /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */ @@ -1052,12 +1087,14 @@ final class WebViewInputDispatcher { /** * Dispatches an event to web kit. + * @param dispatcher The WebViewInputDispatcher sending the event * @param event The event. * @param eventType The event type. * @param flags The event's dispatch flags. * @return True if web kit wants to prevent default event handling. */ - public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags); + public boolean dispatchWebKitEvent(WebViewInputDispatcher dispatcher, + MotionEvent event, int eventType, int flags); } // Runs on UI thread. diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index 74a215c..867ee54 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -276,6 +276,8 @@ public interface WebViewProvider { public void onInitializeAccessibilityEvent(AccessibilityEvent event); + public boolean performAccessibilityAction(int action, Bundle arguments); + public void setOverScrollMode(int mode); public void setScrollBarStyle(int style); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 04c8cdc..9abe72b 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -579,6 +579,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private InputConnectionWrapper mPublicInputConnection; private Runnable mClearScrollingCache; + Runnable mPositionScrollAfterLayout; private int mMinimumVelocity; private int mMaximumVelocity; private float mVelocityScale = 1.0f; @@ -1352,24 +1353,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te case ACCESSIBILITY_FOCUS_FORWARD: { ViewRootImpl viewRootImpl = getViewRootImpl(); if (viewRootImpl == null) { - break; + return null; } View currentFocus = viewRootImpl.getAccessibilityFocusedHost(); if (currentFocus == null) { - break; + return super.focusSearch(this, direction); } // If we have the focus try giving it to the first child. if (currentFocus == this) { - final int firstVisiblePosition = getFirstVisiblePosition(); - if (firstVisiblePosition >= 0) { + if (getChildCount() > 0) { return getChildAt(0); } - return null; + return super.focusSearch(this, direction); } // Find the item that has accessibility focus. final int currentPosition = getPositionForView(currentFocus); if (currentPosition < 0 || currentPosition >= getCount()) { - break; + return super.focusSearch(this, direction); } // Try to advance focus in the current item. View currentItem = getChildAt(currentPosition - getFirstVisiblePosition()); @@ -1386,25 +1386,31 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int nextPosition = currentPosition - getFirstVisiblePosition() + 1; if (nextPosition < getChildCount()) { return getChildAt(nextPosition); + } else { + return super.focusSearch(this, direction); } - } break; + } case ACCESSIBILITY_FOCUS_BACKWARD: { ViewRootImpl viewRootImpl = getViewRootImpl(); if (viewRootImpl == null) { - break; + return null; } View currentFocus = viewRootImpl.getAccessibilityFocusedHost(); if (currentFocus == null) { - break; + return super.focusSearch(this, direction); } // If we have the focus do a generic search. if (currentFocus == this) { + final int lastChildIndex = getChildCount() - 1; + if (lastChildIndex >= 0) { + return getChildAt(lastChildIndex); + } return super.focusSearch(this, direction); } // Find the item that has accessibility focus. final int currentPosition = getPositionForView(currentFocus); if (currentPosition < 0 || currentPosition >= getCount()) { - break; + return super.focusSearch(this, direction); } // Try to advance focus in the current item. View currentItem = getChildAt(currentPosition - getFirstVisiblePosition()); @@ -1422,7 +1428,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (nextPosition >= 0) { return getChildAt(nextPosition); } else { - return this; + return super.focusSearch(this, direction); } } } @@ -1451,7 +1457,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int lastVisiblePosition = getLastVisiblePosition(); if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) { - return; + return; } else { mLastAccessibilityScrollEventFromIndex = firstVisiblePosition; mLastAccessibilityScrollEventToIndex = lastVisiblePosition; @@ -1470,11 +1476,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(AbsListView.class.getName()); - if (getFirstVisiblePosition() > 0) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); - } - if (getLastVisiblePosition() < getCount() - 1) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + if (isEnabled()) { + if (getFirstVisiblePosition() > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (getLastVisiblePosition() < getCount() - 1) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } } } @@ -1485,14 +1493,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } switch (action) { case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { - if (getLastVisiblePosition() < getCount() - 1) { + if (isEnabled() && getLastVisiblePosition() < getCount() - 1) { final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION); return true; } } return false; case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { - if (mFirstPosition > 0) { + if (isEnabled() && mFirstPosition > 0) { final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION); return true; @@ -1903,6 +1911,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te removeAllViewsInLayout(); mFirstPosition = 0; mDataChanged = false; + mPositionScrollAfterLayout = null; mNeedSync = false; mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; @@ -2214,31 +2223,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te View child; if (scrapView != null) { - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP, - position, -1); - } - child = mAdapter.getView(position, scrapView, this); if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW, - position, getChildCount()); - } - if (child != scrapView) { mRecycler.addScrapView(scrapView, position); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, - position, -1); - } } else { isScrap[0] = true; child.dispatchFinishTemporaryDetach(); @@ -2253,10 +2248,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW, - position, getChildCount()); - } } if (mAdapterHasStableIds) { @@ -2289,11 +2280,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te super.onInitializeAccessibilityNodeInfo(host, info); final int position = getPositionForView(host); + final ListAdapter adapter = getAdapter(); - if (position == INVALID_POSITION) { + if ((position == INVALID_POSITION) || (adapter == null)) { + // Cannot perform actions on invalid items. + info.setEnabled(false); return; } + if (!isEnabled() || !adapter.isEnabled(position)) { + // Cannot perform actions on invalid items. + info.setEnabled(false); + return; + } + + if (position == getSelectedItemPosition()) { + info.setSelected(true); + info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION); + } else { + info.addAction(AccessibilityNodeInfo.ACTION_SELECT); + } + if (isClickable()) { info.addAction(AccessibilityNodeInfo.ACTION_CLICK); info.setClickable(true); @@ -2304,43 +2311,55 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te info.setLongClickable(true); } - info.addAction(AccessibilityNodeInfo.ACTION_SELECT); - - if (position == getSelectedItemPosition()) { - info.setSelected(true); - } } @Override public boolean performAccessibilityAction(View host, int action, Bundle arguments) { + if (super.performAccessibilityAction(host, action, arguments)) { + return true; + } + final int position = getPositionForView(host); + final ListAdapter adapter = getAdapter(); - if (position == INVALID_POSITION) { + if ((position == INVALID_POSITION) || (adapter == null)) { + // Cannot perform actions on invalid items. + return false; + } + + if (!isEnabled() || !adapter.isEnabled(position)) { + // Cannot perform actions on disabled items. return false; } final long id = getItemIdAtPosition(position); switch (action) { - case AccessibilityNodeInfo.ACTION_SELECT: - setSelection(position); - return true; - case AccessibilityNodeInfo.ACTION_CLICK: - if (!super.performAccessibilityAction(host, action, arguments)) { + case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: { + if (getSelectedItemPosition() == position) { + setSelection(INVALID_POSITION); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_SELECT: { + if (getSelectedItemPosition() != position) { + setSelection(position); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_CLICK: { + if (isClickable()) { return performItemClick(host, position, id); } - return true; - case AccessibilityNodeInfo.ACTION_LONG_CLICK: - if (!super.performAccessibilityAction(host, action, arguments)) { + } return false; + case AccessibilityNodeInfo.ACTION_LONG_CLICK: { + if (isLongClickable()) { return performLongPress(host, position, id); } - return true; - case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: - smoothScrollToPosition(position); - break; + } return false; } - return super.performAccessibilityAction(host, action, arguments); + return false; } } @@ -4231,11 +4250,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mDataChanged) { // Wait until we're back in a stable state to try this. - post(new Runnable() { + mPositionScrollAfterLayout = new Runnable() { @Override public void run() { start(position); } - }); + }; return; } @@ -4282,11 +4301,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mDataChanged) { // Wait until we're back in a stable state to try this. - post(new Runnable() { + mPositionScrollAfterLayout = new Runnable() { @Override public void run() { start(position, boundPosition); } - }); + }; return; } @@ -4359,11 +4378,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mDataChanged) { // Wait until we're back in a stable state to try this. final int postOffset = offset; - post(new Runnable() { + mPositionScrollAfterLayout = new Runnable() { @Override public void run() { startWithOffset(position, postOffset, duration); } - }); + }; return; } @@ -4929,12 +4948,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { mRecycler.addScrapView(child, position); - - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, - ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, - firstPosition + i, -1); - } } } } @@ -4953,12 +4966,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { mRecycler.addScrapView(child, position); - - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, - ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, - firstPosition + i, -1); - } } } } @@ -5902,63 +5909,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te removeAllViewsInLayout(); } - /** - * @hide - */ - @Override - protected boolean onConsistencyCheck(int consistency) { - boolean result = super.onConsistencyCheck(consistency); - - final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0; - - if (checkLayout) { - // The active recycler must be empty - final View[] activeViews = mRecycler.mActiveViews; - int count = activeViews.length; - for (int i = 0; i < count; i++) { - if (activeViews[i] != null) { - result = false; - Log.d(ViewDebug.CONSISTENCY_LOG_TAG, - "AbsListView " + this + " has a view in its active recycler: " + - activeViews[i]); - } - } - - // All views in the recycler must NOT be on screen and must NOT have a parent - final ArrayList<View> scrap = mRecycler.mCurrentScrap; - if (!checkScrap(scrap)) result = false; - final ArrayList<View>[] scraps = mRecycler.mScrapViews; - count = scraps.length; - for (int i = 0; i < count; i++) { - if (!checkScrap(scraps[i])) result = false; - } - } - - return result; - } - - private boolean checkScrap(ArrayList<View> scrap) { - if (scrap == null) return true; - boolean result = true; - - final int count = scrap.size(); - for (int i = 0; i < count; i++) { - final View view = scrap.get(i); - if (view.getParent() != null) { - result = false; - Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this + - " has a view in its scrap heap still attached to a parent: " + view); - } - if (indexOfChild(view) >= 0) { - result = false; - Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this + - " has a view in its scrap heap that is also a direct child: " + view); - } - } - - return result; - } - private void finishGlows() { if (mEdgeGlowTop != null) { mEdgeGlowTop.finish(); @@ -6514,12 +6464,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (hasListener) { mRecyclerListener.onMovedToScrapHeap(victim); } - - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(victim, - ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP, - mFirstActivePosition + i, -1); - } } } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index ae68794..e217e4f 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -21,6 +21,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; @@ -486,5 +487,46 @@ public abstract class AbsSeekBar extends ProgressBar { public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(AbsSeekBar.class.getName()); + + if (isEnabled()) { + final int progress = getProgress(); + if (progress > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (progress < getMax()) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + } + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (super.performAccessibilityAction(action, arguments)) { + return true; + } + if (!isEnabled()) { + return false; + } + final int progress = getProgress(); + final int increment = Math.max(1, Math.round((float) getMax() / 5)); + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + if (progress <= 0) { + return false; + } + setProgress(progress - increment, true); + onKeyChange(); + return true; + } + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + if (progress >= getMax()) { + return false; + } + setProgress(progress + increment, true); + onKeyChange(); + return true; + } + } + return false; } } diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java index be6b4e2..4eb169b 100644 --- a/core/java/android/widget/ActivityChooserView.java +++ b/core/java/android/widget/ActivityChooserView.java @@ -400,6 +400,9 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod if (viewTreeObserver.isAlive()) { viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener); } + if (isShowingPopup()) { + dismissPopup(); + } mIsAttachedToWindow = false; } @@ -420,9 +423,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { mActivityChooserContent.layout(0, 0, right - left, bottom - top); - if (getListPopupWindow().isShowing()) { - showPopupUnchecked(mAdapter.getMaxActivityCount()); - } else { + if (!isShowingPopup()) { dismissPopup(); } } diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index b2c8164..e4e7239 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -56,7 +56,12 @@ import com.android.internal.R; * @attr ref android.R.styleable#Gallery_animationDuration * @attr ref android.R.styleable#Gallery_spacing * @attr ref android.R.styleable#Gallery_gravity + * + * @deprecated This widget is no longer supported. Other horizontally scrolling + * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager} + * from the support library. */ +@Deprecated @Widget public class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener { @@ -1369,11 +1374,13 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList super.onInitializeAccessibilityNodeInfo(info); info.setClassName(Gallery.class.getName()); info.setScrollable(mItemCount > 1); - if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); - } - if (mItemCount > 0 && mSelectedPosition > 0) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + if (isEnabled()) { + if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } } } @@ -1384,13 +1391,13 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList } switch (action) { case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { - if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) { + if (isEnabled() && mItemCount > 0 && mSelectedPosition < mItemCount - 1) { final int currentChildIndex = mSelectedPosition - mFirstPosition; return scrollToChild(currentChildIndex + 1); } } return false; case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { - if (mItemCount > 0 && mSelectedPosition > 0) { + if (isEnabled() && mItemCount > 0 && mSelectedPosition > 0) { final int currentChildIndex = mSelectedPosition - mFirstPosition; return scrollToChild(currentChildIndex - 1); } diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 60a1d15..772d748 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -294,8 +294,32 @@ public class GridLayout extends ViewGroup { } /** - * Orientation is used only to generate default row/column indices when - * they are not specified by a component's layout parameters. + * + * GridLayout uses the orientation property for two purposes: + * <ul> + * <li> + * To control the 'direction' in which default row/column indices are generated + * when they are not specified in a component's layout parameters. + * </li> + * <li> + * To control which axis should be processed first during the layout operation: + * when orientation is {@link #HORIZONTAL} the horizontal axis is laid out first. + * </li> + * </ul> + * + * The order in which axes are laid out is important if, for example, the height of + * one of GridLayout's children is dependent on its width - and its width is, in turn, + * dependent on the widths of other components. + * <p> + * If your layout contains a {@link TextView} (or derivative: + * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is + * in multi-line mode (the default) it is normally best to leave GridLayout's + * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of + * deriving its height for a given width, but not the other way around. + * <p> + * Other than the effects above, orientation does not affect the actual layout operation of + * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if + * the height of the intended layout greatly exceeds its width. * <p> * The default value of this property is {@link #HORIZONTAL}. * @@ -1373,6 +1397,7 @@ public class GridLayout extends ViewGroup { break; } case PENDING: { + // le singe est dans l'arbre assert false; break; } diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 0a40d5e..8975109 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -1275,6 +1275,10 @@ public class GridView extends AbsListView { mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; + if (mPositionScrollAfterLayout != null) { + post(mPositionScrollAfterLayout); + mPositionScrollAfterLayout = null; + } mNeedSync = false; setNextSelectedPositionInt(mSelectedPosition); diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index f889cb7..8c6ce19 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -744,6 +744,9 @@ public class HorizontalScrollView extends FrameLayout { } switch (action) { case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + if (!isEnabled()) { + return false; + } final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight; final int targetScrollX = Math.min(mScrollX + viewportWidth, getScrollRange()); if (targetScrollX != mScrollX) { @@ -752,6 +755,9 @@ public class HorizontalScrollView extends FrameLayout { } } return false; case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + if (!isEnabled()) { + return false; + } final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight; final int targetScrollX = Math.max(0, mScrollX - viewportWidth); if (targetScrollX != mScrollX) { @@ -770,10 +776,10 @@ public class HorizontalScrollView extends FrameLayout { final int scrollRange = getScrollRange(); if (scrollRange > 0) { info.setScrollable(true); - if (mScrollX > 0) { + if (isEnabled() && mScrollX > 0) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); } - if (mScrollX < scrollRange) { + if (isEnabled() && mScrollX < scrollRange) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); } } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 5098523..d2e55d9 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1557,10 +1557,6 @@ public class ListView extends AbsListView { if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(getChildAt(i), - ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i); - } } } else { recycleBin.fillActiveViews(childCount, firstPosition); @@ -1695,6 +1691,10 @@ public class ListView extends AbsListView { mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; + if (mPositionScrollAfterLayout != null) { + post(mPositionScrollAfterLayout); + mPositionScrollAfterLayout = null; + } mNeedSync = false; setNextSelectedPositionInt(mSelectedPosition); @@ -1757,11 +1757,6 @@ public class ListView extends AbsListView { // Try to use an existing view for this position child = mRecycler.getActiveView(position); if (child != null) { - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP, - position, getChildCount()); - } - // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 515f0c4..b60ffc5 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -2173,13 +2173,15 @@ public class NumberPicker extends LinearLayout { return false; } case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { - if (getWrapSelectorWheel() || getValue() < getMaxValue()) { + if (NumberPicker.this.isEnabled() + && (getWrapSelectorWheel() || getValue() < getMaxValue())) { changeValueByOne(true); return true; } } return false; case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { - if (getWrapSelectorWheel() || getValue() > getMinValue()) { + if (NumberPicker.this.isEnabled() + && (getWrapSelectorWheel() || getValue() > getMinValue())) { changeValueByOne(false); return true; } @@ -2189,20 +2191,23 @@ public class NumberPicker extends LinearLayout { case VIRTUAL_VIEW_ID_INPUT: { switch (action) { case AccessibilityNodeInfo.ACTION_FOCUS: { - if (!mInputText.isFocused()) { + if (NumberPicker.this.isEnabled() && !mInputText.isFocused()) { return mInputText.requestFocus(); } } break; case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: { - if (mInputText.isFocused()) { + if (NumberPicker.this.isEnabled() && mInputText.isFocused()) { mInputText.clearFocus(); return true; } return false; } case AccessibilityNodeInfo.ACTION_CLICK: { - showSoftInput(); - return true; + if (NumberPicker.this.isEnabled()) { + showSoftInput(); + return true; + } + return false; } case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: { if (mAccessibilityFocusedView != virtualViewId) { @@ -2230,10 +2235,13 @@ public class NumberPicker extends LinearLayout { case VIRTUAL_VIEW_ID_INCREMENT: { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { - NumberPicker.this.changeValueByOne(true); - sendAccessibilityEventForVirtualView(virtualViewId, - AccessibilityEvent.TYPE_VIEW_CLICKED); - } return true; + if (NumberPicker.this.isEnabled()) { + NumberPicker.this.changeValueByOne(true); + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_CLICKED); + return true; + } + } return false; case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: { if (mAccessibilityFocusedView != virtualViewId) { mAccessibilityFocusedView = virtualViewId; @@ -2257,11 +2265,14 @@ public class NumberPicker extends LinearLayout { case VIRTUAL_VIEW_ID_DECREMENT: { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { - final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT); - NumberPicker.this.changeValueByOne(increment); - sendAccessibilityEventForVirtualView(virtualViewId, - AccessibilityEvent.TYPE_VIEW_CLICKED); - } return true; + if (NumberPicker.this.isEnabled()) { + final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT); + NumberPicker.this.changeValueByOne(increment); + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_CLICKED); + return true; + } + } return false; case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: { if (mAccessibilityFocusedView != virtualViewId) { mAccessibilityFocusedView = virtualViewId; @@ -2470,7 +2481,9 @@ public class NumberPicker extends LinearLayout { if (mAccessibilityFocusedView == virtualViewId) { info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } - info.addAction(AccessibilityNodeInfo.ACTION_CLICK); + if (NumberPicker.this.isEnabled()) { + info.addAction(AccessibilityNodeInfo.ACTION_CLICK); + } return info; } @@ -2509,11 +2522,13 @@ public class NumberPicker extends LinearLayout { if (mAccessibilityFocusedView == View.NO_ID) { info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } - if (getWrapSelectorWheel() || getValue() < getMaxValue()) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); - } - if (getWrapSelectorWheel() || getValue() > getMinValue()) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + if (NumberPicker.this.isEnabled()) { + if (getWrapSelectorWheel() || getValue() < getMaxValue()) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + if (getWrapSelectorWheel() || getValue() > getMinValue()) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } } return info; diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 5fa4ad0..f442912 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1253,13 +1253,13 @@ public class PopupWindow { unregisterForScrollChanged(); try { - mWindowManager.removeView(mPopupView); + mWindowManager.removeViewImmediate(mPopupView); } finally { if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { ((ViewGroup) mPopupView).removeView(mContentView); } mPopupView = null; - + if (mOnDismissListener != null) { mOnDismissListener.onDismiss(); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 56c4bd8..1985792 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -37,6 +37,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.LayoutInflater.Filter; import android.view.RemotableViewMethod; @@ -1182,6 +1183,87 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Helper action to set text size on a TextView in any supported units. + */ + private class TextViewSizeAction extends Action { + public TextViewSizeAction(int viewId, int units, float size) { + this.viewId = viewId; + this.units = units; + this.size = size; + } + + public TextViewSizeAction(Parcel parcel) { + viewId = parcel.readInt(); + units = parcel.readInt(); + size = parcel.readFloat(); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + dest.writeInt(units); + dest.writeFloat(size); + } + + @Override + public void apply(View root, ViewGroup rootParent) { + final Context context = root.getContext(); + final TextView target = (TextView) root.findViewById(viewId); + if (target == null) return; + target.setTextSize(units, size); + } + + int viewId; + int units; + float size; + + public final static int TAG = 13; + } + + /** + * Helper action to set padding on a View. + */ + private class ViewPaddingAction extends Action { + public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) { + this.viewId = viewId; + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + + public ViewPaddingAction(Parcel parcel) { + viewId = parcel.readInt(); + left = parcel.readInt(); + top = parcel.readInt(); + right = parcel.readInt(); + bottom = parcel.readInt(); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + dest.writeInt(left); + dest.writeInt(top); + dest.writeInt(right); + dest.writeInt(bottom); + } + + @Override + public void apply(View root, ViewGroup rootParent) { + final Context context = root.getContext(); + final View target = root.findViewById(viewId); + if (target == null) return; + target.setPadding(left, top, right, bottom); + } + + int viewId; + int left, top, right, bottom; + + public final static int TAG = 14; + } + + /** * Simple class used to keep track of memory usage in a RemoteViews. * */ @@ -1334,6 +1416,12 @@ public class RemoteViews implements Parcelable, Filter { case TextViewDrawableAction.TAG: mActions.add(new TextViewDrawableAction(parcel)); break; + case TextViewSizeAction.TAG: + mActions.add(new TextViewSizeAction(parcel)); + break; + case ViewPaddingAction.TAG: + mActions.add(new ViewPaddingAction(parcel)); + break; case BitmapReflectionAction.TAG: mActions.add(new BitmapReflectionAction(parcel)); break; @@ -1445,7 +1533,8 @@ public class RemoteViews implements Parcelable, Filter { /** * Returns an estimate of the bitmap heap memory usage for this RemoteViews. */ - int estimateMemoryUsage() { + /** @hide */ + public int estimateMemoryUsage() { return mMemoryUsageCounter.getMemoryUsage(); } @@ -1540,7 +1629,19 @@ public class RemoteViews implements Parcelable, Filter { public void setTextViewText(int viewId, CharSequence text) { setCharSequence(viewId, "setText", text); } - + + /** + * @hide + * Equivalent to calling {@link TextView#setTextSize(int, float)} + * + * @param viewId The id of the view whose text size should change + * @param units The units of size (e.g. COMPLEX_UNIT_SP) + * @param size The size of the text + */ + public void setTextViewTextSize(int viewId, int units, float size) { + addAction(new TextViewSizeAction(viewId, units, size)); + } + /** * Equivalent to calling * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. @@ -1798,6 +1899,20 @@ public class RemoteViews implements Parcelable, Filter { } /** + * @hide + * Equivalent to calling {@link View#setPadding(int, int, int, int)}. + * + * @param viewId The id of the view to change + * @param left the left padding in pixels + * @param top the top padding in pixels + * @param right the right padding in pixels + * @param bottom the bottom padding in pixels + */ + public void setViewPadding(int viewId, int left, int top, int right, int bottom) { + addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); + } + + /** * Call a method taking one boolean on a view in the layout for this RemoteViews. * * @param viewId The id of the view on which to call the method. diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index f266d50..46ec923 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -614,7 +614,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback maxDistIndexNonRequested = i; maxDistNonRequested = dist; } - if (dist > maxDist) { + if (dist >= maxDist) { // maxDist/maxDistIndex will store the index of the farthest position // regardless of whether it was directly requested or not maxDistIndex = i; diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index a499743..2a20c56 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -745,6 +745,9 @@ public class ScrollView extends FrameLayout { if (super.performAccessibilityAction(action, arguments)) { return true; } + if (!isEnabled()) { + return false; + } switch (action) { case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop; @@ -770,14 +773,16 @@ public class ScrollView extends FrameLayout { public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(ScrollView.class.getName()); - final int scrollRange = getScrollRange(); - if (scrollRange > 0) { - info.setScrollable(true); - if (mScrollY > 0) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); - } - if (mScrollY < scrollRange) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + if (isEnabled()) { + final int scrollRange = getScrollRange(); + if (scrollRange > 0) { + info.setScrollable(true); + if (mScrollY > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (mScrollY < scrollRange) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } } } } diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 521597b..8f3a311 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -511,7 +511,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { public void setQuery(CharSequence query, boolean submit) { mQueryTextView.setText(query); if (query != null) { - mQueryTextView.setSelection(query.length()); + mQueryTextView.setSelection(mQueryTextView.length()); mUserQuery = query; } diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index dd0915b..293eda1 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -1230,11 +1230,13 @@ public class StackView extends AdapterViewAnimator { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(StackView.class.getName()); info.setScrollable(getChildCount() > 1); - if (getDisplayedChild() < getChildCount() - 1) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); - } - if (getDisplayedChild() > 0) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + if (isEnabled()) { + if (getDisplayedChild() < getChildCount() - 1) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + if (getDisplayedChild() > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } } } @@ -1243,6 +1245,9 @@ public class StackView extends AdapterViewAnimator { if (super.performAccessibilityAction(action, arguments)) { return true; } + if (!isEnabled()) { + return false; + } switch (action) { case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { if (getDisplayedChild() < getChildCount() - 1) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fc56e11..bd19f00 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -39,6 +39,7 @@ import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import android.provider.Settings; import android.text.BoringLayout; import android.text.DynamicLayout; import android.text.Editable; @@ -5625,6 +5626,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener physicalWidth, false); } + /** @hide */ @Override public void onResolvedLayoutDirectionReset() { if (mLayoutAlignment != null) { @@ -7676,7 +7678,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); if (subtype != null) { - locale = new Locale(subtype.getLocale()); + locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale()); } return locale; } @@ -7704,14 +7706,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener super.onPopulateAccessibilityEvent(event); final boolean isPassword = hasPasswordTransformationMethod(); - if (!isPassword) { - CharSequence text = getTextForAccessibility(); + if (!isPassword || shouldSpeakPasswordsForAccessibility()) { + final CharSequence text = getTextForAccessibility(); if (!TextUtils.isEmpty(text)) { event.getText().add(text); } } } + /** + * @return true if the user has explicitly allowed accessibility services + * to speak passwords. + */ + private boolean shouldSpeakPasswordsForAccessibility() { + return (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1); + } + @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); @@ -7739,8 +7750,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener info.setText(getTextForAccessibility()); } - if (TextUtils.isEmpty(getContentDescription()) - && !TextUtils.isEmpty(mText)) { + if (TextUtils.isEmpty(getContentDescription()) && !TextUtils.isEmpty(mText)) { info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER @@ -8162,6 +8172,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mEditor.mInBatchEditControllers; } + /** @hide */ @Override public void onResolvedTextDirectionChanged() { if (hasPasswordTransformationMethod()) { diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 88d7e05..fafc113 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -120,7 +120,12 @@ public class Toast { */ public void cancel() { mTN.hide(); - // TODO this still needs to cancel the inflight notification if any + + try { + getService().cancelToast(mContext.getPackageName(), mTN); + } catch (RemoteException e) { + // Empty + } } /** |
