summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java8
-rw-r--r--core/java/android/accounts/ChooseTypeAndAccountActivity.java86
-rw-r--r--core/java/android/animation/LayoutTransition.java9
-rw-r--r--core/java/android/app/Activity.java5
-rw-r--r--core/java/android/app/ActivityManager.java39
-rw-r--r--core/java/android/app/ActivityManagerNative.java22
-rw-r--r--core/java/android/app/ActivityThread.java6
-rw-r--r--core/java/android/app/IActivityManager.java5
-rw-r--r--core/java/android/app/Notification.java102
-rw-r--r--core/java/android/app/TaskStackBuilder.java12
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java8
-rw-r--r--core/java/android/content/Intent.java25
-rw-r--r--core/java/android/content/pm/PackageManager.java11
-rw-r--r--core/java/android/content/res/Configuration.java166
-rwxr-xr-xcore/java/android/content/res/Resources.java15
-rw-r--r--core/java/android/hardware/SensorManager.java7
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl9
-rwxr-xr-xcore/java/android/hardware/input/InputManager.java147
-rw-r--r--core/java/android/hardware/input/KeyboardLayout.java26
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java35
-rw-r--r--core/java/android/net/NetworkStats.java8
-rw-r--r--core/java/android/net/NetworkStatsHistory.java18
-rw-r--r--core/java/android/net/NetworkTemplate.java11
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java7
-rw-r--r--core/java/android/nfc/NfcActivityManager.java18
-rw-r--r--core/java/android/nfc/NfcAdapter.java137
-rw-r--r--core/java/android/os/AsyncTask.java40
-rw-r--r--core/java/android/os/Environment.java14
-rw-r--r--core/java/android/os/Looper.java20
-rw-r--r--core/java/android/os/Trace.java2
-rw-r--r--core/java/android/os/storage/IMountService.java9
-rw-r--r--core/java/android/preference/DialogPreference.java2
-rw-r--r--core/java/android/provider/Settings.java9
-rwxr-xr-xcore/java/android/server/BluetoothService.java4
-rw-r--r--core/java/android/speech/RecognizerIntent.java39
-rw-r--r--core/java/android/view/AccessibilityIterators.java6
-rw-r--r--core/java/android/view/Choreographer.java319
-rw-r--r--core/java/android/view/GestureDetector.java8
-rw-r--r--core/java/android/view/HardwareRenderer.java28
-rw-r--r--core/java/android/view/MenuInflater.java29
-rw-r--r--core/java/android/view/TextureView.java18
-rw-r--r--core/java/android/view/View.java214
-rw-r--r--core/java/android/view/ViewConfiguration.java23
-rw-r--r--core/java/android/view/ViewDebug.java660
-rw-r--r--core/java/android/view/ViewGroup.java115
-rw-r--r--core/java/android/view/ViewRootImpl.java227
-rw-r--r--core/java/android/view/WindowManager.java7
-rw-r--r--core/java/android/view/WindowManagerPolicy.java6
-rw-r--r--core/java/android/view/animation/Animation.java79
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java20
-rw-r--r--core/java/android/webkit/AccessibilityInjector.java819
-rw-r--r--core/java/android/webkit/AccessibilityInjectorFallback.java575
-rw-r--r--core/java/android/webkit/SelectActionModeCallback.java4
-rw-r--r--core/java/android/webkit/WebCoreThreadWatchdog.java153
-rw-r--r--core/java/android/webkit/WebView.java9
-rw-r--r--core/java/android/webkit/WebViewClassic.java543
-rw-r--r--core/java/android/webkit/WebViewCore.java5
-rw-r--r--core/java/android/webkit/WebViewInputDispatcher.java41
-rw-r--r--core/java/android/webkit/WebViewProvider.java2
-rw-r--r--core/java/android/widget/AbsListView.java220
-rw-r--r--core/java/android/widget/AbsSeekBar.java42
-rw-r--r--core/java/android/widget/ActivityChooserView.java7
-rw-r--r--core/java/android/widget/Gallery.java21
-rw-r--r--core/java/android/widget/GridLayout.java29
-rw-r--r--core/java/android/widget/GridView.java4
-rw-r--r--core/java/android/widget/HorizontalScrollView.java10
-rw-r--r--core/java/android/widget/ListView.java13
-rw-r--r--core/java/android/widget/NumberPicker.java57
-rw-r--r--core/java/android/widget/PopupWindow.java4
-rw-r--r--core/java/android/widget/RemoteViews.java119
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java2
-rw-r--r--core/java/android/widget/ScrollView.java21
-rw-r--r--core/java/android/widget/StackView.java15
-rw-r--r--core/java/android/widget/TextView.java19
-rw-r--r--core/java/android/widget/Toast.java7
75 files changed, 3293 insertions, 2288 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> &lt;service android:name=".MyAccessibilityService"&gt;
+ * <pre> &lt;service android:name=".MyAccessibilityService"
+ * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
* &lt;/intent-filter&gt;
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/Notification.java b/core/java/android/app/Notification.java
index 0c47069..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;
@@ -907,6 +910,8 @@ public class Notification implements Parcelable
* </pre>
*/
public static class Builder {
+ private static final int MAX_ACTION_BUTTONS = 2;
+
private Context mContext;
private long mWhen;
@@ -938,7 +943,7 @@ public class Notification implements Parcelable
private ArrayList<String> mKindList = new ArrayList<String>(1);
private Bundle mExtras;
private int mPriority;
- private ArrayList<Action> mActions = new ArrayList<Action>(3);
+ private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
private boolean mUseChronometer;
private Style mStyle;
@@ -1376,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);
@@ -1399,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);
@@ -1419,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);
@@ -1449,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;
}
@@ -1460,7 +1480,7 @@ public class Notification implements Parcelable
if (N > 0) {
// Log.d("Notification", "has actions: " + mContentText);
big.setViewVisibility(R.id.actions, View.VISIBLE);
- if (N>3) N=3;
+ if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
big.removeAllViews(R.id.actions);
for (int i=0; i<N; i++) {
final RemoteViews button = generateActionButton(mActions.get(i));
@@ -1500,18 +1520,14 @@ public class Notification implements Parcelable
}
private RemoteViews generateActionButton(Action action) {
- RemoteViews button = new RemoteViews(mContext.getPackageName(), R.layout.notification_action);
+ final boolean tombstone = (action.actionIntent == null);
+ RemoteViews button = new RemoteViews(mContext.getPackageName(),
+ tombstone ? R.layout.notification_action_tombstone
+ : R.layout.notification_action);
button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0);
button.setTextViewText(R.id.action0, action.title);
- if (action.actionIntent != null) {
+ if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
- //button.setBoolean(R.id.action0, "setEnabled", true);
- button.setFloat(R.id.button0, "setAlpha", 1.0f);
- button.setBoolean(R.id.button0, "setClickable", true);
- } else {
- //button.setBoolean(R.id.action0, "setEnabled", false);
- button.setFloat(R.id.button0, "setAlpha", 0.5f);
- button.setBoolean(R.id.button0, "setClickable", false);
}
button.setContentDescription(R.id.action0, action.title);
return button;
@@ -1631,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;
@@ -1675,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() {
}
@@ -1705,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);
@@ -1717,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;
}
@@ -1846,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) {
@@ -1858,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>
- * &lt;receiver android:name=".InputDeviceReceiver">
+ * &lt;receiver android:name=".InputDeviceReceiver"
+ * android:label="@string/keyboard_layouts_label">
* &lt;intent-filter>
* &lt;action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" />
* &lt;/intent-filter>
@@ -90,7 +93,9 @@ public final class InputManager {
* an XML resource whose root element is <code>&lt;keyboard-layouts></code> that
* contains zero or more <code>&lt;keyboard-layout></code> elements.
* Each <code>&lt;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>
* &lt;?xml version="1.0" encoding="utf-8"?>
* &lt;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/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/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..20eef11 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);
@@ -9868,10 +9902,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 +9945,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 +9997,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;
}
@@ -12347,10 +12369,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 +12488,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 +12725,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 +12739,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 +13103,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 +13175,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 +13518,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 +14773,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 +14885,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 +14935,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..c9a41ad 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) {
@@ -2971,20 +2895,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 +3097,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 +3544,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 +4066,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 +4104,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 +4157,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 +4303,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/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/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..9f8771c 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,114 +3914,10 @@ 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);
- }
- }
-
- /**
- * 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);
+ if (isAccessibilityEnabled()) {
+ getAccessibilityInjector().onPageFinished(url);
}
- 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
@@ -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;
}
}
@@ -4909,30 +4875,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 +4982,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 +5101,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 +5287,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 +5300,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 +5519,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 +5552,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 +5706,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 +5988,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 +6035,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 +6300,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 +6731,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 +7268,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 +7362,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 +7433,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
int cursorPosition = start + text.length();
replaceTextfieldText(start, end, text,
cursorPosition, cursorPosition);
+ selectionDone();
break;
}
@@ -7564,7 +7460,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
case CLEAR_CARET_HANDLE:
- selectionDone();
+ if (mIsCaretSelection) {
+ selectionDone();
+ }
break;
case KEY_PRESS:
@@ -7652,6 +7550,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
invalidate();
}
}
+
+ @Override
+ public void clearPreviousHitTest() {
+ setHitTestResult(null);
+ }
}
private void setHitTestTypeFromUrl(String url) {
@@ -7969,7 +7872,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 +7928,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 +8349,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
@@ -8661,4 +8554,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..af7914e 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -178,8 +178,10 @@ public final class WebViewCore {
// Start the singleton watchdog which will monitor the WebCore thread
// to verify it's still processing messages.
- WebCoreThreadWatchdog.start(context, sWebCoreHandler);
+ 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.
@@ -1979,6 +1981,7 @@ public final class WebViewCore {
mEventHub.sendMessageAtFrontOfQueue(
Message.obtain(null, EventHub.DESTROY));
mEventHub.blockMessages();
+ WebCoreThreadWatchdog.unregisterWebView(mWebViewClassic);
}
}
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
index 9eeb311..feff16e 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();
+ }
}
}
}
@@ -802,6 +811,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 +835,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 +1063,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. */
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/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..81a44fd 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) {
@@ -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
+ }
}
/**