Merge "Fix accessibility actions in AbsListView." into jb-dev
diff --git a/api/16.txt b/api/16.txt
index 2c02347..6aab939 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -9243,7 +9243,6 @@
method public int getMinimumWidth();
method public abstract int getOpacity();
method public boolean getPadding(android.graphics.Rect);
- method public int getResolvedLayoutDirectionSelf();
method public int[] getState();
method public android.graphics.Region getTransparentRegion();
method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -24513,14 +24512,14 @@
public class ViewDebug {
ctor public ViewDebug();
method public static void dumpCapturedView(java.lang.String, java.lang.Object);
- method public static void startHierarchyTracing(java.lang.String, android.view.View);
- method public static void startRecyclerTracing(java.lang.String, android.view.View);
- method public static void stopHierarchyTracing();
- method public static void stopRecyclerTracing();
- method public static void trace(android.view.View, android.view.ViewDebug.RecyclerTraceType, int...);
- method public static void trace(android.view.View, android.view.ViewDebug.HierarchyTraceType);
- field public static final boolean TRACE_HIERARCHY = false;
- field public static final boolean TRACE_RECYCLER = false;
+ method public static deprecated void startHierarchyTracing(java.lang.String, android.view.View);
+ method public static deprecated void startRecyclerTracing(java.lang.String, android.view.View);
+ method public static deprecated void stopHierarchyTracing();
+ method public static deprecated void stopRecyclerTracing();
+ method public static deprecated void trace(android.view.View, android.view.ViewDebug.RecyclerTraceType, int...);
+ method public static deprecated void trace(android.view.View, android.view.ViewDebug.HierarchyTraceType);
+ field public static final deprecated boolean TRACE_HIERARCHY = false;
+ field public static final deprecated boolean TRACE_RECYCLER = false;
}
public static abstract class ViewDebug.CapturedViewProperty implements java.lang.annotation.Annotation {
@@ -24532,7 +24531,7 @@
public static abstract class ViewDebug.FlagToString implements java.lang.annotation.Annotation {
}
- public static final class ViewDebug.HierarchyTraceType extends java.lang.Enum {
+ public static final deprecated class ViewDebug.HierarchyTraceType extends java.lang.Enum {
method public static android.view.ViewDebug.HierarchyTraceType valueOf(java.lang.String);
method public static final android.view.ViewDebug.HierarchyTraceType[] values();
enum_constant public static final android.view.ViewDebug.HierarchyTraceType BUILD_CACHE;
@@ -24548,7 +24547,7 @@
public static abstract class ViewDebug.IntToString implements java.lang.annotation.Annotation {
}
- public static final class ViewDebug.RecyclerTraceType extends java.lang.Enum {
+ public static final deprecated class ViewDebug.RecyclerTraceType extends java.lang.Enum {
method public static android.view.ViewDebug.RecyclerTraceType valueOf(java.lang.String);
method public static final android.view.ViewDebug.RecyclerTraceType[] values();
enum_constant public static final android.view.ViewDebug.RecyclerTraceType BIND_VIEW;
@@ -27329,7 +27328,7 @@
field public int gravity;
}
- public class Gallery extends android.widget.AbsSpinner implements android.view.GestureDetector.OnGestureListener {
+ public deprecated class Gallery extends android.widget.AbsSpinner implements android.view.GestureDetector.OnGestureListener {
ctor public Gallery(android.content.Context);
ctor public Gallery(android.content.Context, android.util.AttributeSet);
ctor public Gallery(android.content.Context, android.util.AttributeSet, int);
@@ -28620,8 +28619,6 @@
method public void onEndBatchEdit();
method public boolean onPreDraw();
method public boolean onPrivateIMECommand(java.lang.String, android.os.Bundle);
- method public void onResolvedLayoutDirectionReset();
- method public void onResolvedTextDirectionChanged();
method public void onRestoreInstanceState(android.os.Parcelable);
method public android.os.Parcelable onSaveInstanceState();
method protected void onSelectionChanged(int, int);
diff --git a/api/current.txt b/api/current.txt
index 2c02347..6aab939 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9243,7 +9243,6 @@
method public int getMinimumWidth();
method public abstract int getOpacity();
method public boolean getPadding(android.graphics.Rect);
- method public int getResolvedLayoutDirectionSelf();
method public int[] getState();
method public android.graphics.Region getTransparentRegion();
method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -24513,14 +24512,14 @@
public class ViewDebug {
ctor public ViewDebug();
method public static void dumpCapturedView(java.lang.String, java.lang.Object);
- method public static void startHierarchyTracing(java.lang.String, android.view.View);
- method public static void startRecyclerTracing(java.lang.String, android.view.View);
- method public static void stopHierarchyTracing();
- method public static void stopRecyclerTracing();
- method public static void trace(android.view.View, android.view.ViewDebug.RecyclerTraceType, int...);
- method public static void trace(android.view.View, android.view.ViewDebug.HierarchyTraceType);
- field public static final boolean TRACE_HIERARCHY = false;
- field public static final boolean TRACE_RECYCLER = false;
+ method public static deprecated void startHierarchyTracing(java.lang.String, android.view.View);
+ method public static deprecated void startRecyclerTracing(java.lang.String, android.view.View);
+ method public static deprecated void stopHierarchyTracing();
+ method public static deprecated void stopRecyclerTracing();
+ method public static deprecated void trace(android.view.View, android.view.ViewDebug.RecyclerTraceType, int...);
+ method public static deprecated void trace(android.view.View, android.view.ViewDebug.HierarchyTraceType);
+ field public static final deprecated boolean TRACE_HIERARCHY = false;
+ field public static final deprecated boolean TRACE_RECYCLER = false;
}
public static abstract class ViewDebug.CapturedViewProperty implements java.lang.annotation.Annotation {
@@ -24532,7 +24531,7 @@
public static abstract class ViewDebug.FlagToString implements java.lang.annotation.Annotation {
}
- public static final class ViewDebug.HierarchyTraceType extends java.lang.Enum {
+ public static final deprecated class ViewDebug.HierarchyTraceType extends java.lang.Enum {
method public static android.view.ViewDebug.HierarchyTraceType valueOf(java.lang.String);
method public static final android.view.ViewDebug.HierarchyTraceType[] values();
enum_constant public static final android.view.ViewDebug.HierarchyTraceType BUILD_CACHE;
@@ -24548,7 +24547,7 @@
public static abstract class ViewDebug.IntToString implements java.lang.annotation.Annotation {
}
- public static final class ViewDebug.RecyclerTraceType extends java.lang.Enum {
+ public static final deprecated class ViewDebug.RecyclerTraceType extends java.lang.Enum {
method public static android.view.ViewDebug.RecyclerTraceType valueOf(java.lang.String);
method public static final android.view.ViewDebug.RecyclerTraceType[] values();
enum_constant public static final android.view.ViewDebug.RecyclerTraceType BIND_VIEW;
@@ -27329,7 +27328,7 @@
field public int gravity;
}
- public class Gallery extends android.widget.AbsSpinner implements android.view.GestureDetector.OnGestureListener {
+ public deprecated class Gallery extends android.widget.AbsSpinner implements android.view.GestureDetector.OnGestureListener {
ctor public Gallery(android.content.Context);
ctor public Gallery(android.content.Context, android.util.AttributeSet);
ctor public Gallery(android.content.Context, android.util.AttributeSet, int);
@@ -28620,8 +28619,6 @@
method public void onEndBatchEdit();
method public boolean onPreDraw();
method public boolean onPrivateIMECommand(java.lang.String, android.os.Bundle);
- method public void onResolvedLayoutDirectionReset();
- method public void onResolvedTextDirectionChanged();
method public void onRestoreInstanceState(android.os.Parcelable);
method public android.os.Parcelable onSaveInstanceState();
method protected void onSelectionChanged(int, int);
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 8cd8900..cb53422 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -645,10 +645,6 @@
String process = null;
String cmd = nextArgRequired();
- if ("looper".equals(cmd)) {
- cmd = nextArgRequired();
- profileType = 1;
- }
if ("start".equals(cmd)) {
start = true;
@@ -1295,8 +1291,8 @@
" am broadcast <INTENT>\n" +
" am instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]\n" +
" [--no-window-animation] <COMPONENT>\n" +
- " am profile [looper] start <PROCESS> <FILE>\n" +
- " am profile [looper] stop [<PROCESS>]\n" +
+ " am profile start <PROCESS> <FILE>\n" +
+ " am profile stop [<PROCESS>]\n" +
" am dumpheap [flags] <PROCESS> <FILE>\n" +
" am set-debug-app [-w] [--persistent] <PACKAGE>\n" +
" am clear-debug-app\n" +
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 @@
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 @@
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.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.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 @@
}
}
+ /** @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 @@
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 @@
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 @@
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 @@
}
} 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 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 @@
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 2eea171..c7afd2b2 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1850,7 +1850,7 @@
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) {
@@ -1867,6 +1867,12 @@
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/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 @@
* 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/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 @@
// 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 dfd35e1..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;
@@ -217,6 +219,41 @@
}
/**
+ * 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.
*/
@@ -332,50 +369,129 @@
}
/**
- * 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, or null to remove
- * the mapping so that the default keyboard layout will be used for the input device.
+ * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
*
* @hide
*/
- public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ 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 {
- mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+ return mIm.getKeyboardLayoutsForInputDevice(inputDeviceDescriptor);
} catch (RemoteException ex) {
- Log.w(TAG, "Could not set keyboard layout for input device.", 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 descriptor of the keyboard layout to add.
+ *
+ * @hide
+ */
+ 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.removeKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not remove keyboard layout for input device.", ex);
}
}
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 @@
* 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 @@
* 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 @@
* <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 @@
* 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 @@
*
* @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 @@
*
* @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 @@
/**
* 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/Looper.java b/core/java/android/os/Looper.java
index a06aadb6..02135bc 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -127,29 +127,17 @@
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 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/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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 @@
private final Object[] mActionProviderConstructorArguments;
private Context mContext;
+ private Object mRealOwner;
/**
* Constructs a menu inflater.
@@ -73,6 +74,20 @@
*/
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 @@
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 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 @@
+ "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 411aed3..a719a01 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -374,6 +374,14 @@
// 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());
}
@@ -471,7 +479,7 @@
}
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 6d60797..832d575 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4325,7 +4325,6 @@
if (gainFocus) {
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- requestAccessibilityFocus();
}
}
@@ -6183,8 +6182,6 @@
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;
@@ -6215,26 +6212,19 @@
// 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();
- }
- }
}
}
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);
}
}
}
@@ -9912,10 +9902,6 @@
* @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;
}
@@ -9959,10 +9945,6 @@
* @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;
}
@@ -10015,10 +9997,6 @@
* 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;
}
@@ -12391,10 +12369,6 @@
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;
@@ -12514,9 +12488,6 @@
// 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 {
@@ -13130,9 +13101,6 @@
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 {
@@ -13205,10 +13173,6 @@
* @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);
@@ -13552,10 +13516,6 @@
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;
@@ -14811,60 +14771,6 @@
}
/**
- * @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}.
*
@@ -14977,10 +14883,6 @@
* tree.
*/
public void requestLayout() {
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
- }
-
mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
@@ -15031,10 +14933,6 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * @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.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.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 @@
*/
public class ViewDebug {
/**
- * Log tag used to log errors related to the consistency of the view hierarchy.
- *
- * @hide
+ * @deprecated This flag is now unused
*/
- 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
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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
+ * @deprecated This method is now unused and invoking it is a no-op
*/
- 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
+ @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 @@
}
}
- 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 @@
}
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..421109f 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3949,10 +3949,6 @@
* 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 +4041,6 @@
* 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 +4623,6 @@
}
/**
- * @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 d9e3545..c9a41ad 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -55,7 +55,6 @@
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 @@
boolean mTraversalScheduled;
int mTraversalBarrier;
- long mLastTraversalFinishedTimeNanos;
- long mLastDrawFinishedTimeNanos;
boolean mWillDrawSoon;
boolean mFitSystemWindowsRequested;
boolean mLayoutRequested;
@@ -987,18 +984,6 @@
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 @@
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;
@@ -1865,28 +1842,12 @@
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
- final long startTime;
- if (ViewDebug.DEBUG_PROFILE_LAYOUT) {
- startTime = SystemClock.elapsedRealtime();
- }
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
-
- if (ViewDebug.DEBUG_PROFILE_LAYOUT) {
- EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime);
- }
-
- 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.");
- }
- }
}
public void requestTransparentRegion(View child) {
@@ -2030,18 +1991,6 @@
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;
@@ -2054,14 +2003,6 @@
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;
@@ -2225,19 +2166,8 @@
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;
@@ -2257,7 +2187,7 @@
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.
@@ -2272,11 +2202,6 @@
//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
@@ -2309,46 +2234,23 @@
? 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) {
@@ -2993,20 +2895,6 @@
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();
- }
}
}
}
@@ -3209,10 +3097,6 @@
}
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) {
@@ -3660,9 +3544,6 @@
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) {
@@ -4185,11 +4066,6 @@
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,
@@ -4228,12 +4104,6 @@
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
@@ -4287,42 +4157,6 @@
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 {
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 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/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 @@
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/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 31824f3..6cee0f3 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1352,24 +1352,23 @@
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 +1385,31 @@
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 +1427,7 @@
if (nextPosition >= 0) {
return getChildAt(nextPosition);
} else {
- return this;
+ return super.focusSearch(this, direction);
}
}
}
@@ -2216,31 +2221,17 @@
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();
@@ -2255,10 +2246,6 @@
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
- if (ViewDebug.TRACE_RECYCLER) {
- ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
- position, getChildCount());
- }
}
if (mAdapterHasStableIds) {
@@ -4959,12 +4946,6 @@
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);
- }
}
}
}
@@ -4983,12 +4964,6 @@
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);
- }
}
}
}
@@ -5932,63 +5907,6 @@
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();
@@ -6544,12 +6462,6 @@
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/Gallery.java b/core/java/android/widget/Gallery.java
index 6658fc2..e4e7239 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -56,7 +56,12 @@
* @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 {
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 @@
}
/**
- * 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 @@
break;
}
case PENDING: {
+ // le singe est dans l'arbre
assert false;
break;
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 5098523..c62b62b 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1557,10 +1557,6 @@
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);
@@ -1757,11 +1753,6 @@
// 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/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 51c957a..1985792 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -37,6 +37,7 @@
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 @@
}
/**
+ * 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 @@
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;
@@ -1541,7 +1629,19 @@
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)}.
@@ -1799,6 +1899,20 @@
}
/**
+ * @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/TextView.java b/core/java/android/widget/TextView.java
index 112881c..81a44fd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -39,6 +39,7 @@
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 @@
physicalWidth, false);
}
+ /** @hide */
@Override
public void onResolvedLayoutDirectionReset() {
if (mLayoutAlignment != null) {
@@ -7704,14 +7706,23 @@
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);
@@ -8161,6 +8172,7 @@
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 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
+ }
}
/**
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 614f73f..7334ac3 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -20,6 +20,7 @@
import com.android.internal.content.PackageMonitor;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -34,6 +35,9 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.PatternMatcher;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserId;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -61,6 +65,7 @@
public class ResolverActivity extends AlertActivity implements AdapterView.OnItemClickListener {
private static final String TAG = "ResolverActivity";
+ private int mLaunchedFromUid;
private ResolveListAdapter mAdapter;
private PackageManager mPm;
private boolean mAlwaysUseOption;
@@ -102,6 +107,12 @@
boolean alwaysUseOption) {
setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert);
super.onCreate(savedInstanceState);
+ try {
+ mLaunchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid(
+ getActivityToken());
+ } catch (RemoteException e) {
+ mLaunchedFromUid = -1;
+ }
mPm = getPackageManager();
mAlwaysUseOption = alwaysUseOption;
mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns);
@@ -118,9 +129,14 @@
mIconDpi = am.getLauncherLargeIconDensity();
mIconSize = am.getLauncherLargeIconSize();
- mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList);
+ mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList,
+ mLaunchedFromUid);
int count = mAdapter.getCount();
- if (count > 1) {
+ if (mLaunchedFromUid < 0 || UserId.isIsolated(mLaunchedFromUid)) {
+ // Gulp!
+ finish();
+ return;
+ } else if (count > 1) {
ap.mView = getLayoutInflater().inflate(R.layout.resolver_grid, null);
mGrid = (GridView) ap.mView.findViewById(R.id.resolver_grid);
mGrid.setAdapter(mAdapter);
@@ -146,9 +162,13 @@
if (alwaysUseOption) {
final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
- buttonLayout.setVisibility(View.VISIBLE);
- mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
- mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
+ if (buttonLayout != null) {
+ buttonLayout.setVisibility(View.VISIBLE);
+ mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
+ mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
+ } else {
+ mAlwaysUseOption = false;
+ }
}
}
@@ -207,6 +227,18 @@
mPackageMonitor.unregister();
mRegistered = false;
}
+ if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ // This resolver is in the unusual situation where it has been
+ // launched at the top of a new task. We don't let it be added
+ // to the recent tasks shown to the user, and we need to make sure
+ // that each time we are launched we get the correct launching
+ // uid (not re-using the same resolver from an old launching uid),
+ // so we will now finish ourself since being no longer visible,
+ // the user probably can't get back to us.
+ if (!isChangingConfigurations()) {
+ finish();
+ }
+ }
}
@Override
@@ -363,17 +395,19 @@
private final Intent[] mInitialIntents;
private final List<ResolveInfo> mBaseResolveList;
private final Intent mIntent;
+ private final int mLaunchedFromUid;
private final LayoutInflater mInflater;
private List<ResolveInfo> mCurrentResolveList;
private List<DisplayResolveInfo> mList;
public ResolveListAdapter(Context context, Intent intent,
- Intent[] initialIntents, List<ResolveInfo> rList) {
+ Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid) {
mIntent = new Intent(intent);
mIntent.setComponent(null);
mInitialIntents = initialIntents;
mBaseResolveList = rList;
+ mLaunchedFromUid = launchedFromUid;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rebuildList();
}
@@ -400,6 +434,23 @@
mCurrentResolveList = mPm.queryIntentActivities(
mIntent, PackageManager.MATCH_DEFAULT_ONLY
| (mAlwaysUseOption ? PackageManager.GET_RESOLVED_FILTER : 0));
+ // Filter out any activities that the launched uid does not
+ // have permission for. We don't do this when we have an explicit
+ // list of resolved activities, because that only happens when
+ // we are being subclassed, so we can safely launch whatever
+ // they gave us.
+ if (mCurrentResolveList != null) {
+ for (int i=mCurrentResolveList.size()-1; i >= 0; i--) {
+ ActivityInfo ai = mCurrentResolveList.get(i).activityInfo;
+ int granted = ActivityManager.checkComponentPermission(
+ ai.permission, mLaunchedFromUid,
+ ai.applicationInfo.uid, ai.exported);
+ if (granted != PackageManager.PERMISSION_GRANTED) {
+ // Access not allowed!
+ mCurrentResolveList.remove(i);
+ }
+ }
+ }
}
int N;
if ((mCurrentResolveList != null) && ((N = mCurrentResolveList.size()) > 0)) {
diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
index 6136206..d6fb847 100644
--- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
@@ -67,6 +67,7 @@
public void onReleased(View v, int handle);
public void onTrigger(View v, int target);
public void onGrabbedStateChange(View v, int handle);
+ public void onFinishFinalAnimation();
}
// Tuneable parameters for animation
@@ -77,9 +78,13 @@
private static final int HIDE_ANIMATION_DELAY = 200;
private static final int HIDE_ANIMATION_DURATION = 200;
private static final int SHOW_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DELAY = 0;
+ private static final int SHOW_ANIMATION_DELAY = 50;
private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
- private static final float TARGET_INITIAL_POSITION_SCALE = 0.8f;
+ private static final float TARGET_SCALE_SELECTED = 0.8f;
+ private static final long INITIAL_SHOW_HANDLE_DURATION = 200;
+ private static final float TARGET_SCALE_UNSELECTED = 1.0f;
+ private static final float RING_SCALE_UNSELECTED = 0.5f;
+ private static final float RING_SCALE_SELECTED = 1.5f;
private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut;
@@ -126,6 +131,15 @@
}
}
+ public void cancel() {
+ final int count = size();
+ for (int i = 0; i < count; i++) {
+ Tweener anim = get(i);
+ anim.animator.cancel();
+ }
+ clear();
+ }
+
public void stop() {
final int count = size();
for (int i = 0; i < count; i++) {
@@ -143,6 +157,7 @@
private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animator) {
switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
+ dispatchOnFinishFinalAnimation();
}
};
@@ -150,6 +165,7 @@
public void onAnimationEnd(Animator animator) {
ping();
switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
+ dispatchOnFinishFinalAnimation();
}
};
@@ -349,7 +365,7 @@
stopHandleAnimation();
deactivateTargets();
showTargets(true);
- mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
+ activateHandle();
setGrabbedState(OnTriggerListener.CENTER_HANDLE);
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
announceTargets();
@@ -368,6 +384,19 @@
}
}
+ private void activateHandle() {
+ mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
+ if (mAlwaysTrackFinger) {
+ mHandleAnimations.stop();
+ mHandleDrawable.setAlpha(0.0f);
+ mHandleAnimations.add(Tweener.to(mHandleDrawable, INITIAL_SHOW_HANDLE_DURATION,
+ "ease", Ease.Cubic.easeIn,
+ "alpha", 1.0f,
+ "onUpdate", mUpdateListener));
+ mHandleAnimations.start();
+ }
+ }
+
/**
* Animation used to attract user's attention to the target button.
* Assumes mChevronDrawables is an a list with an even number of chevrons filled with
@@ -463,6 +492,12 @@
}
}
+ private void dispatchOnFinishFinalAnimation() {
+ if (mOnTriggerListener != null) {
+ mOnTriggerListener.onFinishFinalAnimation();
+ }
+ }
+
private void doFinish() {
final int activeTarget = mActiveTarget;
boolean targetHit = activeTarget != -1;
@@ -471,8 +506,9 @@
hideTargets(true);
// Highlight the selected one
- mHandleDrawable.setAlpha(targetHit ? 0.0f : 1.0f);
+ mHandleAnimations.cancel();
if (targetHit) {
+ mHandleDrawable.setAlpha(0.0f);
mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
hideUnselected(activeTarget);
@@ -483,12 +519,11 @@
// Animate handle back to the center based on current state.
int delay = targetHit ? RETURN_TO_HOME_DELAY : 0;
- int duration = targetHit ? 0 : RETURN_TO_HOME_DURATION;
- mHandleAnimations.stop();
+ int duration = RETURN_TO_HOME_DURATION;
mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
"ease", Ease.Quart.easeOut,
"delay", delay,
- "alpha", 1.0f,
+ "alpha", mAlwaysTrackFinger ? 0.0f : 1.0f,
"x", 0,
"y", 0,
"onUpdate", mUpdateListener,
@@ -508,12 +543,15 @@
}
private void hideTargets(boolean animate) {
- mTargetAnimations.stop();
+ mTargetAnimations.cancel();
// Note: these animations should complete at the same time so that we can swap out
// the target assets asynchronously from the setTargetResources() call.
mAnimatingTargets = animate;
final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
+ final boolean targetSelected = mActiveTarget != -1;
+
+ final float targetScale = targetSelected ? TARGET_SCALE_SELECTED : TARGET_SCALE_UNSELECTED;
final int length = mTargetDrawables.size();
for (int i = 0; i < length; i++) {
TargetDrawable target = mTargetDrawables.get(i);
@@ -521,13 +559,13 @@
mTargetAnimations.add(Tweener.to(target, duration,
"ease", Ease.Cubic.easeOut,
"alpha", 0.0f,
- "scaleX", TARGET_INITIAL_POSITION_SCALE,
- "scaleY", TARGET_INITIAL_POSITION_SCALE,
+ "scaleX", targetScale,
+ "scaleY", targetScale,
"delay", delay,
"onUpdate", mUpdateListener));
}
- float ringScaleTarget = mActiveTarget != -1 ? 1.5f : 0.5f;
+ final float ringScaleTarget = targetSelected ? RING_SCALE_SELECTED : RING_SCALE_UNSELECTED;
mTargetAnimations.add(Tweener.to(mOuterRing, duration,
"ease", Ease.Cubic.easeOut,
"alpha", 0.0f,
@@ -544,13 +582,14 @@
mTargetAnimations.stop();
mAnimatingTargets = animate;
final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
+ final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
final int length = mTargetDrawables.size();
for (int i = 0; i < length; i++) {
TargetDrawable target = mTargetDrawables.get(i);
target.setState(TargetDrawable.STATE_INACTIVE);
- target.setScaleX(TARGET_INITIAL_POSITION_SCALE);
- target.setScaleY(TARGET_INITIAL_POSITION_SCALE);
- mTargetAnimations.add(Tweener.to(target, animate ? SHOW_ANIMATION_DURATION : 0,
+ target.setScaleX(TARGET_SCALE_SELECTED);
+ target.setScaleY(TARGET_SCALE_SELECTED);
+ mTargetAnimations.add(Tweener.to(target, duration,
"ease", Ease.Cubic.easeOut,
"alpha", 1.0f,
"scaleX", 1.0f,
@@ -558,9 +597,7 @@
"delay", delay,
"onUpdate", mUpdateListener));
}
- mOuterRing.setScaleX(0.5f);
- mOuterRing.setScaleY(0.5f);
- mTargetAnimations.add(Tweener.to(mOuterRing, animate ? SHOW_ANIMATION_DURATION : 0,
+ mTargetAnimations.add(Tweener.to(mOuterRing, duration,
"ease", Ease.Cubic.easeOut,
"alpha", 1.0f,
"scaleX", 1.0f,
@@ -572,10 +609,6 @@
mTargetAnimations.start();
}
- private void stopTargetAnimation() {
- mTargetAnimations.stop();
- }
-
private void vibrate() {
if (mVibrator != null) {
mVibrator.vibrate(mVibrationDuration);
@@ -708,7 +741,7 @@
public void reset(boolean animate) {
stopChevronAnimation();
stopHandleAnimation();
- stopTargetAnimation();
+ mTargetAnimations.stop();
hideChevrons();
hideTargets(animate);
mHandleDrawable.setX(0);
@@ -760,7 +793,7 @@
private void handleDown(MotionEvent event) {
if (!trySwitchToFirstTouchState(event.getX(), event.getY())) {
mDragging = false;
- stopTargetAnimation();
+ mTargetAnimations.cancel();
ping();
}
}
@@ -815,8 +848,8 @@
// For more than one target, snap to the closest one less than hitRadius away.
float best = Float.MAX_VALUE;
final float hitRadius2 = mHitRadius * mHitRadius;
+ // Find first target in range
for (int i = 0; i < ntargets; i++) {
- // Snap to the first target in range
TargetDrawable target = targets.get(i);
float dx = limitX - target.getX();
float dy = limitY - target.getY();
@@ -842,10 +875,15 @@
float newX = singleTarget ? x : target.getX();
float newY = singleTarget ? y : target.getY();
moveHandleTo(newX, newY, false);
+ mHandleAnimations.cancel();
+ mHandleDrawable.setAlpha(0.0f);
} else {
switchToState(STATE_TRACKING, x, y);
+ if (mActiveTarget != -1) {
+ mHandleAnimations.cancel();
+ mHandleDrawable.setAlpha(1.0f);
+ }
moveHandleTo(x, y, false);
- mHandleDrawable.setAlpha(1.0f);
}
// Draw handle outside parent's bounds
@@ -857,7 +895,6 @@
TargetDrawable target = targets.get(mActiveTarget);
if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
target.setState(TargetDrawable.STATE_INACTIVE);
- mHandleDrawable.setAlpha(1.0f);
}
}
// Focus the new target
@@ -865,7 +902,6 @@
TargetDrawable target = targets.get(activeTarget);
if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
target.setState(TargetDrawable.STATE_FOCUSED);
- mHandleDrawable.setAlpha(0.0f);
}
dispatchGrabbedEvent(activeTarget);
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 7146667..d422951 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -703,7 +703,7 @@
#endif
uint32_t ref = ident;
if (resolve) {
- block = res.resolveReference(&value, block, &ref);
+ block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index e3d11f2..82f8984 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -774,11 +774,12 @@
float transform[16];
sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, surface));
- surfaceTexture->updateTexImage();
- surfaceTexture->getTransformMatrix(transform);
- GLenum renderTarget = surfaceTexture->getCurrentTextureTarget();
+ if (surfaceTexture->updateTexImage() == NO_ERROR) {
+ surfaceTexture->getTransformMatrix(transform);
+ GLenum renderTarget = surfaceTexture->getCurrentTextureTarget();
- LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, renderTarget, transform);
+ LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, renderTarget, transform);
+ }
}
static void android_view_GLES20Canvas_updateRenderLayer(JNIEnv* env, jobject clazz,
diff --git a/core/res/res/drawable-hdpi/ic_settings_language.png b/core/res/res/drawable-hdpi/ic_settings_language.png
new file mode 100755
index 0000000..f635b2e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/drawable-large-nodpi/default_wallpaper.jpg b/core/res/res/drawable-large-nodpi/default_wallpaper.jpg
deleted file mode 100644
index 355286e..0000000
--- a/core/res/res/drawable-large-nodpi/default_wallpaper.jpg
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_settings_language.png b/core/res/res/drawable-mdpi/ic_settings_language.png
new file mode 100644
index 0000000..f8aca67
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/default_wallpaper.jpg b/core/res/res/drawable-nodpi/default_wallpaper.jpg
index 7e92243..d7475b4c 100644
--- a/core/res/res/drawable-nodpi/default_wallpaper.jpg
+++ b/core/res/res/drawable-nodpi/default_wallpaper.jpg
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.jpg b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.jpg
new file mode 100644
index 0000000..03a14c0
--- /dev/null
+++ b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.jpg
Binary files differ
diff --git a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.jpg b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.jpg
new file mode 100644
index 0000000..543d118
--- /dev/null
+++ b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.jpg
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/default_wallpaper.jpg b/core/res/res/drawable-xhdpi/default_wallpaper.jpg
new file mode 100644
index 0000000..5b8d1d5
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/default_wallpaper.jpg
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_settings_language.png b/core/res/res/drawable-xhdpi/ic_settings_language.png
new file mode 100644
index 0000000..2c42db3
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-nodpi/default_wallpaper.jpg b/core/res/res/drawable-xlarge-nodpi/default_wallpaper.jpg
deleted file mode 100644
index 355286e..0000000
--- a/core/res/res/drawable-xlarge-nodpi/default_wallpaper.jpg
+++ /dev/null
Binary files differ
diff --git a/core/res/res/layout/notification_template_inbox.xml b/core/res/res/layout/notification_template_inbox.xml
index eb5e759..3b00feb 100644
--- a/core/res/res/layout/notification_template_inbox.xml
+++ b/core/res/res/layout/notification_template_inbox.xml
@@ -132,6 +132,34 @@
android:visibility="gone"
android:layout_weight="1"
/>
+ <TextView android:id="@+id/inbox_text5"
+ android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ <TextView android:id="@+id/inbox_text6"
+ android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ <TextView android:id="@+id/inbox_more"
+ android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ android:text="@android:string/ellipsis"
+ />
<include
layout="@layout/notification_action_list"
android:id="@+id/actions"
diff --git a/core/res/res/raw/accessibility_gestures.bin b/core/res/res/raw/accessibility_gestures.bin
index 96fa1ec..acd7993 100644
--- a/core/res/res/raw/accessibility_gestures.bin
+++ b/core/res/res/raw/accessibility_gestures.bin
Binary files differ
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a24e345c..bbb2e8e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -208,6 +208,9 @@
<java-symbol type="id" name="inbox_text2" />
<java-symbol type="id" name="inbox_text3" />
<java-symbol type="id" name="inbox_text4" />
+ <java-symbol type="id" name="inbox_text5" />
+ <java-symbol type="id" name="inbox_text6" />
+ <java-symbol type="id" name="inbox_more" />
<java-symbol type="id" name="status_bar_latest_event_content" />
<java-symbol type="attr" name="actionModeShareDrawable" />
@@ -1489,6 +1492,8 @@
<java-symbol type="string" name="low_internal_storage_view_title" />
<java-symbol type="string" name="report" />
<java-symbol type="string" name="select_input_method" />
+ <java-symbol type="string" name="select_keyboard_layout_notification_title" />
+ <java-symbol type="string" name="select_keyboard_layout_notification_message" />
<java-symbol type="string" name="smv_application" />
<java-symbol type="string" name="smv_process" />
<java-symbol type="string" name="tethered_notification_message" />
@@ -1513,6 +1518,8 @@
<java-symbol type="xml" name="storage_list" />
<java-symbol type="bool" name="config_enableDreams" />
<java-symbol type="string" name="config_defaultDreamComponent" />
+ <java-symbol type="string" name="enable_explore_by_touch_warning_title" />
+ <java-symbol type="string" name="enable_explore_by_touch_warning_message" />
<java-symbol type="layout" name="resolver_grid" />
<java-symbol type="id" name="resolver_grid" />
@@ -1580,6 +1587,7 @@
<java-symbol type="drawable" name="expander_ic_minimized" />
<java-symbol type="drawable" name="ic_menu_archive" />
<java-symbol type="drawable" name="ic_menu_goto" />
+ <java-symbol type="drawable" name="ic_settings_language" />
<java-symbol type="drawable" name="title_bar_medium" />
<java-symbol type="id" name="body" />
<java-symbol type="string" name="fast_scroll_alphabet" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4511201..5929439 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2503,6 +2503,25 @@
<!-- SearchView accessibility description for voice button [CHAR LIMIT=NONE] -->
<string name="searchview_description_voice">Voice search</string>
+ <!-- Title for a warning message about the interaction model changes after allowing an accessibility
+ service to put the device into explore by touch mode, displayed as a dialog message when
+ the user selects to enables the service. (default). [CHAR LIMIT=35] -->
+ <string name="enable_explore_by_touch_warning_title">Enable Explore by Touch?</string>
+ <!-- Summary for a warning message about the interaction model changes after allowing an accessibility
+ service to put the device into explore by touch mode, displayed as a dialog message when
+ the user selects to enables the service. (tablet). [CHAR LIMIT=NONE] -->
+ <string name="enable_explore_by_touch_warning_message" product="tablet">
+ <xliff:g id="accessibility_service_name">%1$s</xliff:g> wants to enable Explore by Touch.
+ When Explore by Touch is turned on, you can hear or see descriptions of what\'s under
+ your finger or perform gestures to interact with the tablet.</string>
+ <!-- Summary for a warning message about the interaction model changes after allowing an accessibility
+ service to put the device into explore by touch mode, displayed as a dialog message when
+ the user selects to enables the service. (default). [CHAR LIMIT=NONE] -->
+ <string name="enable_explore_by_touch_warning_message" product="default">
+ <xliff:g id="accessibility_service_name">%1$s</xliff:g> wants to enable Explore by Touch.
+ When Explore by Touch is turned on, you can hear or see descriptions of what\'s under
+ your finger or perform gestures to interact with the phone.</string>
+
<!-- String used to display the date. This is the string to say something happened 1 month ago. -->
<string name="oneMonthDurationPast">1 month ago</string>
<!-- String used to display the date. This is the string to say something happened more than 1 month ago. -->
@@ -3092,6 +3111,11 @@
<!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=10] -->
<string name="hardware">Hardware</string>
+ <!-- Title of the notification to prompt the user to select a keyboard layout. -->
+ <string name="select_keyboard_layout_notification_title">Select keyboard layout</string>
+ <!-- Message of the notification to prompt the user to select a keyboard layout. -->
+ <string name="select_keyboard_layout_notification_message">Touch to select a keyboard layout.</string>
+
<string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
@@ -3572,6 +3596,6 @@
<!-- Title for a button to choose the currently selected activity
from the activity resolver to use just this once. [CHAR LIMIT=25] -->
- <string name="activity_resolver_use_once">Just Once</string>
+ <string name="activity_resolver_use_once">Just once</string>
</resources>
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 544076f..782a701 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -252,6 +252,7 @@
label: ' '
base: ' '
alt, meta: fallback SEARCH
+ ctrl: fallback LANGUAGE_SWITCH
}
key ENTER {
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index e592013..d90b790 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -249,6 +249,7 @@
label: ' '
base: ' '
alt, meta: fallback SEARCH
+ ctrl: fallback LANGUAGE_SWITCH
}
key ENTER {
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 5f74c01..785582c 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -386,6 +386,7 @@
/**
* Get the resolved layout direction of this Drawable.
+ * @hide
*/
public int getResolvedLayoutDirectionSelf() {
final Callback callback = getCallback();
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index aa29444..da01c44 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -59,6 +59,7 @@
import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.System;
+import android.speech.RecognizerIntent;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -3818,24 +3819,32 @@
* Tell the system to start voice-based interactions / voice commands
*/
private void startVoiceBasedInteractions(boolean needWakeLock) {
- Intent voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
+ Intent voiceIntent = null;
+ // select which type of search to launch:
+ // - screen on and device unlocked: action is ACTION_WEB_SEARCH
+ // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE
+ // with EXTRA_SECURE set to true if the device is securely locked
+ PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+ if (!isLocked && pm.isScreenOn()) {
+ voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
+ } else {
+ voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
+ isLocked && mKeyguardManager.isKeyguardSecure());
+ }
+ // start the search activity
if (needWakeLock) {
mMediaEventWakeLock.acquire();
}
- voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
try {
- if (mKeyguardManager != null) {
- // it's ok to start voice-based interactions when:
- // - the device is locked but doesn't require a password to be unlocked
- // - the device is not locked
- if ((mKeyguardManager.isKeyguardLocked() && !mKeyguardManager.isKeyguardSecure())
- || !mKeyguardManager.isKeyguardLocked()) {
- mContext.startActivity(voiceIntent);
- }
+ if (voiceIntent != null) {
+ voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ mContext.startActivity(voiceIntent);
}
} catch (ActivityNotFoundException e) {
- Log.e(TAG, "Error launching activity for ACTION_WEB_SEARCH: " + e);
+ Log.w(TAG, "No activity for search: " + e);
} finally {
if (needWakeLock) {
mMediaEventWakeLock.release();
@@ -3843,20 +3852,6 @@
}
}
- /**
- * Verify whether it is safe to start voice-based interactions given the state of the system
- * @return false is the Keyguard is locked and secure, true otherwise
- */
- private boolean safeToStartVoiceBasedInteractions() {
- KeyguardManager keyguard =
- (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- if (keyguard == null) {
- return false;
- }
-
- return true;
- }
-
private PowerManager.WakeLock mMediaEventWakeLock;
private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
index 3657d8a..d8aa40f 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
@@ -376,8 +376,6 @@
@Override
public void process(FilterContext context) {
- if (mLogVerbose) Log.v(TAG, "Starting frame processing");
-
GLEnvironment glEnv = context.getGLEnvironment();
// Get input frame
Frame input = pullInput("videoframe");
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
index b023e42..674a2bd 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
@@ -121,6 +121,7 @@
}
public void updateRenderMode() {
+ if (mLogVerbose) Log.v(TAG, "updateRenderMode. Thread: " + Thread.currentThread());
if (mRenderModeString != null) {
if (mRenderModeString.equals("stretch")) {
mRenderMode = RENDERMODE_STRETCH;
@@ -139,6 +140,7 @@
@Override
public void prepare(FilterContext context) {
+ if (mLogVerbose) Log.v(TAG, "Prepare. Thread: " + Thread.currentThread());
// Create identity shader to render, and make sure to render upside-down, as textures
// are stored internally bottom-to-top.
mProgram = ShaderProgram.createIdentity(context);
@@ -214,8 +216,10 @@
float currentAspectRatio =
(float)input.getFormat().getWidth() / input.getFormat().getHeight();
if (currentAspectRatio != mAspectRatio) {
- if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +
- ", previously: " + mAspectRatio);
+ if (mLogVerbose) {
+ Log.v(TAG, "Process. New aspect ratio: " + currentAspectRatio +
+ ", previously: " + mAspectRatio + ". Thread: " + Thread.currentThread());
+ }
mAspectRatio = currentAspectRatio;
updateTargetRect();
}
@@ -249,6 +253,7 @@
@Override
public void fieldPortValueUpdated(String name, FilterContext context) {
+ if (mLogVerbose) Log.v(TAG, "FPVU. Thread: " + Thread.currentThread());
updateRenderMode();
}
@@ -260,16 +265,22 @@
}
private void updateTargetRect() {
+ if (mLogVerbose) Log.v(TAG, "updateTargetRect. Thread: " + Thread.currentThread());
if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) {
float screenAspectRatio = (float)mScreenWidth / mScreenHeight;
float relativeAspectRatio = screenAspectRatio / mAspectRatio;
+ if (mLogVerbose) {
+ Log.v(TAG, "UTR. screen w = " + (float)mScreenWidth + " x screen h = " +
+ (float)mScreenHeight + " Screen AR: " + screenAspectRatio +
+ ", frame AR: " + mAspectRatio + ", relative AR: " + relativeAspectRatio);
+ }
if (relativeAspectRatio == 1.0f && mRenderMode != RENDERMODE_CUSTOMIZE) {
+ mProgram.setTargetRect(0, 0, 1, 1);
mProgram.setClearsOutput(false);
} else {
switch (mRenderMode) {
case RENDERMODE_STRETCH:
- mProgram.setTargetRect(0, 0, 1, 1);
mTargetQuad.p0.set(0f, 0.0f);
mTargetQuad.p1.set(1f, 0.0f);
mTargetQuad.p2.set(0f, 1.0f);
@@ -313,6 +324,7 @@
((ShaderProgram) mProgram).setSourceRegion(mSourceQuad);
break;
}
+ if (mLogVerbose) Log.v(TAG, "UTR. quad: " + mTargetQuad);
((ShaderProgram) mProgram).setTargetRegion(mTargetQuad);
}
}
diff --git a/packages/SystemUI/res/anim/search_launch_enter.xml b/packages/SystemUI/res/anim/search_launch_enter.xml
new file mode 100644
index 0000000..055ea5d
--- /dev/null
+++ b/packages/SystemUI/res/anim/search_launch_enter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" android:zAdjustment="top">
+
+ <alpha android:fromAlpha="0" android:toAlpha="1.0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@android:interpolator/decelerate_quad"
+ android:duration="300"/>
+
+ <translate android:fromYDelta="200" android:toYDelta="0"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@android:interpolator/decelerate_cubic"
+ android:duration="300" />
+</set>
diff --git a/packages/SystemUI/res/anim/search_launch_exit.xml b/packages/SystemUI/res/anim/search_launch_exit.xml
new file mode 100644
index 0000000..b4ed278
--- /dev/null
+++ b/packages/SystemUI/res/anim/search_launch_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator"
+ android:fromXDelta="0" android:toXDelta="0"
+ android:duration="300" />
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
index 6b0bb87..28283ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
@@ -18,6 +18,8 @@
import android.animation.Animator;
import android.animation.LayoutTransition;
+import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -36,6 +38,7 @@
import com.android.internal.widget.multiwaveview.MultiWaveView;
import com.android.internal.widget.multiwaveview.MultiWaveView.OnTriggerListener;
+import com.android.server.am.ActivityManagerService;
import com.android.systemui.R;
import com.android.systemui.recent.StatusBarTouchProxy;
import com.android.systemui.statusbar.BaseStatusBar;
@@ -103,26 +106,22 @@
}
private void startAssistActivity() {
- if (mSearchManager != null) {
- ComponentName globalSearchActivity = mSearchManager.getGlobalSearchActivity();
- if (globalSearchActivity != null) {
- Intent intent = new Intent(Intent.ACTION_ASSIST);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setPackage(globalSearchActivity.getPackageName());
- try {
- mContext.startActivity(intent);
- } catch (ActivityNotFoundException e) {
- Slog.w(TAG, "Activity not found for " + intent.getAction());
- }
- } else {
- Slog.w(TAG, "No global search activity");
- }
+ Intent intent = getAssistIntent();
+ try {
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.search_launch_enter, R.anim.search_launch_exit);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent, opts.toBundle());
+ } catch (ActivityNotFoundException e) {
+ Slog.w(TAG, "Activity not found for " + intent.getAction());
}
}
final MultiWaveView.OnTriggerListener mMultiWaveViewListener
= new MultiWaveView.OnTriggerListener() {
+ private int mTarget = -1;
+
public void onGrabbed(View v, int handle) {
}
@@ -136,11 +135,18 @@
}
public void onTrigger(View v, int target) {
- final int resId = mMultiWaveView.getResourceIdForTarget(target);
- switch (resId) {
- case com.android.internal.R.drawable.ic_lockscreen_search:
- startAssistActivity();
- break;
+ mTarget = target;
+ }
+
+ public void onFinishFinalAnimation() {
+ if (mTarget != -1) {
+ final int resId = mMultiWaveView.getResourceIdForTarget(mTarget);
+ mTarget = -1; // a safety to make sure we never launch w/o prior call to onTrigger
+ switch (resId) {
+ case com.android.internal.R.drawable.ic_lockscreen_search:
+ startAssistActivity();
+ break;
+ }
}
mBar.hideSearchPanel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 69d2e73..d38611d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -515,14 +515,8 @@
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
- Slog.d(TAG, "showing search panel");
showSearchPanel();
break;
-
- case MotionEvent.ACTION_UP:
- Slog.d(TAG, "hiding search panel");
- hideSearchPanel();
- break;
}
return false;
}
@@ -533,8 +527,8 @@
mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPanel);
+ mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener);
updateSearchPanel();
-// mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener);
}
// For small-screen devices (read: phones) that lack hardware navigation buttons
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index dba1606..10c5dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -188,6 +188,17 @@
public Context getContext() { return mContext; }
+ private View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() {
+ public boolean onTouch(View v, MotionEvent event) {
+ switch(event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ showSearchPanel();
+ break;
+ }
+ return false;
+ }
+ };
+
@Override
protected void createAndAddWindows() {
addStatusBarWindow();
@@ -290,6 +301,7 @@
// Search Panel
mStatusBarView.setBar(this);
+ mHomeButton.setOnTouchListener(mHomeSearchActionListener);
updateSearchPanel();
// Input methods Panel
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index 22f70a5..30cb530 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -401,6 +401,10 @@
public void cleanUp() {
mMultiWaveView.setOnTriggerListener(null);
}
+
+ public void onFinishFinalAnimation() {
+
+ }
}
private void requestUnlockScreen() {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index cee01ac..29de5c1 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -326,6 +326,7 @@
RecentApplicationsDialog mRecentAppsDialog;
int mRecentAppsDialogHeldModifiers;
+ boolean mLanguageSwitchKeyPressed;
int mLidState = LID_ABSENT;
boolean mHaveBuiltInKeyboard;
@@ -1943,6 +1944,22 @@
RECENT_APPS_BEHAVIOR_DISMISS_AND_SWITCH);
}
+ // Handle keyboard language switching.
+ if (down && repeatCount == 0
+ && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
+ || (keyCode == KeyEvent.KEYCODE_SPACE
+ && (metaState & KeyEvent.META_CTRL_MASK) != 0))) {
+ int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+ mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
+ return -1;
+ }
+ if (mLanguageSwitchKeyPressed && !down
+ && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
+ || keyCode == KeyEvent.KEYCODE_SPACE)) {
+ mLanguageSwitchKeyPressed = false;
+ return -1;
+ }
+
// Let the application handle the key.
return 0;
}
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index a49ccf7..ff14568 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -195,6 +195,7 @@
private PendingIntent mImeSwitchPendingIntent;
private boolean mShowOngoingImeSwitcherForPhones;
private boolean mNotificationShown;
+ private final boolean mImeSelectedOnBoot;
class SessionState {
final ClientState client;
@@ -590,7 +591,6 @@
mImeSwitcherNotification.vibrate = null;
Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
- mLastSystemLocale = mRes.getConfiguration().locale;
mShowOngoingImeSwitcherForPhones = false;
@@ -612,11 +612,17 @@
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
mRes, context.getContentResolver(), mMethodMap, mMethodList);
+
+ // Just checking if defaultImiId is empty or not
+ final String defaultImiId = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
+
buildInputMethodListLocked(mMethodList, mMethodMap);
mSettings.enableAllIMEsIfThereIsNoEnabledIME();
- if (TextUtils.isEmpty(Settings.Secure.getString(
- mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
+ if (!mImeSelectedOnBoot) {
+ Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
resetDefaultImeLocked(context);
}
@@ -639,6 +645,10 @@
}
private void checkCurrentLocaleChangedLocked() {
+ if (!mSystemReady) {
+ // not system ready
+ return;
+ }
final Locale newLocale = mRes.getConfiguration().locale;
if (newLocale != null && !newLocale.equals(mLastSystemLocale)) {
if (DEBUG) {
@@ -647,6 +657,7 @@
buildInputMethodListLocked(mMethodList, mMethodMap);
// Reset the current ime to the proper one
resetDefaultImeLocked(mContext);
+ updateFromSettingsLocked();
mLastSystemLocale = newLocale;
}
}
@@ -675,7 +686,10 @@
}
}
- private static boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
+ private boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
+ if (!mSystemReady) {
+ return false;
+ }
if (!isSystemIme(imi)) {
return false;
}
@@ -738,7 +752,6 @@
mContext.getSystemService(Context.KEYGUARD_SERVICE);
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- mLastSystemLocale = mContext.getResources().getConfiguration().locale;
mStatusBar = statusBar;
statusBar.setIconVisibility("ime", false);
updateImeWindowStatusLocked();
@@ -748,6 +761,12 @@
mWindowManagerService.setOnHardKeyboardStatusChangeListener(
mHardKeyboardListener);
}
+ buildInputMethodListLocked(mMethodList, mMethodMap);
+ if (!mImeSelectedOnBoot) {
+ Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
+ checkCurrentLocaleChangedLocked();
+ }
+ mLastSystemLocale = mRes.getConfiguration().locale;
try {
startInputInnerLocked();
} catch (RuntimeException e) {
@@ -1421,34 +1440,41 @@
throw new IllegalArgumentException("Unknown id: " + id);
}
+ // See if we need to notify a subtype change within the same IME.
if (id.equals(mCurMethodId)) {
- InputMethodSubtype subtype = null;
- if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) {
- subtype = info.getSubtypeAt(subtypeId);
+ final int subtypeCount = info.getSubtypeCount();
+ if (subtypeCount <= 0) {
+ return;
}
- if (subtype != mCurrentSubtype) {
- synchronized (mMethodMap) {
- if (subtype != null) {
- setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
- }
- if (mCurMethod != null) {
- try {
- refreshImeWindowVisibilityLocked();
- // If subtype is null, try to find the most applicable one from
- // getCurrentInputMethodSubtype.
- if (subtype == null) {
- subtype = getCurrentInputMethodSubtype();
- }
- mCurMethod.changeInputMethodSubtype(subtype);
- } catch (RemoteException e) {
- return;
- }
+ final InputMethodSubtype oldSubtype = mCurrentSubtype;
+ final InputMethodSubtype newSubtype;
+ if (subtypeId >= 0 && subtypeId < subtypeCount) {
+ newSubtype = info.getSubtypeAt(subtypeId);
+ } else {
+ // If subtype is null, try to find the most applicable one from
+ // getCurrentInputMethodSubtype.
+ newSubtype = getCurrentInputMethodSubtype();
+ }
+ if (newSubtype == null || oldSubtype == null) {
+ Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
+ + ", new subtype = " + newSubtype);
+ return;
+ }
+ if (newSubtype != oldSubtype) {
+ setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
+ if (mCurMethod != null) {
+ try {
+ refreshImeWindowVisibilityLocked();
+ mCurMethod.changeInputMethodSubtype(newSubtype);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call changeInputMethodSubtype");
}
}
}
return;
}
+ // Changing to a different IME.
final long ident = Binder.clearCallingIdentity();
try {
// Set a subtype to this input method.
@@ -2137,7 +2163,6 @@
return subtypes;
}
-
private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
InputMethodInfo imi, String mode) {
ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
@@ -2155,15 +2180,19 @@
List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
if (enabled != null && enabled.size() > 0) {
// We'd prefer to fall back on a system IME, since that is safer.
- int i=enabled.size();
+ int i = enabled.size();
+ int firstFoundSystemIme = -1;
while (i > 0) {
i--;
final InputMethodInfo imi = enabled.get(i);
- if (isSystemIme(imi) && !imi.isAuxiliaryIme()) {
- break;
+ if (isSystemImeThatHasEnglishSubtype(imi) && !imi.isAuxiliaryIme()) {
+ return imi;
+ }
+ if (firstFoundSystemIme < 0 && isSystemIme(imi) && !imi.isAuxiliaryIme()) {
+ firstFoundSystemIme = i;
}
}
- return enabled.get(i);
+ return enabled.get(Math.max(firstFoundSystemIme, 0));
}
return null;
}
@@ -2238,11 +2267,17 @@
}
}
- String defaultIme = Settings.Secure.getString(mContext
+ final String defaultImiId = Settings.Secure.getString(mContext
.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
- if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
- if (chooseNewDefaultIMELocked()) {
- updateFromSettingsLocked();
+ if (!TextUtils.isEmpty(defaultImiId)) {
+ if (!map.containsKey(defaultImiId)) {
+ Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
+ if (chooseNewDefaultIMELocked()) {
+ updateFromSettingsLocked();
+ }
+ } else {
+ // Double check that the default IME is certainly enabled.
+ setInputMethodEnabledLocked(defaultImiId, true);
}
}
}
@@ -2626,7 +2661,8 @@
mCurrentSubtype = subtype;
} else {
mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
- mCurrentSubtype = null;
+ // If the subtype is not specified, choose the most applicable one
+ mCurrentSubtype = getCurrentInputMethodSubtype();
}
}
@@ -3007,8 +3043,8 @@
mContext = context;
mPm = context.getPackageManager();
mImms = imms;
- mSystemLocaleStr =
- imms.mLastSystemLocale != null ? imms.mLastSystemLocale.toString() : "";
+ final Locale locale = context.getResources().getConfiguration().locale;
+ mSystemLocaleStr = locale != null ? locale.toString() : "";
}
private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 3fbac38..3e35b20d 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -16,7 +16,6 @@
package com.android.server.accessibility;
-import com.android.server.accessibility.TouchExplorer.GestureListener;
import com.android.server.input.InputFilter;
import android.content.Context;
@@ -40,7 +39,7 @@
private final PowerManager mPm;
- private final GestureListener mGestureListener;
+ private final AccessibilityManagerService mAms;
/**
* This is an interface for explorers that take a {@link MotionEvent}
@@ -73,10 +72,10 @@
private int mTouchscreenSourceDeviceId;
- public AccessibilityInputFilter(Context context, GestureListener gestureListener) {
+ public AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
super(context.getMainLooper());
mContext = context;
- mGestureListener = gestureListener;
+ mAms = service;
mPm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
}
@@ -85,7 +84,7 @@
if (DEBUG) {
Slog.d(TAG, "Accessibility input filter installed.");
}
- mTouchExplorer = new TouchExplorer(this, mContext, mGestureListener);
+ mTouchExplorer = new TouchExplorer(this, mContext, mAms);
super.onInstalled();
}
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index f23b25e..ebc2074 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -24,12 +24,15 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
@@ -58,7 +61,9 @@
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -66,8 +71,8 @@
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
+import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
-import com.android.server.accessibility.TouchExplorer.GestureListener;
import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParserException;
@@ -90,8 +95,7 @@
*
* @hide
*/
-public class AccessibilityManagerService extends IAccessibilityManager.Stub
- implements GestureListener {
+public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private static final boolean DEBUG = false;
@@ -102,6 +106,8 @@
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
+ private static final int MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG = 1;
+
private static int sIdCounter = 0;
private static int sNextWindowId;
@@ -128,6 +134,8 @@
private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
+ private final Rect mTempRect = new Rect();
+
private PackageManager mPackageManager;
private int mHandledFeedbackTypes = 0;
@@ -146,23 +154,15 @@
private final SecurityPolicy mSecurityPolicy;
+ private final MainHanler mMainHandler;
+
private Service mUiAutomationService;
- /**
- * Handler for delayed event dispatch.
- */
- private Handler mHandler = new Handler() {
+ private Service mQueryBridge;
- @Override
- public void handleMessage(Message message) {
- Service service = (Service) message.obj;
- int eventType = message.arg1;
+ private boolean mTouchExplorationGestureEnded;
- synchronized (mLock) {
- notifyAccessibilityEventLocked(service, eventType);
- }
- }
- };
+ private boolean mTouchExplorationGestureStarted;
/**
* Creates a new instance.
@@ -175,7 +175,7 @@
mWindowManagerService = (WindowManagerService) ServiceManager.getService(
Context.WINDOW_SERVICE);
mSecurityPolicy = new SecurityPolicy();
-
+ mMainHandler = new MainHanler();
registerPackageChangeAndBootCompletedBroadcastReceiver();
registerSettingsContentObservers();
}
@@ -349,15 +349,37 @@
}
public boolean sendAccessibilityEvent(AccessibilityEvent event) {
+ // The event for gesture start should be strictly before the
+ // first hover enter event for the gesture.
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+ && mTouchExplorationGestureStarted) {
+ mTouchExplorationGestureStarted = false;
+ AccessibilityEvent gestureStartEvent = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
+ sendAccessibilityEvent(gestureStartEvent);
+ }
+
synchronized (mLock) {
if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) {
- mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event);
+ mSecurityPolicy.updateActiveWindowAndEventSourceLocked(event);
notifyAccessibilityServicesDelayedLocked(event, false);
notifyAccessibilityServicesDelayedLocked(event, true);
}
}
+
+ // The event for gesture end should be strictly after the
+ // last hover exit event for the gesture.
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+ && mTouchExplorationGestureEnded) {
+ mTouchExplorationGestureEnded = false;
+ AccessibilityEvent gestureEndEvent = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ sendAccessibilityEvent(gestureEndEvent);
+ }
+
event.recycle();
mHandledFeedbackTypes = 0;
+
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
@@ -472,8 +494,7 @@
}
}
- @Override
- public boolean onGesture(int gestureId) {
+ boolean onGesture(int gestureId) {
synchronized (mLock) {
boolean handled = notifyGestureLocked(gestureId, false);
if (!handled) {
@@ -483,6 +504,65 @@
}
}
+ /**
+ * Gets the bounds of the accessibility focus if the provided,
+ * point coordinates are within the currently active window
+ * and accessibility focus is found within the latter.
+ *
+ * @param x X coordinate.
+ * @param y Y coordinate
+ * @param outBounds The output to which to write the focus bounds.
+ * @return The accessibility focus rectangle if the point is in the
+ * window and the window has accessibility focus.
+ */
+ boolean getAccessibilityFocusBounds(int x, int y, Rect outBounds) {
+ // Instead of keeping track of accessibility focus events per
+ // window to be able to find the focus in the active window,
+ // we take a stateless approach and look it up. This is fine
+ // since we do this only when the user clicks/long presses.
+ Service service = getQueryBridge();
+ final int connectionId = service.mId;
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ client.addConnection(connectionId, service);
+ try {
+ AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
+ .getRootInActiveWindow(connectionId);
+ if (root == null) {
+ return false;
+ }
+ Rect bounds = mTempRect;
+ root.getBoundsInScreen(bounds);
+ if (!bounds.contains(x, y)) {
+ return false;
+ }
+ AccessibilityNodeInfo focus = root.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+ if (focus == null) {
+ return false;
+ }
+ focus.getBoundsInScreen(outBounds);
+ return true;
+ } finally {
+ client.removeConnection(connectionId);
+ }
+ }
+
+ private Service getQueryBridge() {
+ if (mQueryBridge == null) {
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ mQueryBridge = new Service(null, info, true);
+ }
+ return mQueryBridge;
+ }
+
+ public void touchExplorationGestureEnded() {
+ mTouchExplorationGestureEnded = true;
+ }
+
+ public void touchExplorationGestureStarted() {
+ mTouchExplorationGestureStarted = true;
+ }
+
private boolean notifyGestureLocked(int gestureId, boolean isDefault) {
// TODO: Now we are giving the gestures to the last enabled
// service that can handle them which is the last one
@@ -496,12 +576,7 @@
for (int i = mServices.size() - 1; i >= 0; i--) {
Service service = mServices.get(i);
if (service.mReqeustTouchExplorationMode && service.mIsDefault == isDefault) {
- try {
- service.mServiceInterface.onGesture(gestureId);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
- + " to " + service.mService, re);
- }
+ service.notifyGesture(gestureId);
return true;
}
}
@@ -573,7 +648,7 @@
if (service.mIsDefault == isDefault) {
if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) {
mHandledFeedbackTypes |= service.mFeedbackType;
- notifyAccessibilityServiceDelayedLocked(service, event);
+ service.notifyAccessibilityEvent(event);
}
}
}
@@ -586,90 +661,6 @@
}
/**
- * Performs an {@link AccessibilityService} delayed notification. The delay is configurable
- * and denotes the period after the last event before notifying the service.
- *
- * @param service The service.
- * @param event The event.
- */
- private void notifyAccessibilityServiceDelayedLocked(Service service,
- AccessibilityEvent event) {
- synchronized (mLock) {
- final int eventType = event.getEventType();
- // Make a copy since during dispatch it is possible the event to
- // be modified to remove its source if the receiving service does
- // not have permission to access the window content.
- AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
- AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
- service.mPendingEvents.put(eventType, newEvent);
-
- final int what = eventType | (service.mId << 16);
- if (oldEvent != null) {
- mHandler.removeMessages(what);
- oldEvent.recycle();
- }
-
- Message message = mHandler.obtainMessage(what, service);
- message.arg1 = eventType;
- mHandler.sendMessageDelayed(message, service.mNotificationTimeout);
- }
- }
-
- /**
- * Notifies an accessibility service client for a scheduled event given the event type.
- *
- * @param service The service client.
- * @param eventType The type of the event to dispatch.
- */
- private void notifyAccessibilityEventLocked(Service service, int eventType) {
- IAccessibilityServiceClient listener = service.mServiceInterface;
-
- // If the service died/was disabled while the message for dispatching
- // the accessibility event was propagating the listener may be null.
- if (listener == null) {
- return;
- }
-
- AccessibilityEvent event = service.mPendingEvents.get(eventType);
-
- // Check for null here because there is a concurrent scenario in which this
- // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
- // which posts a message for dispatching an event. 2) The message is pulled
- // from the queue by the handler on the service thread and the latter is
- // just about to acquire the lock and call this method. 3) Now another binder
- // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked
- // so the service thread waits for the lock; 4) The binder thread replaces
- // the event with a more recent one (assume the same event type) and posts a
- // dispatch request releasing the lock. 5) Now the main thread is unblocked and
- // dispatches the event which is removed from the pending ones. 6) And ... now
- // the service thread handles the last message posted by the last binder call
- // but the event is already dispatched and hence looking it up in the pending
- // ones yields null. This check is much simpler that keeping count for each
- // event type of each service to catch such a scenario since only one message
- // is processed at a time.
- if (event == null) {
- return;
- }
-
- service.mPendingEvents.remove(eventType);
- try {
- if (mSecurityPolicy.canRetrieveWindowContent(service)) {
- event.setConnectionId(service.mId);
- } else {
- event.setSource(null);
- }
- event.setSealed(true);
- listener.onAccessibilityEvent(event);
- event.recycle();
- if (DEBUG) {
- Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
- }
- }
-
- /**
* Adds a service.
*
* @param service The service to add.
@@ -683,6 +674,7 @@
mServices.add(service);
mComponentNameToServiceMap.put(service.mComponentName, service);
updateInputFilterLocked();
+ tryEnableTouchExploration(service);
} catch (RemoteException e) {
/* do nothing */
}
@@ -700,10 +692,10 @@
return false;
}
mComponentNameToServiceMap.remove(service.mComponentName);
- mHandler.removeMessages(service.mId);
service.unlinkToOwnDeath();
service.dispose();
updateInputFilterLocked();
+ tryDisableTouchExploration(service);
return removed;
}
@@ -932,6 +924,29 @@
Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
}
+ private void tryEnableTouchExploration(final Service service) {
+ if (!mIsTouchExplorationEnabled && service.mRequestTouchExplorationMode) {
+ mMainHandler.obtainMessage(MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG,
+ service).sendToTarget();
+ }
+ }
+
+ private void tryDisableTouchExploration(Service service) {
+ if (mIsTouchExplorationEnabled && service.mReqeustTouchExplorationMode) {
+ synchronized (mLock) {
+ final int serviceCount = mServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ Service other = mServices.get(i);
+ if (other != service && other.mRequestTouchExplorationMode) {
+ return;
+ }
+ }
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0);
+ }
+ }
+ }
+
private class AccessibilityConnectionWrapper implements DeathRecipient {
private final int mWindowId;
private final IAccessibilityInteractionConnection mConnection;
@@ -959,6 +974,42 @@
}
}
+ private class MainHanler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ final int type = msg.what;
+ switch (type) {
+ case MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG: {
+ Service service = (Service) msg.obj;
+ String label = service.mResolveInfo.loadLabel(
+ mContext.getPackageManager()).toString();
+ final AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .setTitle(R.string.enable_explore_by_touch_warning_title)
+ .setMessage(mContext.getString(
+ R.string.enable_explore_by_touch_warning_message, label))
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
+ dialog.setCanceledOnTouchOutside(true);
+ dialog.show();
+ }
+ }
+ }
+ }
+
/**
* This class represents an accessibility service. It stores all per service
* data required for the service management, provides API for starting/stopping the
@@ -969,6 +1020,11 @@
*/
class Service extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, DeathRecipient {
+
+ // We pick the MSB to avoid collision since accessibility event types are
+ // used as message types allowing us to remove messages per event type.
+ private static final int MSG_ON_GESTURE = 0x80000000;
+
int mId = 0;
AccessibilityServiceInfo mAccessibilityServiceInfo;
@@ -985,6 +1041,8 @@
boolean mIsDefault;
+ boolean mRequestTouchExplorationMode;
+
boolean mIncludeNotImportantViews;
long mNotificationTimeout;
@@ -1001,12 +1059,35 @@
final Rect mTempBounds = new Rect();
+ final ResolveInfo mResolveInfo;
+
// the events pending events to be dispatched to this service
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<AccessibilityEvent>();
+ /**
+ * Handler for delayed event dispatch.
+ */
+ public Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_ON_GESTURE: {
+ final int gestureId = message.arg1;
+ notifyGestureInternal(gestureId);
+ } break;
+ default: {
+ final int eventType = type;
+ notifyAccessibilityEventInternal(eventType);
+ } break;
+ }
+ }
+ };
+
public Service(ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, boolean isAutomation) {
+ mResolveInfo = accessibilityServiceInfo.getResolveInfo();
mId = sIdCounter++;
mComponentName = componentName;
mAccessibilityServiceInfo = accessibilityServiceInfo;
@@ -1043,6 +1124,9 @@
(info.flags & FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
}
+ mRequestTouchExplorationMode = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+
synchronized (mLock) {
tryAddServiceLocked(this);
}
@@ -1403,6 +1487,108 @@
}
}
+ /**
+ * Performs a notification for an {@link AccessibilityEvent}.
+ *
+ * @param event The event.
+ */
+ public void notifyAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ final int eventType = event.getEventType();
+ // Make a copy since during dispatch it is possible the event to
+ // be modified to remove its source if the receiving service does
+ // not have permission to access the window content.
+ AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
+ AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
+ mPendingEvents.put(eventType, newEvent);
+
+ final int what = eventType;
+ if (oldEvent != null) {
+ mHandler.removeMessages(what);
+ oldEvent.recycle();
+ }
+
+ Message message = mHandler.obtainMessage(what);
+ mHandler.sendMessageDelayed(message, mNotificationTimeout);
+ }
+ }
+
+ /**
+ * Notifies an accessibility service client for a scheduled event given the event type.
+ *
+ * @param eventType The type of the event to dispatch.
+ */
+ private void notifyAccessibilityEventInternal(int eventType) {
+ IAccessibilityServiceClient listener;
+ AccessibilityEvent event;
+
+ synchronized (mLock) {
+ listener = mServiceInterface;
+
+ // If the service died/was disabled while the message for dispatching
+ // the accessibility event was propagating the listener may be null.
+ if (listener == null) {
+ return;
+ }
+
+ event = mPendingEvents.get(eventType);
+
+ // Check for null here because there is a concurrent scenario in which this
+ // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
+ // which posts a message for dispatching an event. 2) The message is pulled
+ // from the queue by the handler on the service thread and the latter is
+ // just about to acquire the lock and call this method. 3) Now another binder
+ // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked
+ // so the service thread waits for the lock; 4) The binder thread replaces
+ // the event with a more recent one (assume the same event type) and posts a
+ // dispatch request releasing the lock. 5) Now the main thread is unblocked and
+ // dispatches the event which is removed from the pending ones. 6) And ... now
+ // the service thread handles the last message posted by the last binder call
+ // but the event is already dispatched and hence looking it up in the pending
+ // ones yields null. This check is much simpler that keeping count for each
+ // event type of each service to catch such a scenario since only one message
+ // is processed at a time.
+ if (event == null) {
+ return;
+ }
+
+ mPendingEvents.remove(eventType);
+ if (mSecurityPolicy.canRetrieveWindowContent(this)) {
+ event.setConnectionId(mId);
+ } else {
+ event.setSource(null);
+ }
+ event.setSealed(true);
+ }
+
+ try {
+ listener.onAccessibilityEvent(event);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+ }
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+ } finally {
+ event.recycle();
+ }
+ }
+
+ public void notifyGesture(int gestureId) {
+ mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget();
+ }
+
+ private void notifyGestureInternal(int gestureId) {
+ IAccessibilityServiceClient listener = mServiceInterface;
+ if (listener != null) {
+ try {
+ listener.onGesture(gestureId);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
+ + " to " + mService, re);
+ }
+ }
+ }
+
private void sendDownAndUpKeyEvents(int keyCode) {
final long token = Binder.clearCallingIdentity();
@@ -1454,7 +1640,7 @@
private int resolveAccessibilityWindowId(int accessibilityWindowId) {
if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) {
- return mSecurityPolicy.mRetrievalAlowingWindowId;
+ return mSecurityPolicy.mActiveWindowId;
}
return accessibilityWindowId;
}
@@ -1497,24 +1683,35 @@
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
- private static final int RETRIEVAL_ALLOWING_WINDOW_CHANGE_EVENT_TYPES =
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
- | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT;
-
- private int mRetrievalAlowingWindowId;
+ private int mActiveWindowId;
private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) {
// Send window changed event only for the retrieval allowing window.
return (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
- || event.getWindowId() == mRetrievalAlowingWindowId);
+ || event.getWindowId() == mActiveWindowId);
}
- public void updateRetrievalAllowingWindowAndEventSourceLocked(AccessibilityEvent event) {
+ public void updateActiveWindowAndEventSourceLocked(AccessibilityEvent event) {
+ // The active window is either the window that has input focus or
+ // the window that the user is currently touching. If the user is
+ // touching a window that does not have input focus as soon as the
+ // the user stops touching that window the focused window becomes
+ // the active one.
final int windowId = event.getWindowId();
final int eventType = event.getEventType();
- if ((eventType & RETRIEVAL_ALLOWING_WINDOW_CHANGE_EVENT_TYPES) != 0) {
- mRetrievalAlowingWindowId = windowId;
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
+ if (getFocusedWindowId() == windowId) {
+ mActiveWindowId = windowId;
+ }
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
+ case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
+ mActiveWindowId = windowId;
+ } break;
+ case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
+ mActiveWindowId = getFocusedWindowId();
+ } break;
}
if ((eventType & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) {
event.setSource(null);
@@ -1522,7 +1719,7 @@
}
public int getRetrievalAllowingWindowLocked() {
- return mRetrievalAlowingWindowId;
+ return mActiveWindowId;
}
public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) {
@@ -1550,7 +1747,7 @@
}
private boolean isRetrievalAllowingWindow(int windowId) {
- return (mRetrievalAlowingWindowId == windowId);
+ return (mActiveWindowId == windowId);
}
private boolean isActionPermitted(int action) {
@@ -1567,5 +1764,22 @@
+ " required to call " + function);
}
}
+
+ private int getFocusedWindowId() {
+ // We call this only on window focus change or after touch
+ // exploration gesture end and the shown windows are not that
+ // many, so the linear look up is just fine.
+ IBinder token = mWindowManagerService.getFocusedWindowClientToken();
+ if (token != null) {
+ SparseArray<IBinder> windows = mWindowIdToWindowTokenMap;
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ if (windows.valueAt(i) == token) {
+ return windows.keyAt(i);
+ }
+ }
+ }
+ return -1;
+ }
}
}
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index 39012e6..b0b2b8d 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -16,9 +16,6 @@
package com.android.server.accessibility;
-import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
-import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
-
import android.content.Context;
import android.gesture.Gesture;
import android.gesture.GestureLibraries;
@@ -26,17 +23,19 @@
import android.gesture.GesturePoint;
import android.gesture.GestureStroke;
import android.gesture.Prediction;
+import android.graphics.Rect;
import android.os.Handler;
+import android.os.SystemClock;
import android.util.Slog;
import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import com.android.server.input.InputFilter;
import com.android.internal.R;
+import com.android.server.input.InputFilter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -47,17 +46,18 @@
* and consuming certain events. The interaction model is:
*
* <ol>
- * <li>1. One finger moving around performs touch exploration.</li>
- * <li>2. Two close fingers moving in the same direction perform a drag.</li>
- * <li>3. Multi-finger gestures are delivered to view hierarchy.</li>
- * <li>4. Pointers that have not moved more than a specified distance after they
+ * <li>1. One finger moving slow around performs touch exploration.</li>
+ * <li>2. One finger moving fast around performs gestures.</li>
+ * <li>3. Two close fingers moving in the same direction perform a drag.</li>
+ * <li>4. Multi-finger gestures are delivered to view hierarchy.</li>
+ * <li>5. Pointers that have not moved more than a specified distance after they
* went down are considered inactive.</li>
- * <li>5. Two fingers moving too far from each other or in different directions
- * are considered a multi-finger gesture.</li>
- * <li>6. Tapping on the last touch explored location within given time and
- * distance slop performs a click.</li>
- * <li>7. Tapping and holding for a while on the last touch explored location within
- * given time and distance slop performs a long press.</li>
+ * <li>6. Two fingers moving in different directions are considered a multi-finger gesture.</li>
+ * <li>7. Double tapping clicks on the on the last touch explored location of it was in
+ * a window that does not take focus, otherwise the click is within the accessibility
+ * focused rectangle.</li>
+ * <li>7. Tapping and holding for a while performs a long press in a similar fashion
+ * as the click above.</li>
* <ol>
*
* @hide
@@ -75,85 +75,116 @@
private static final int STATE_DELEGATING = 0x00000004;
private static final int STATE_GESTURE_DETECTING = 0x00000005;
- // The time slop in milliseconds for activating an item after it has
- // been touch explored. Tapping on an item within this slop will perform
- // a click and tapping and holding down a long press.
- private static final long ACTIVATION_TIME_SLOP = 2000;
-
// The minimum of the cosine between the vectors of two moving
// pointers so they can be considered moving in the same direction.
private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
- // The delay for sending a hover enter event.
- private static final long DELAY_SEND_HOVER_ENTER = 200;
-
// Constant referring to the ids bits of all pointers.
private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
// This constant captures the current implementation detail that
// pointer IDs are between 0 and 31 inclusive (subject to change).
// (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
- public static final int MAX_POINTER_COUNT = 32;
+ private static final int MAX_POINTER_COUNT = 32;
// Invalid pointer ID.
- public static final int INVALID_POINTER_ID = -1;
+ private static final int INVALID_POINTER_ID = -1;
+
+ // The velocity above which we detect gestures.
+ private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
+
+ // The minimal distance before we take the middle of the distance between
+ // the two dragging pointers as opposed to use the location of the primary one.
+ private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
// Temporary array for storing pointer IDs.
private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
- // The distance from the last touch explored location tapping within
- // which would perform a click and tapping and holding a long press.
- private final int mTouchExplorationTapSlop;
+ // Timeout within which we try to detect a tap.
+ private final int mTapTimeout;
+
+ // Timeout within which we try to detect a double tap.
+ private final int mDoubleTapTimeout;
+
+ // Slop between the down and up tap to be a tap.
+ private final int mTouchSlop;
+
+ // Slop between the first and second tap to be a double tap.
+ private final int mDoubleTapSlop;
// The InputFilter this tracker is associated with i.e. the filter
// which delegates event processing to this touch explorer.
private final InputFilter mInputFilter;
- // Handle to the accessibility manager for firing accessibility events
- // announcing touch exploration gesture start and end.
- private final AccessibilityManager mAccessibilityManager;
-
- // The last event that was received while performing touch exploration.
- private MotionEvent mLastTouchExploreEvent;
-
// The current state of the touch explorer.
private int mCurrentState = STATE_TOUCH_EXPLORING;
- // Flag whether a touch exploration gesture is in progress.
- private boolean mTouchExploreGestureInProgress;
-
// The ID of the pointer used for dragging.
private int mDraggingPointerId;
// Handler for performing asynchronous operations.
private final Handler mHandler;
- // Command for delayed sending of a hover event.
- private final SendHoverDelayed mSendHoverDelayed;
+ // Command for delayed sending of a hover enter event.
+ private final SendHoverDelayed mSendHoverEnterDelayed;
+
+ // Command for delayed sending of a hover exit event.
+ private final SendHoverDelayed mSendHoverExitDelayed;
// Command for delayed sending of a long press.
private final PerformLongPressDelayed mPerformLongPressDelayed;
+ // Helper to detect and react to double tap in touch explore mode.
+ private final DoubleTapDetector mDoubleTapDetector;
+
+ // The scaled minimal distance before we take the middle of the distance between
+ // the two dragging pointers as opposed to use the location of the primary one.
+ private final int mScaledMinPointerDistanceToUseMiddleLocation;
+
+ // The scaled velocity above which we detect gestures.
+ private final int mScaledGestureDetectionVelocity;
+
+ // Helper to track gesture velocity.
private VelocityTracker mVelocityTracker;
+ // Helper class to track received pointers.
private final ReceivedPointerTracker mReceivedPointerTracker;
+ // Helper class to track injected pointers.
private final InjectedPointerTracker mInjectedPointerTracker;
- private final GestureListener mGestureListener;
+ // Handle to the accessibility manager service.
+ private final AccessibilityManagerService mAms;
- /**
- * Callback for gesture detection.
- */
- public interface GestureListener {
+ // Temporary rectangle to avoid instantiation.
+ private final Rect mTempRect = new Rect();
- /**
- * Called when a given gesture was performed.
- *
- * @param gestureId The gesture id.
- */
- public boolean onGesture(int gestureId);
- }
+ // The X of the previous event.
+ private float mPreviousX;
+
+ // The Y of the previous event.
+ private float mPreviousY;
+
+ // Buffer for storing points for gesture detection.
+ private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+ // The minimal delta between moves to add a gesture point.
+ private static final int TOUCH_TOLERANCE = 3;
+
+ // The minimal score for accepting a predicted gesture.
+ private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+ // The library for gesture detection.
+ private GestureLibrary mGestureLibrary;
+
+ // The long pressing pointer id if coordinate remapping is needed.
+ private int mLongPressingPointerId;
+
+ // The long pressing pointer X if coordinate remapping is needed.
+ private int mLongPressingPointerDeltaX;
+
+ // The long pressing pointer Y if coordinate remapping is needed.
+ private int mLongPressingPointerDeltaY;
/**
* Creates a new instance.
@@ -162,25 +193,73 @@
* @param context A context handle for accessing resources.
*/
public TouchExplorer(InputFilter inputFilter, Context context,
- GestureListener gestureListener) {
- mGestureListener = gestureListener;
+ AccessibilityManagerService service) {
+ mAms = service;
mReceivedPointerTracker = new ReceivedPointerTracker(context);
mInjectedPointerTracker = new InjectedPointerTracker();
mInputFilter = inputFilter;
- mTouchExplorationTapSlop =
- ViewConfiguration.get(context).getScaledTouchExploreTapSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
mHandler = new Handler(context.getMainLooper());
- mSendHoverDelayed = new SendHoverDelayed();
mPerformLongPressDelayed = new PerformLongPressDelayed();
- mAccessibilityManager = AccessibilityManager.getInstance(context);
mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
mGestureLibrary.setOrientationStyle(4);
mGestureLibrary.load();
+ mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
+ mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
+ mDoubleTapDetector = new DoubleTapDetector();
+ final float density = context.getResources().getDisplayMetrics().density;
+ mScaledMinPointerDistanceToUseMiddleLocation =
+ (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
+ mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
+ }
+
+ public void clear() {
+ // If we have not received an event then we are in initial
+ // state. Therefore, there is not need to clean anything.
+ MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
+ if (event != null) {
+ clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
+ }
}
public void clear(MotionEvent event, int policyFlags) {
- sendUpForInjectedDownPointers(event, policyFlags);
- clear();
+ switch (mCurrentState) {
+ case STATE_TOUCH_EXPLORING: {
+ // If a touch exploration gesture is in progress send events for its end.
+ sendExitEventsIfNeeded(policyFlags);
+ } break;
+ case STATE_DRAGGING: {
+ mDraggingPointerId = INVALID_POINTER_ID;
+ // Send exit to all pointers that we have delivered.
+ sendUpForInjectedDownPointers(event, policyFlags);
+ } break;
+ case STATE_DELEGATING: {
+ // Send exit to all pointers that we have delivered.
+ sendUpForInjectedDownPointers(event, policyFlags);
+ } break;
+ case STATE_GESTURE_DETECTING: {
+ // Clear the current stroke.
+ mStrokeBuffer.clear();
+ } break;
+ }
+ // Remove all pending callbacks.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ // Reset the pointer trackers.
+ mReceivedPointerTracker.clear();
+ mInjectedPointerTracker.clear();
+ // Clear the double tap detector
+ mDoubleTapDetector.clear();
+ // Go to initial state.
+ // Clear the long pressing pointer remap data.
+ mLongPressingPointerId = -1;
+ mLongPressingPointerDeltaX = 0;
+ mLongPressingPointerDeltaY = 0;
+ mCurrentState = STATE_TOUCH_EXPLORING;
}
public void onMotionEvent(MotionEvent event, int policyFlags) {
@@ -218,7 +297,6 @@
*/
private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) {
ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
- InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
final int activePointerCount = receivedTracker.getActivePointerCount();
if (mVelocityTracker == null) {
@@ -226,8 +304,16 @@
}
mVelocityTracker.addMovement(event);
+ mDoubleTapDetector.onMotionEvent(event, policyFlags);
+
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ // Pre-feed the motion events to the gesture detector since we
+ // have a distance slop before getting into gesture detection
+ // mode and not using the points within this slop significantly
+ // decreases the quality of gesture recognition.
+ handleMotionEventGestureDetecting(event, policyFlags);
+ //$FALL-THROUGH$
case MotionEvent.ACTION_POINTER_DOWN: {
switch (activePointerCount) {
case 0: {
@@ -235,44 +321,31 @@
+ "touch exploring state!");
}
case 1: {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- // Send a hover for every finger down so the user gets feedback.
+ // If we still have not notified the user for the last
+ // touch, we figure out what to do. If were waiting
+ // we resent the delayed callback and wait again.
+ if (mSendHoverEnterDelayed.isPending()) {
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ }
+
+ // If we have the first tap schedule a long press and break
+ // since we do not want to schedule hover enter because
+ // the delayed callback will kick in before the long click.
+ // This would lead to a state transition resulting in long
+ // pressing the item below the double taped area which is
+ // not necessary where accessibility focus is.
+ if (mDoubleTapDetector.firstTapDetected()) {
+ // We got a tap now post a long press action.
+ mPerformLongPressDelayed.post(event, policyFlags);
+ break;
+ }
+ // Deliver hover enter with a delay to have a chance
+ // to detect what the user is trying to do.
final int pointerId = receivedTracker.getPrimaryActivePointerId();
final int pointerIdBits = (1 << pointerId);
- final int lastAction = injectedTracker.getLastInjectedHoverAction();
-
- // Deliver hover enter with a delay to have a change to detect
- // whether the user actually starts a scrolling gesture.
- if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
- mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER,
- pointerIdBits, policyFlags, DELAY_SEND_HOVER_ENTER);
- } else {
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
- policyFlags);
- }
-
- if (mLastTouchExploreEvent == null) {
- break;
- }
-
- // If more pointers down on the screen since the last touch
- // exploration we discard the last cached touch explore event.
- if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) {
- mLastTouchExploreEvent = null;
- break;
- }
-
- // If the down is in the time slop => schedule a long press.
- final long pointerDownTime =
- receivedTracker.getReceivedPointerDownTime(pointerId);
- final long lastExploreTime = mLastTouchExploreEvent.getEventTime();
- final long deltaTimeExplore = pointerDownTime - lastExploreTime;
- if (deltaTimeExplore <= ACTIVATION_TIME_SLOP) {
- mPerformLongPressDelayed.post(event, policyFlags,
- ViewConfiguration.getLongPressTimeout());
- break;
- }
+ mSendHoverEnterDelayed.post(event, pointerIdBits, policyFlags);
} break;
default: {
/* do nothing - let the code for ACTION_MOVE decide what to do */
@@ -288,119 +361,130 @@
/* do nothing - no active pointers so we swallow the event */
} break;
case 1: {
- // Detect touch exploration gesture start by having one active pointer
- // that moved more than a given distance.
- if (!mTouchExploreGestureInProgress) {
+ // We have not started sending events since we try to
+ // figure out what the user is doing.
+ if (mSendHoverEnterDelayed.isPending()) {
+ // Pre-feed the motion events to the gesture detector since we
+ // have a distance slop before getting into gesture detection
+ // mode and not using the points within this slop significantly
+ // decreases the quality of gesture recognition.
+ handleMotionEventGestureDetecting(event, policyFlags);
+
final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
- event.getX(pointerIndex);
final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
- event.getY(pointerIndex);
final double moveDelta = Math.hypot(deltaX, deltaY);
-
- if (moveDelta > mTouchExplorationTapSlop) {
-
+ // The user has moved enough for us to decide.
+ if (moveDelta > mDoubleTapSlop) {
+ // Check whether the user is performing a gesture. We
+ // detect gestures if the pointer is moving above a
+ // given velocity.
mVelocityTracker.computeCurrentVelocity(1000);
final float maxAbsVelocity = Math.max(
Math.abs(mVelocityTracker.getXVelocity(pointerId)),
Math.abs(mVelocityTracker.getYVelocity(pointerId)));
- // TODO: Tune the velocity cut off and add a constant.
- if (maxAbsVelocity > 1000) {
- clear(event, policyFlags);
+ if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
+ // We have to perform gesture detection, so
+ // clear the current state and try to detect.
mCurrentState = STATE_GESTURE_DETECTING;
- event.setAction(MotionEvent.ACTION_DOWN);
- handleMotionEventGestureDetecting(event, policyFlags);
- return;
- }
-
- mTouchExploreGestureInProgress = true;
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
- // Make sure the scheduled down/move event is sent.
- mSendHoverDelayed.forceSendAndRemove();
- mPerformLongPressDelayed.remove();
- // If we have transitioned to exploring state from another one
- // we need to send a hover enter event here.
- final int lastAction = injectedTracker.getLastInjectedHoverAction();
- if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER,
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ } else {
+ // We have just decided that the user is touch,
+ // exploring so start sending events.
+ mSendHoverEnterDelayed.forceSendAndRemove();
+ mSendHoverExitDelayed.remove();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
pointerIdBits, policyFlags);
}
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
- policyFlags);
+ break;
}
} else {
- // Touch exploration gesture in progress so send a hover event.
+ // The user is wither double tapping or performing long
+ // press so do not send move events yet.
+ if (mDoubleTapDetector.firstTapDetected()) {
+ break;
+ }
+ sendEnterEventsIfNeeded(policyFlags);
sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
policyFlags);
}
-
- // If the exploring pointer moved enough => cancel the long press.
- if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null
- && mPerformLongPressDelayed.isPenidng()) {
-
- // If the pointer moved more than the tap slop => cancel long press.
- final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
- - event.getX(pointerIndex);
- final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
- - event.getY(pointerIndex);
- final float moveDelta = (float) Math.hypot(deltaX, deltaY);
- if (moveDelta > mTouchExplorationTapSlop) {
- mLastTouchExploreEvent = null;
- mPerformLongPressDelayed.remove();
- break;
- }
- }
} break;
case 2: {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- // We want to no longer hover over the location so subsequent
- // touch at the same spot will generate a hover enter.
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
+ // More than one pointer so the user is not touch exploring
+ // and now we have to decide whether to delegate or drag.
+ if (mSendHoverEnterDelayed.isPending()) {
+ // We have not started sending events so cancel
+ // scheduled sending events.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ } else {
+ // If the user is touch exploring the second pointer may be
+ // performing a double tap to activate an item without need
+ // for the user to lift his exploring finger.
+ final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
+ - event.getX(pointerIndex);
+ final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
+ - event.getY(pointerIndex);
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ if (moveDelta < mDoubleTapSlop) {
+ break;
+ }
+ // We are sending events so send exit and gesture
+ // end since we transition to another state.
+ sendExitEventsIfNeeded(policyFlags);
+ }
+
+ // We know that a new state transition is to happen and the
+ // new state will not be gesture recognition, so clear the
+ // stashed gesture strokes.
+ mStrokeBuffer.clear();
if (isDraggingGesture(event)) {
// Two pointers moving in the same direction within
// a given distance perform a drag.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
mCurrentState = STATE_DRAGGING;
- if (mTouchExploreGestureInProgress) {
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
- mTouchExploreGestureInProgress = false;
- }
- mLastTouchExploreEvent = null;
mDraggingPointerId = pointerId;
sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
policyFlags);
} else {
// Two pointers moving arbitrary are delegated to the view hierarchy.
mCurrentState = STATE_DELEGATING;
- mSendHoverDelayed.remove();
- if (mTouchExploreGestureInProgress) {
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
- mTouchExploreGestureInProgress = false;
- }
- mLastTouchExploreEvent = null;
sendDownForAllActiveNotInjectedPointers(event, policyFlags);
}
} break;
default: {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- // We want to no longer hover over the location so subsequent
- // touch at the same spot will generate a hover enter.
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
+ // More than one pointer so the user is not touch exploring
+ // and now we have to decide whether to delegate or drag.
+ if (mSendHoverEnterDelayed.isPending()) {
+ // We have not started sending events so cancel
+ // scheduled sending events.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ } else {
+ // We are sending events so send exit and gesture
+ // end since we transition to another state.
+ sendExitEventsIfNeeded(policyFlags);
+ }
// More than two pointers are delegated to the view hierarchy.
mCurrentState = STATE_DELEGATING;
- mSendHoverDelayed.remove();
- if (mTouchExploreGestureInProgress) {
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
- mTouchExploreGestureInProgress = false;
- }
- mLastTouchExploreEvent = null;
sendDownForAllActiveNotInjectedPointers(event, policyFlags);
}
}
} break;
case MotionEvent.ACTION_UP:
+ // We know that we do not need the pre-fed gesture points are not
+ // needed anymore since the last pointer just went up.
+ mStrokeBuffer.clear();
+ //$FALL-THROUGH$
case MotionEvent.ACTION_POINTER_UP: {
final int pointerId = receivedTracker.getLastReceivedUpPointerId();
final int pointerIdBits = (1 << pointerId);
@@ -413,59 +497,12 @@
mPerformLongPressDelayed.remove();
- // If touch exploring announce the end of the gesture.
- // Also do not click on the last explored location.
- if (mTouchExploreGestureInProgress) {
- mTouchExploreGestureInProgress = false;
- mSendHoverDelayed.forceSendAndRemove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- mLastTouchExploreEvent = MotionEvent.obtain(event);
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
- break;
- }
-
- // Detect whether to activate i.e. click on the last explored location.
- if (mLastTouchExploreEvent != null) {
- // If the down was not in the time slop => nothing else to do.
- final long eventTime =
- receivedTracker.getLastReceivedUpPointerDownTime();
- final long exploreTime = mLastTouchExploreEvent.getEventTime();
- final long deltaTime = eventTime - exploreTime;
- if (deltaTime > ACTIVATION_TIME_SLOP) {
- mSendHoverDelayed.forceSendAndRemove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- mLastTouchExploreEvent = MotionEvent.obtain(event);
- break;
- }
-
- // If a tap is farther than the tap slop => nothing to do.
- final int pointerIndex = event.findPointerIndex(pointerId);
- final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
- - event.getX(pointerIndex);
- final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
- - event.getY(pointerIndex);
- final float deltaMove = (float) Math.hypot(deltaX, deltaY);
- if (deltaMove > mTouchExplorationTapSlop) {
- mSendHoverDelayed.forceSendAndRemove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- mLastTouchExploreEvent = MotionEvent.obtain(event);
- break;
- }
-
- // This is a tap so do not send hover events since
- // this events will result in firing the corresponding
- // accessibility events confusing the user about what
- // is actually clicked.
- mSendHoverDelayed.remove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
-
- // All preconditions are met, so click the last explored location.
- sendActionDownAndUp(mLastTouchExploreEvent, policyFlags);
- mLastTouchExploreEvent = null;
+ // If we have not delivered the enter schedule exit.
+ if (mSendHoverEnterDelayed.isPending()) {
+ mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
} else {
- mSendHoverDelayed.forceSendAndRemove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- mLastTouchExploreEvent = MotionEvent.obtain(event);
+ // The user is touch exploring so we send events for end.
+ sendExitEventsIfNeeded(policyFlags);
}
} break;
}
@@ -475,16 +512,7 @@
}
} break;
case MotionEvent.ACTION_CANCEL: {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- final int pointerId = receivedTracker.getPrimaryActivePointerId();
- final int pointerIdBits = (1 << pointerId);
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- clear();
- if (mVelocityTracker != null) {
- mVelocityTracker.clear();
- mVelocityTracker = null;
- }
+ clear(event, policyFlags);
} break;
}
}
@@ -517,6 +545,28 @@
} break;
case 2: {
if (isDraggingGesture(event)) {
+ // If the dragging pointer are closer that a given distance we
+ // use the location of the primary one. Otherwise, we take the
+ // middle between the pointers.
+ int[] pointerIds = mTempPointerIds;
+ mReceivedPointerTracker.populateActivePointerIds(pointerIds);
+
+ final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
+ final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
+
+ final float firstPtrX = event.getX(firstPtrIndex);
+ final float firstPtrY = event.getY(firstPtrIndex);
+ final float secondPtrX = event.getX(secondPtrIndex);
+ final float secondPtrY = event.getY(secondPtrIndex);
+
+ final float deltaX = firstPtrX - secondPtrX;
+ final float deltaY = firstPtrY - secondPtrY;
+ final double distance = Math.hypot(deltaX, deltaY);
+
+ if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
+ event.setLocation(deltaX / 2, deltaY / 2);
+ }
+
// If still dragging send a drag event.
sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
policyFlags);
@@ -557,7 +607,7 @@
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
case MotionEvent.ACTION_CANCEL: {
- clear();
+ clear(event, policyFlags);
} break;
}
}
@@ -574,9 +624,6 @@
throw new IllegalStateException("Delegating state can only be reached if "
+ "there is at least one pointer down!");
}
- case MotionEvent.ACTION_UP: {
- mCurrentState = STATE_TOUCH_EXPLORING;
- } break;
case MotionEvent.ACTION_MOVE: {
// Check whether some other pointer became active because they have moved
// a given distance and if such exist send them to the view hierarchy
@@ -587,30 +634,24 @@
sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
}
} break;
+ case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: {
+ mLongPressingPointerId = -1;
+ mLongPressingPointerDeltaX = 0;
+ mLongPressingPointerDeltaY = 0;
// No active pointers => go to initial state.
if (mReceivedPointerTracker.getActivePointerCount() == 0) {
mCurrentState = STATE_TOUCH_EXPLORING;
}
} break;
case MotionEvent.ACTION_CANCEL: {
- clear();
+ clear(event, policyFlags);
} break;
}
// Deliver the event striping out inactive pointers.
sendMotionEventStripInactivePointers(event, policyFlags);
}
- private float mPreviousX;
- private float mPreviousY;
-
- private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
- private static final int TOUCH_TOLERANCE = 3;
- private static final float MIN_PREDICTION_SCORE = 2.0f;
-
- private GestureLibrary mGestureLibrary;
-
private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
@@ -631,8 +672,7 @@
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
}
} break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_POINTER_UP: {
+ case MotionEvent.ACTION_UP: {
float x = event.getX();
float y = event.getY();
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
@@ -650,7 +690,7 @@
}
try {
final int gestureId = Integer.parseInt(bestPrediction.name);
- mGestureListener.onGesture(gestureId);
+ mAms.onGesture(gestureId);
} catch (NumberFormatException nfe) {
Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
}
@@ -661,8 +701,7 @@
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
case MotionEvent.ACTION_CANCEL: {
- mStrokeBuffer.clear();
- mCurrentState = STATE_TOUCH_EXPLORING;
+ clear(event, policyFlags);
} break;
}
}
@@ -706,17 +745,32 @@
}
/**
- * Ensures that hover exit has been sent.
+ * Sends the exit events if needed. Such events are hover exit and touch explore
+ * gesture end.
*
- * @param prototype The prototype from which to create the injected events.
- * @param pointerIdBits The bits of the pointers to send.
* @param policyFlags The policy flags associated with the event.
*/
- private void ensureHoverExitSent(MotionEvent prototype, int pointerIdBits, int policyFlags) {
- final int lastAction = mInjectedPointerTracker.getLastInjectedHoverAction();
- if (lastAction != MotionEvent.ACTION_HOVER_EXIT) {
- sendMotionEvent(prototype, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits,
- policyFlags);
+ private void sendExitEventsIfNeeded(int policyFlags) {
+ MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+ if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
+ final int pointerIdBits = event.getPointerIdBits();
+ mAms.touchExplorationGestureEnded();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
+ }
+ }
+
+ /**
+ * Sends the enter events if needed. Such events are hover enter and touch explore
+ * gesture start.
+ *
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendEnterEventsIfNeeded(int policyFlags) {
+ MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+ if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
+ final int pointerIdBits = event.getPointerIdBits();
+ mAms.touchExplorationGestureStarted();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
}
}
@@ -826,6 +880,36 @@
event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
}
+ // If the user is long pressing but the long pressing pointer
+ // was not exactly over the accessibility focused item we need
+ // to remap the location of that pointer so the user does not
+ // have to explicitly touch explore something to be able to
+ // long press it, or even worse to avoid the user long pressing
+ // on the wrong item since click and long press behave differently.
+ if (mLongPressingPointerId >= 0) {
+ final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
+ final int pointerCount = event.getPointerCount();
+ PointerProperties[] props = PointerProperties.createArray(pointerCount);
+ PointerCoords[] coords = PointerCoords.createArray(pointerCount);
+ for (int i = 0; i < pointerCount; i++) {
+ event.getPointerProperties(i, props[i]);
+ event.getPointerCoords(i, coords[i]);
+ if (i == remappedIndex) {
+ coords[i].x -= mLongPressingPointerDeltaX;
+ coords[i].y -= mLongPressingPointerDeltaY;
+ }
+ }
+ MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
+ event.getEventTime(), event.getAction(), event.getPointerCount(),
+ props, coords, event.getMetaState(), event.getButtonState(),
+ 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
+ event.getSource(), event.getFlags());
+ if (event != prototype) {
+ event.recycle();
+ }
+ event = remapped;
+ }
+
if (DEBUG) {
Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
@@ -878,6 +962,172 @@
}
}
+ private class DoubleTapDetector {
+ private MotionEvent mDownEvent;
+ private MotionEvent mFirstTapEvent;
+
+ public void onMotionEvent(MotionEvent event, int policyFlags) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ if (mFirstTapEvent != null && !isSamePointerContext(mFirstTapEvent, event)) {
+ clear();
+ }
+ mDownEvent = MotionEvent.obtain(event);
+ } break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP: {
+ if (mDownEvent == null) {
+ return;
+ }
+ if (!isSamePointerContext(mDownEvent, event)) {
+ clear();
+ return;
+ }
+ if (isTap(mDownEvent, event)) {
+ if (mFirstTapEvent == null || isTimedOut(mFirstTapEvent, event,
+ mDoubleTapTimeout)) {
+ mFirstTapEvent = MotionEvent.obtain(event);
+ mDownEvent.recycle();
+ mDownEvent = null;
+ return;
+ }
+ if (isDoubleTap(mFirstTapEvent, event)) {
+ onDoubleTap(event, policyFlags);
+ mFirstTapEvent.recycle();
+ mFirstTapEvent = null;
+ mDownEvent.recycle();
+ mDownEvent = null;
+ return;
+ }
+ mFirstTapEvent.recycle();
+ mFirstTapEvent = null;
+ } else {
+ if (mFirstTapEvent != null) {
+ mFirstTapEvent.recycle();
+ mFirstTapEvent = null;
+ }
+ }
+ mDownEvent.recycle();
+ mDownEvent = null;
+ } break;
+ }
+ }
+
+ public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
+ // This should never be called when more than two pointers are down.
+ if (secondTapUp.getPointerCount() > 2) {
+ return;
+ }
+
+ // Remove pending event deliveries.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+
+ // This is a tap so do not send hover events since
+ // this events will result in firing the corresponding
+ // accessibility events confusing the user about what
+ // is actually clicked.
+ sendExitEventsIfNeeded(policyFlags);
+
+ // If the last touched explored location is not within the focused
+ // window we will click at that exact spot, otherwise we find the
+ // accessibility focus and if the tap is within its bounds we click
+ // there, otherwise we pick the middle of the focus rectangle.
+ MotionEvent lastEvent = mInjectedPointerTracker.getLastInjectedHoverEvent();
+ if (lastEvent == null) {
+ return;
+ }
+
+ final int exploreLocationX = (int) lastEvent.getX(lastEvent.getActionIndex());
+ final int exploreLocationY = (int) lastEvent.getY(lastEvent.getActionIndex());
+
+ Rect bounds = mTempRect;
+ boolean useLastHoverLocation = false;
+
+ final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
+ final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
+ if (mAms.getAccessibilityFocusBounds(exploreLocationX, exploreLocationY, bounds)) {
+ // If the user's last touch explored location is not
+ // within the accessibility focus bounds we use the center
+ // of the accessibility focused rectangle.
+ if (!bounds.contains((int) secondTapUp.getX(pointerIndex),
+ (int) secondTapUp.getY(pointerIndex))) {
+ useLastHoverLocation = true;
+ }
+ }
+
+ // Do the click.
+ PointerProperties[] properties = new PointerProperties[1];
+ properties[0] = new PointerProperties();
+ secondTapUp.getPointerProperties(pointerIndex, properties[0]);
+ PointerCoords[] coords = new PointerCoords[1];
+ coords[0] = new PointerCoords();
+ coords[0].x = (useLastHoverLocation) ? bounds.centerX() : exploreLocationX;
+ coords[0].y = (useLastHoverLocation) ? bounds.centerY() : exploreLocationY;
+ MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(),
+ secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
+ coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0,
+ secondTapUp.getSource(), secondTapUp.getFlags());
+ sendActionDownAndUp(event, policyFlags);
+ event.recycle();
+ }
+
+ public void clear() {
+ if (mDownEvent != null) {
+ mDownEvent.recycle();
+ mDownEvent = null;
+ }
+ if (mFirstTapEvent != null) {
+ mFirstTapEvent.recycle();
+ mFirstTapEvent = null;
+ }
+ }
+
+ public boolean isTap(MotionEvent down, MotionEvent up) {
+ return eventsWithinTimeoutAndDistance(down, up, mTapTimeout, mTouchSlop);
+ }
+
+ private boolean isDoubleTap(MotionEvent firstUp, MotionEvent secondUp) {
+ return eventsWithinTimeoutAndDistance(firstUp, secondUp, mDoubleTapTimeout,
+ mDoubleTapSlop);
+ }
+
+ private boolean eventsWithinTimeoutAndDistance(MotionEvent first, MotionEvent second,
+ int timeout, int distance) {
+ if (isTimedOut(first, second, timeout)) {
+ return false;
+ }
+ final int downPtrIndex = first.getActionIndex();
+ final int upPtrIndex = second.getActionIndex();
+ final float deltaX = second.getX(upPtrIndex) - first.getX(downPtrIndex);
+ final float deltaY = second.getY(upPtrIndex) - first.getY(downPtrIndex);
+ final double deltaMove = Math.hypot(deltaX, deltaY);
+ if (deltaMove >= distance) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
+ final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
+ return (deltaTime >= timeout);
+ }
+
+ private boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
+ return (first.getPointerIdBits() == second.getPointerIdBits()
+ && first.getPointerId(first.getActionIndex())
+ == second.getPointerId(second.getActionIndex()));
+ }
+
+ public boolean firstTapDetected() {
+ return mFirstTapEvent != null
+ && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
+ }
+ }
+
/**
* Determines whether a two pointer gesture is a dragging one.
*
@@ -940,30 +1190,6 @@
return true;
}
- /**
- * Sends an event announcing the start/end of a touch exploration gesture.
- *
- * @param eventType The type of the event to send.
- */
- private void sendAccessibilityEvent(int eventType) {
- AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
-
- /**
- * Clears the internal state of this explorer.
- */
- public void clear() {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- mReceivedPointerTracker.clear();
- mInjectedPointerTracker.clear();
- mLastTouchExploreEvent = null;
- mCurrentState = STATE_TOUCH_EXPLORING;
- mTouchExploreGestureInProgress = false;
- mDraggingPointerId = INVALID_POINTER_ID;
- }
-
/**
* Gets the symbolic name of a state.
*
@@ -1002,10 +1228,10 @@
private MotionEvent mEvent;
private int mPolicyFlags;
- public void post(MotionEvent prototype, int policyFlags, long delay) {
+ public void post(MotionEvent prototype, int policyFlags) {
mEvent = MotionEvent.obtain(prototype);
mPolicyFlags = policyFlags;
- mHandler.postDelayed(this, delay);
+ mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout());
}
public void remove() {
@@ -1021,16 +1247,29 @@
@Override
public void run() {
- mCurrentState = STATE_DELEGATING;
- // Make sure the scheduled hover exit is delivered.
- mSendHoverDelayed.remove();
+ final int pointerIndex = mEvent.getActionIndex();
+ final int eventX = (int) mEvent.getX(pointerIndex);
+ final int eventY = (int) mEvent.getY(pointerIndex);
+ Rect bounds = mTempRect;
+ if (mAms.getAccessibilityFocusBounds(eventX, eventY, bounds)
+ && !bounds.contains(eventX, eventY)) {
+ mLongPressingPointerId = mEvent.getPointerId(pointerIndex);
+ mLongPressingPointerDeltaX = eventX - bounds.centerX();
+ mLongPressingPointerDeltaY = eventY - bounds.centerY();
+ } else {
+ mLongPressingPointerId = -1;
+ mLongPressingPointerDeltaX = 0;
+ mLongPressingPointerDeltaY = 0;
+ }
+ // We are sending events so send exit and gesture
+ // end since we transition to another state.
final int pointerId = mReceivedPointerTracker.getPrimaryActivePointerId();
final int pointerIdBits = (1 << pointerId);
- ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags);
+ mAms.touchExplorationGestureEnded();
+ sendExitEventsIfNeeded(mPolicyFlags);
+ mCurrentState = STATE_DELEGATING;
sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
- mTouchExploreGestureInProgress = false;
- mLastTouchExploreEvent = null;
clear();
}
@@ -1047,20 +1286,41 @@
/**
* Class for delayed sending of hover events.
*/
- private final class SendHoverDelayed implements Runnable {
- private MotionEvent mEvent;
- private int mAction;
+ class SendHoverDelayed implements Runnable {
+ private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName();
+
+ private final int mHoverAction;
+ private final boolean mGestureStarted;
+
+ private MotionEvent mPrototype;
private int mPointerIdBits;
private int mPolicyFlags;
- public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags,
- long delay) {
+ public SendHoverDelayed(int hoverAction, boolean gestureStarted) {
+ mHoverAction = hoverAction;
+ mGestureStarted = gestureStarted;
+ }
+
+ public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) {
remove();
- mEvent = MotionEvent.obtain(prototype);
- mAction = action;
+ mPrototype = MotionEvent.obtain(prototype);
mPointerIdBits = pointerIdBits;
mPolicyFlags = policyFlags;
- mHandler.postDelayed(this, delay);
+ mHandler.postDelayed(this, mTapTimeout);
+ }
+
+ public float getX() {
+ if (isPending()) {
+ return mPrototype.getX();
+ }
+ return 0;
+ }
+
+ public float getY() {
+ if (isPending()) {
+ return mPrototype.getY();
+ }
+ return 0;
}
public void remove() {
@@ -1068,23 +1328,22 @@
clear();
}
- private boolean isPenidng() {
- return (mEvent != null);
+ private boolean isPending() {
+ return (mPrototype != null);
}
private void clear() {
- if (!isPenidng()) {
+ if (!isPending()) {
return;
}
- mEvent.recycle();
- mEvent = null;
- mAction = 0;
+ mPrototype.recycle();
+ mPrototype = null;
mPointerIdBits = -1;
mPolicyFlags = 0;
}
public void forceSendAndRemove() {
- if (isPenidng()) {
+ if (isPending()) {
run();
remove();
}
@@ -1092,16 +1351,17 @@
public void run() {
if (DEBUG) {
- if (mAction == MotionEvent.ACTION_HOVER_ENTER) {
- Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER);
- } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) {
- Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE");
- } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) {
- Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT");
- }
+ Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: "
+ + MotionEvent.actionToString(mHoverAction));
+ Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ?
+ "touchExplorationGestureStarted" : "touchExplorationGestureEnded");
}
-
- sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags);
+ if (mGestureStarted) {
+ mAms.touchExplorationGestureStarted();
+ } else {
+ mAms.touchExplorationGestureEnded();
+ }
+ sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
clear();
}
}
@@ -1120,8 +1380,8 @@
// The time of the last injected down.
private long mLastInjectedDownEventTime;
- // The action of the last injected hover event.
- private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT;
+ // The last injected hover event.
+ private MotionEvent mLastInjectedHoverEvent;
/**
* Processes an injected {@link MotionEvent} event.
@@ -1150,11 +1410,14 @@
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_HOVER_EXIT: {
- mLastInjectedHoverEventAction = event.getActionMasked();
+ if (mLastInjectedHoverEvent != null) {
+ mLastInjectedHoverEvent.recycle();
+ }
+ mLastInjectedHoverEvent = MotionEvent.obtain(event);
} break;
}
if (DEBUG) {
- Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer: " + toString());
+ Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
}
}
@@ -1198,10 +1461,10 @@
}
/**
- * @return The action of the last injected hover event.
+ * @return The the last injected hover event.
*/
- public int getLastInjectedHoverAction() {
- return mLastInjectedHoverEventAction;
+ public MotionEvent getLastInjectedHoverEvent() {
+ return mLastInjectedHoverEvent;
}
@Override
@@ -1260,6 +1523,8 @@
private float mLastReceivedUpPointerDownX;
private float mLastReceivedUpPointerDownY;
+ private MotionEvent mLastReceivedEvent;
+
/**
* Creates a new instance.
*
@@ -1294,6 +1559,11 @@
* @param event The event to process.
*/
public void onMotionEvent(MotionEvent event) {
+ if (mLastReceivedEvent != null) {
+ mLastReceivedEvent.recycle();
+ }
+ mLastReceivedEvent = MotionEvent.obtain(event);
+
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
@@ -1318,6 +1588,13 @@
}
/**
+ * @return The last received event.
+ */
+ public MotionEvent getLastReceivedEvent() {
+ return mLastReceivedEvent;
+ }
+
+ /**
* @return The number of received pointers that are down.
*/
public int getReceivedPointerDownCount() {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 76016f4..6464d7f 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4639,35 +4639,12 @@
pid = tlsIdentity.pid;
}
- // Root, system server and our own process get to do everything.
- if (uid == 0 || uid == Process.SYSTEM_UID || pid == MY_PID) {
+ if (pid == MY_PID) {
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;
+
+ return ActivityManager.checkComponentPermission(permission, uid,
+ owningUid, exported);
}
/**
@@ -13544,6 +13521,14 @@
}
}
+ public int getLaunchedFromUid(IBinder activityToken) {
+ ActivityRecord srec = ActivityRecord.forToken(activityToken);
+ if (srec == null) {
+ return -1;
+ }
+ return srec.launchedFromUid;
+ }
+
// =========================================================
// LIFETIME MANAGEMENT
// =========================================================
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 4f1f76f..d85facc 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -16,16 +16,16 @@
package com.android.server.input;
-import com.android.internal.os.AtomicFile;
-import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.R;
import com.android.internal.util.XmlUtils;
import com.android.server.Watchdog;
import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
@@ -70,23 +70,19 @@
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
+import android.widget.Toast;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
-import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
-import libcore.io.IoUtils;
import libcore.io.Streams;
import libcore.util.Objects;
@@ -95,11 +91,15 @@
*/
public class InputManagerService extends IInputManager.Stub implements Watchdog.Monitor {
static final String TAG = "InputManager";
- static final boolean DEBUG = true;
+ static final boolean DEBUG = false;
private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
+ private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
+ private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
+ private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+ private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
// Pointer to native input manager service object.
private final int mPtr;
@@ -109,6 +109,7 @@
private final InputManagerHandler mHandler;
private boolean mSystemReady;
private BluetoothService mBluetoothService;
+ private NotificationManager mNotificationManager;
// Persistent data store. Must be locked each time during use.
private final PersistentDataStore mDataStore = new PersistentDataStore();
@@ -122,6 +123,11 @@
private final ArrayList<InputDevicesChangedListenerRecord>
mTempInputDevicesChangedListenersToNotify =
new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
+ private final ArrayList<InputDevice>
+ mTempFullKeyboards = new ArrayList<InputDevice>(); // handler thread only
+ private boolean mKeyboardLayoutNotificationShown;
+ private PendingIntent mKeyboardLayoutIntent;
+ private Toast mSwitchedKeyboardLayoutToast;
// State for vibrator tokens.
private Object mVibratorLock = new Object();
@@ -235,6 +241,8 @@
Slog.d(TAG, "System ready.");
}
mBluetoothService = bluetoothService;
+ mNotificationManager = (NotificationManager)mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
mSystemReady = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -244,10 +252,7 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Slog.d(TAG, "Packages changed, reloading keyboard layouts.");
- }
- reloadKeyboardLayouts();
+ updateKeyboardLayouts();
}
}, filter, null, mHandler);
@@ -255,22 +260,25 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Slog.d(TAG, "Bluetooth alias changed, reloading device names.");
- }
reloadDeviceAliases();
}
}, filter, null, mHandler);
- reloadKeyboardLayouts();
- reloadDeviceAliases();
+ mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
+ mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
}
private void reloadKeyboardLayouts() {
+ if (DEBUG) {
+ Slog.d(TAG, "Reloading keyboard layouts.");
+ }
nativeReloadKeyboardLayouts(mPtr);
}
private void reloadDeviceAliases() {
+ if (DEBUG) {
+ Slog.d(TAG, "Reloading device names.");
+ }
nativeReloadDeviceAliases(mPtr);
}
@@ -558,9 +566,11 @@
}
// Must be called on handler.
- private void deliverInputDevicesChanged() {
+ private void deliverInputDevicesChanged(InputDevice[] oldInputDevices) {
+ // Scan for changes.
+ int numFullKeyboardsAdded = 0;
mTempInputDevicesChangedListenersToNotify.clear();
-
+ mTempFullKeyboards.clear();
final int numListeners;
final int[] deviceIdAndGeneration;
synchronized (mInputDevicesLock) {
@@ -581,13 +591,126 @@
final InputDevice inputDevice = mInputDevices[i];
deviceIdAndGeneration[i * 2] = inputDevice.getId();
deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();
+
+ if (isFullKeyboard(inputDevice)) {
+ if (!containsInputDeviceWithDescriptor(oldInputDevices,
+ inputDevice.getDescriptor())) {
+ mTempFullKeyboards.add(numFullKeyboardsAdded++, inputDevice);
+ } else {
+ mTempFullKeyboards.add(inputDevice);
+ }
+ }
}
}
+ // Notify listeners.
for (int i = 0; i < numListeners; i++) {
mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
deviceIdAndGeneration);
}
+ mTempInputDevicesChangedListenersToNotify.clear();
+
+ // Check for missing keyboard layouts.
+ if (mNotificationManager != null) {
+ final int numFullKeyboards = mTempFullKeyboards.size();
+ boolean missingLayoutForExternalKeyboard = false;
+ boolean missingLayoutForExternalKeyboardAdded = false;
+ synchronized (mDataStore) {
+ for (int i = 0; i < numFullKeyboards; i++) {
+ final InputDevice inputDevice = mTempFullKeyboards.get(i);
+ if (mDataStore.getCurrentKeyboardLayout(inputDevice.getDescriptor()) == null) {
+ missingLayoutForExternalKeyboard = true;
+ if (i < numFullKeyboardsAdded) {
+ missingLayoutForExternalKeyboardAdded = true;
+ }
+ }
+ }
+ }
+ if (missingLayoutForExternalKeyboard) {
+ if (missingLayoutForExternalKeyboardAdded) {
+ showMissingKeyboardLayoutNotification();
+ }
+ } else if (mKeyboardLayoutNotificationShown) {
+ hideMissingKeyboardLayoutNotification();
+ }
+ }
+ mTempFullKeyboards.clear();
+ }
+
+ // Must be called on handler.
+ private void showMissingKeyboardLayoutNotification() {
+ if (!mKeyboardLayoutNotificationShown) {
+ if (mKeyboardLayoutIntent == null) {
+ final Intent intent = new Intent("android.settings.INPUT_METHOD_SETTINGS");
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mKeyboardLayoutIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ }
+
+ Resources r = mContext.getResources();
+ Notification notification = new Notification.Builder(mContext)
+ .setContentTitle(r.getString(
+ R.string.select_keyboard_layout_notification_title))
+ .setContentText(r.getString(
+ R.string.select_keyboard_layout_notification_message))
+ .setContentIntent(mKeyboardLayoutIntent)
+ .setSmallIcon(R.drawable.ic_settings_language)
+ .setPriority(Notification.PRIORITY_LOW)
+ .build();
+ mNotificationManager.notify(R.string.select_keyboard_layout_notification_title,
+ notification);
+ mKeyboardLayoutNotificationShown = true;
+ }
+ }
+
+ // Must be called on handler.
+ private void hideMissingKeyboardLayoutNotification() {
+ if (mKeyboardLayoutNotificationShown) {
+ mKeyboardLayoutNotificationShown = false;
+ mNotificationManager.cancel(R.string.select_keyboard_layout_notification_title);
+ }
+ }
+
+ // Must be called on handler.
+ private void updateKeyboardLayouts() {
+ // Scan all input devices state for keyboard layouts that have been uninstalled.
+ final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+ visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+ @Override
+ public void visitKeyboardLayout(Resources resources,
+ String descriptor, String label, String collection, int keyboardLayoutResId) {
+ availableKeyboardLayouts.add(descriptor);
+ }
+ });
+ synchronized (mDataStore) {
+ try {
+ mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+
+ // Reload keyboard layouts.
+ reloadKeyboardLayouts();
+ }
+
+ private static boolean isFullKeyboard(InputDevice inputDevice) {
+ return !inputDevice.isVirtual()
+ && (inputDevice.getSources() & InputDevice.SOURCE_KEYBOARD) != 0
+ && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC;
+ }
+
+ private static boolean containsInputDeviceWithDescriptor(InputDevice[] inputDevices,
+ String descriptor) {
+ final int numDevices = inputDevices.length;
+ for (int i = 0; i < numDevices; i++) {
+ final InputDevice inputDevice = inputDevices[i];
+ if (inputDevice.getDescriptor().equals(descriptor)) {
+ return true;
+ }
+ }
+ return false;
}
@Override // Binder call
@@ -720,43 +843,147 @@
}
@Override // Binder call
- public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
+ public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
synchronized (mDataStore) {
- return mDataStore.getKeyboardLayout(inputDeviceDescriptor);
+ return mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
}
}
@Override // Binder call
- public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
String keyboardLayoutDescriptor) {
if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
- "setKeyboardLayoutForInputDevice()")) {
+ "setCurrentKeyboardLayoutForInputDevice()")) {
throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
}
-
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
- final boolean changed;
synchronized (mDataStore) {
try {
- changed = mDataStore.setKeyboardLayout(
- inputDeviceDescriptor, keyboardLayoutDescriptor);
+ if (mDataStore.setCurrentKeyboardLayout(
+ inputDeviceDescriptor, keyboardLayoutDescriptor)) {
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ }
} finally {
mDataStore.saveIfNeeded();
}
}
+ }
- if (changed) {
- if (DEBUG) {
- Slog.d(TAG, "Keyboard layout changed, reloading keyboard layouts.");
+ @Override // Binder call
+ public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+
+ synchronized (mDataStore) {
+ return mDataStore.getKeyboardLayouts(inputDeviceDescriptor);
+ }
+ }
+
+ @Override // Binder call
+ public void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+ "addKeyboardLayoutForInputDevice()")) {
+ throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+ }
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
+ synchronized (mDataStore) {
+ try {
+ String oldLayout = mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+ if (mDataStore.addKeyboardLayout(inputDeviceDescriptor, keyboardLayoutDescriptor)
+ && !Objects.equal(oldLayout,
+ mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor))) {
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
}
- reloadKeyboardLayouts();
+ }
+ }
+
+ @Override // Binder call
+ public void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+ "removeKeyboardLayoutForInputDevice()")) {
+ throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+ }
+ if (inputDeviceDescriptor == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
+ synchronized (mDataStore) {
+ try {
+ String oldLayout = mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+ if (mDataStore.removeKeyboardLayout(inputDeviceDescriptor,
+ keyboardLayoutDescriptor)
+ && !Objects.equal(oldLayout,
+ mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor))) {
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
+ public void switchKeyboardLayout(int deviceId, int direction) {
+ mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+ }
+
+ // Must be called on handler.
+ private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+ final InputDevice device = getInputDevice(deviceId);
+ final String inputDeviceDescriptor = device.getDescriptor();
+ if (device != null) {
+ final boolean changed;
+ final String keyboardLayoutDescriptor;
+ synchronized (mDataStore) {
+ try {
+ changed = mDataStore.switchKeyboardLayout(inputDeviceDescriptor, direction);
+ keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
+ inputDeviceDescriptor);
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+
+ if (changed) {
+ if (mSwitchedKeyboardLayoutToast != null) {
+ mSwitchedKeyboardLayoutToast.cancel();
+ mSwitchedKeyboardLayoutToast = null;
+ }
+ if (keyboardLayoutDescriptor != null) {
+ KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
+ if (keyboardLayout != null) {
+ mSwitchedKeyboardLayoutToast = Toast.makeText(
+ mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
+ mSwitchedKeyboardLayoutToast.show();
+ }
+ }
+
+ reloadKeyboardLayouts();
+ }
}
}
@@ -978,12 +1205,13 @@
// Native callback.
private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
synchronized (mInputDevicesLock) {
- mInputDevices = inputDevices;
-
if (!mInputDevicesChangedPending) {
mInputDevicesChangedPending = true;
- mHandler.sendEmptyMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED);
+ mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED,
+ mInputDevices).sendToTarget();
}
+
+ mInputDevices = inputDevices;
}
}
@@ -1132,7 +1360,8 @@
return null;
}
- String keyboardLayoutDescriptor = getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
+ String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(
+ inputDeviceDescriptor);
if (keyboardLayoutDescriptor == null) {
return null;
}
@@ -1203,7 +1432,19 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DELIVER_INPUT_DEVICES_CHANGED:
- deliverInputDevicesChanged();
+ deliverInputDevicesChanged((InputDevice[])msg.obj);
+ break;
+ case MSG_SWITCH_KEYBOARD_LAYOUT:
+ handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+ break;
+ case MSG_RELOAD_KEYBOARD_LAYOUTS:
+ reloadKeyboardLayouts();
+ break;
+ case MSG_UPDATE_KEYBOARD_LAYOUTS:
+ updateKeyboardLayouts();
+ break;
+ case MSG_RELOAD_DEVICE_ALIASES:
+ reloadDeviceAliases();
break;
}
}
@@ -1316,186 +1557,4 @@
onVibratorTokenDied(this);
}
}
-
- /**
- * Manages persistent state recorded by the input manager service as an XML file.
- * Caller must acquire lock on the data store before accessing it.
- *
- * File format:
- * <code>
- * <input-mananger-state>
- * <input-devices>
- * <input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
- * >input-devices>
- * >/input-manager-state>
- * </code>
- */
- private static final class PersistentDataStore {
- // Input device state by descriptor.
- private final HashMap<String, InputDeviceState> mInputDevices =
- new HashMap<String, InputDeviceState>();
- private final AtomicFile mAtomicFile;
-
- // True if the data has been loaded.
- private boolean mLoaded;
-
- // True if there are changes to be saved.
- private boolean mDirty;
-
- public PersistentDataStore() {
- mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
- }
-
- public void saveIfNeeded() {
- if (mDirty) {
- save();
- mDirty = false;
- }
- }
-
- public String getKeyboardLayout(String inputDeviceDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
- return state != null ? state.keyboardLayoutDescriptor : null;
- }
-
- public boolean setKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
- if (!Objects.equal(state.keyboardLayoutDescriptor, keyboardLayoutDescriptor)) {
- state.keyboardLayoutDescriptor = keyboardLayoutDescriptor;
- setDirty();
- return true;
- }
- return false;
- }
-
- private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
- boolean createIfAbsent) {
- loadIfNeeded();
- InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
- if (state == null && createIfAbsent) {
- state = new InputDeviceState();
- mInputDevices.put(inputDeviceDescriptor, state);
- setDirty();
- }
- return state;
- }
-
- private void loadIfNeeded() {
- if (!mLoaded) {
- load();
- mLoaded = true;
- }
- }
-
- private void setDirty() {
- mDirty = true;
- }
-
- private void clearState() {
- mInputDevices.clear();
- }
-
- private void load() {
- clearState();
-
- final InputStream is;
- try {
- is = mAtomicFile.openRead();
- } catch (FileNotFoundException ex) {
- return;
- }
-
- XmlPullParser parser;
- try {
- parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(is), null);
- loadFromXml(parser);
- } catch (IOException ex) {
- Slog.w(TAG, "Failed to load input manager persistent store data.", ex);
- clearState();
- } catch (XmlPullParserException ex) {
- Slog.w(TAG, "Failed to load input manager persistent store data.", ex);
- clearState();
- } finally {
- IoUtils.closeQuietly(is);
- }
- }
-
- private void save() {
- final FileOutputStream os;
- try {
- os = mAtomicFile.startWrite();
- boolean success = false;
- try {
- XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(new BufferedOutputStream(os), "utf-8");
- saveToXml(serializer);
- serializer.flush();
- success = true;
- } finally {
- if (success) {
- mAtomicFile.finishWrite(os);
- } else {
- mAtomicFile.failWrite(os);
- }
- }
- } catch (IOException ex) {
- Slog.w(TAG, "Failed to save input manager persistent store data.", ex);
- }
- }
-
- private void loadFromXml(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- XmlUtils.beginDocument(parser, "input-manager-state");
- final int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("input-devices")) {
- loadInputDevicesFromXml(parser);
- }
- }
- }
-
- private void loadInputDevicesFromXml(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- final int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("input-device")) {
- String descriptor = parser.getAttributeValue(null, "descriptor");
- if (descriptor == null) {
- throw new XmlPullParserException(
- "Missing descriptor attribute on input-device");
- }
- InputDeviceState state = new InputDeviceState();
- state.keyboardLayoutDescriptor =
- parser.getAttributeValue(null, "keyboard-layout");
- mInputDevices.put(descriptor, state);
- }
- }
- }
-
- private void saveToXml(XmlSerializer serializer) throws IOException {
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- serializer.startTag(null, "input-manager-state");
- serializer.startTag(null, "input-devices");
- for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
- final String descriptor = entry.getKey();
- final InputDeviceState state = entry.getValue();
- serializer.startTag(null, "input-device");
- serializer.attribute(null, "descriptor", descriptor);
- if (state.keyboardLayoutDescriptor != null) {
- serializer.attribute(null, "keyboard-layout", state.keyboardLayoutDescriptor);
- }
- serializer.endTag(null, "input-device");
- }
- serializer.endTag(null, "input-devices");
- serializer.endTag(null, "input-manager-state");
- serializer.endDocument();
- }
- }
-
- private static final class InputDeviceState {
- public String keyboardLayoutDescriptor;
- }
}
diff --git a/services/java/com/android/server/input/PersistentDataStore.java b/services/java/com/android/server/input/PersistentDataStore.java
new file mode 100644
index 0000000..fbe3e8b
--- /dev/null
+++ b/services/java/com/android/server/input/PersistentDataStore.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Slog;
+import android.util.Xml;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import libcore.io.IoUtils;
+import libcore.util.Objects;
+
+/**
+ * Manages persistent state recorded by the input manager service as an XML file.
+ * Caller must acquire lock on the data store before accessing it.
+ *
+ * File format:
+ * <code>
+ * <input-mananger-state>
+ * <input-devices>
+ * <input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
+ * >input-devices>
+ * >/input-manager-state>
+ * </code>
+ */
+final class PersistentDataStore {
+ static final String TAG = "InputManager";
+
+ // Input device state by descriptor.
+ private final HashMap<String, InputDeviceState> mInputDevices =
+ new HashMap<String, InputDeviceState>();
+ private final AtomicFile mAtomicFile;
+
+ // True if the data has been loaded.
+ private boolean mLoaded;
+
+ // True if there are changes to be saved.
+ private boolean mDirty;
+
+ public PersistentDataStore() {
+ mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
+ }
+
+ public void saveIfNeeded() {
+ if (mDirty) {
+ save();
+ mDirty = false;
+ }
+ }
+
+ public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ return state != null ? state.getCurrentKeyboardLayout() : null;
+ }
+
+ public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+ if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ if (state == null) {
+ return (String[])ArrayUtils.emptyArray(String.class);
+ }
+ return state.getKeyboardLayouts();
+ }
+
+ public boolean addKeyboardLayout(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+ if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean removeKeyboardLayout(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+ if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ if (state != null && state.switchKeyboardLayout(direction)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
+ boolean changed = false;
+ for (InputDeviceState state : mInputDevices.values()) {
+ if (state.removeUninstalledKeyboardLayouts(availableKeyboardLayouts)) {
+ changed = true;
+ }
+ }
+ if (changed) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
+ boolean createIfAbsent) {
+ loadIfNeeded();
+ InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
+ if (state == null && createIfAbsent) {
+ state = new InputDeviceState();
+ mInputDevices.put(inputDeviceDescriptor, state);
+ setDirty();
+ }
+ return state;
+ }
+
+ private void loadIfNeeded() {
+ if (!mLoaded) {
+ load();
+ mLoaded = true;
+ }
+ }
+
+ private void setDirty() {
+ mDirty = true;
+ }
+
+ private void clearState() {
+ mInputDevices.clear();
+ }
+
+ private void load() {
+ clearState();
+
+ final InputStream is;
+ try {
+ is = mAtomicFile.openRead();
+ } catch (FileNotFoundException ex) {
+ return;
+ }
+
+ XmlPullParser parser;
+ try {
+ parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(is), null);
+ loadFromXml(parser);
+ } catch (IOException ex) {
+ Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
+ clearState();
+ } catch (XmlPullParserException ex) {
+ Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
+ clearState();
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+
+ private void save() {
+ final FileOutputStream os;
+ try {
+ os = mAtomicFile.startWrite();
+ boolean success = false;
+ try {
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(new BufferedOutputStream(os), "utf-8");
+ saveToXml(serializer);
+ serializer.flush();
+ success = true;
+ } finally {
+ if (success) {
+ mAtomicFile.finishWrite(os);
+ } else {
+ mAtomicFile.failWrite(os);
+ }
+ }
+ } catch (IOException ex) {
+ Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex);
+ }
+ }
+
+ private void loadFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ XmlUtils.beginDocument(parser, "input-manager-state");
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("input-devices")) {
+ loadInputDevicesFromXml(parser);
+ }
+ }
+ }
+
+ private void loadInputDevicesFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("input-device")) {
+ String descriptor = parser.getAttributeValue(null, "descriptor");
+ if (descriptor == null) {
+ throw new XmlPullParserException(
+ "Missing descriptor attribute on input-device.");
+ }
+ if (mInputDevices.containsKey(descriptor)) {
+ throw new XmlPullParserException("Found duplicate input device.");
+ }
+
+ InputDeviceState state = new InputDeviceState();
+ state.loadFromXml(parser);
+ mInputDevices.put(descriptor, state);
+ }
+ }
+ }
+
+ private void saveToXml(XmlSerializer serializer) throws IOException {
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startTag(null, "input-manager-state");
+ serializer.startTag(null, "input-devices");
+ for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
+ final String descriptor = entry.getKey();
+ final InputDeviceState state = entry.getValue();
+ serializer.startTag(null, "input-device");
+ serializer.attribute(null, "descriptor", descriptor);
+ state.saveToXml(serializer);
+ serializer.endTag(null, "input-device");
+ }
+ serializer.endTag(null, "input-devices");
+ serializer.endTag(null, "input-manager-state");
+ serializer.endDocument();
+ }
+
+ private static final class InputDeviceState {
+ private String mCurrentKeyboardLayout;
+ private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+
+ public String getCurrentKeyboardLayout() {
+ return mCurrentKeyboardLayout;
+ }
+
+ public boolean setCurrentKeyboardLayout(String keyboardLayout) {
+ if (Objects.equal(mCurrentKeyboardLayout, keyboardLayout)) {
+ return false;
+ }
+ addKeyboardLayout(keyboardLayout);
+ mCurrentKeyboardLayout = keyboardLayout;
+ return true;
+ }
+
+ public String[] getKeyboardLayouts() {
+ if (mKeyboardLayouts.isEmpty()) {
+ return (String[])ArrayUtils.emptyArray(String.class);
+ }
+ return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
+ }
+
+ public boolean addKeyboardLayout(String keyboardLayout) {
+ int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+ if (index >= 0) {
+ return false;
+ }
+ mKeyboardLayouts.add(-index - 1, keyboardLayout);
+ if (mCurrentKeyboardLayout == null) {
+ mCurrentKeyboardLayout = keyboardLayout;
+ }
+ return true;
+ }
+
+ public boolean removeKeyboardLayout(String keyboardLayout) {
+ int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+ if (index < 0) {
+ return false;
+ }
+ mKeyboardLayouts.remove(index);
+ updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
+ return true;
+ }
+
+ private void updateCurrentKeyboardLayoutIfRemoved(
+ String removedKeyboardLayout, int removedIndex) {
+ if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
+ if (!mKeyboardLayouts.isEmpty()) {
+ int index = removedIndex;
+ if (index == mKeyboardLayouts.size()) {
+ index = 0;
+ }
+ mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+ } else {
+ mCurrentKeyboardLayout = null;
+ }
+ }
+ }
+
+ public boolean switchKeyboardLayout(int direction) {
+ final int size = mKeyboardLayouts.size();
+ if (size < 2) {
+ return false;
+ }
+ int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
+ assert index >= 0;
+ if (direction > 0) {
+ index = (index + 1) % size;
+ } else {
+ index = (index + size - 1) % size;
+ }
+ mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+ return true;
+ }
+
+ public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
+ boolean changed = false;
+ for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
+ String keyboardLayout = mKeyboardLayouts.get(i);
+ if (!availableKeyboardLayouts.contains(keyboardLayout)) {
+ Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
+ mKeyboardLayouts.remove(i);
+ updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ public void loadFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals("keyboard-layout")) {
+ String descriptor = parser.getAttributeValue(null, "descriptor");
+ if (descriptor == null) {
+ throw new XmlPullParserException(
+ "Missing descriptor attribute on keyboard-layout.");
+ }
+ String current = parser.getAttributeValue(null, "current");
+ if (mKeyboardLayouts.contains(descriptor)) {
+ throw new XmlPullParserException(
+ "Found duplicate keyboard layout.");
+ }
+
+ mKeyboardLayouts.add(descriptor);
+ if (current != null && current.equals("true")) {
+ if (mCurrentKeyboardLayout != null) {
+ throw new XmlPullParserException(
+ "Found multiple current keyboard layouts.");
+ }
+ mCurrentKeyboardLayout = descriptor;
+ }
+ }
+ }
+
+ // Maintain invariant that layouts are sorted.
+ Collections.sort(mKeyboardLayouts);
+
+ // Maintain invariant that there is always a current keyboard layout unless
+ // there are none installed.
+ if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
+ mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
+ }
+ }
+
+ public void saveToXml(XmlSerializer serializer) throws IOException {
+ for (String layout : mKeyboardLayouts) {
+ serializer.startTag(null, "keyboard-layout");
+ serializer.attribute(null, "descriptor", layout);
+ if (layout.equals(mCurrentKeyboardLayout)) {
+ serializer.attribute(null, "current", "true");
+ }
+ serializer.endTag(null, "keyboard-layout");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index 480992b..cf3a5d2 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -538,8 +538,16 @@
if (mDimAnimator == null) {
mDimAnimator = new DimAnimator(mService.mFxSession);
}
- mService.mH.sendMessage(mService.mH.obtainMessage(SET_DIM_PARAMETERS,
- new DimAnimator.Parameters(winAnimator, width, height, target)));
+ // Only set dim params on the highest dimmed layer.
+ final WindowStateAnimator dimWinAnimator = mDimParams == null
+ ? null : mDimParams.mDimWinAnimator;
+ // Don't turn on for an unshown surface, or for any layer but the highest dimmed one.
+ if (winAnimator.mSurfaceShown &&
+ (dimWinAnimator == null || !dimWinAnimator.mSurfaceShown
+ || dimWinAnimator.mAnimLayer < winAnimator.mAnimLayer)) {
+ mService.mH.sendMessage(mService.mH.obtainMessage(SET_DIM_PARAMETERS,
+ new DimAnimator.Parameters(winAnimator, width, height, target)));
+ }
}
// TODO(cmautner): Move into Handler
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 076ba9a..fbf9256 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -5007,6 +5007,12 @@
// Called by window manager policy. Not exposed externally.
@Override
+ public void switchKeyboardLayout(int deviceId, int direction) {
+ mInputManager.switchKeyboardLayout(deviceId, direction);
+ }
+
+ // Called by window manager policy. Not exposed externally.
+ @Override
public void shutdown() {
ShutdownThread.shutdown(mContext, true);
}
@@ -6561,6 +6567,16 @@
sendScreenStatusToClients();
}
+ public IBinder getFocusedWindowClientToken() {
+ synchronized (mWindowMap) {
+ WindowState windowState = getFocusedWindowLocked();
+ if (windowState != null) {
+ return windowState.mClient.asBinder();
+ }
+ return null;
+ }
+ }
+
private WindowState getFocusedWindow() {
synchronized (mWindowMap) {
return getFocusedWindowLocked();
diff --git a/telephony/java/com/android/internal/telephony/IccCard.java b/telephony/java/com/android/internal/telephony/IccCard.java
index d738d7b..fcf2f92 100644
--- a/telephony/java/com/android/internal/telephony/IccCard.java
+++ b/telephony/java/com/android/internal/telephony/IccCard.java
@@ -579,10 +579,14 @@
mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARD_ADDED, null));
}
- // Call onReady only when SIM or RUIM card becomes ready (not NV)
+ // Call onReady Record(s) on the IccCard becomes ready (not NV)
if (oldState != State.READY && newState == State.READY &&
(is3gpp || isSubscriptionFromIccCard)) {
- mIccFileHandler.setAid(getAid());
+ if (!(mIccFileHandler instanceof CdmaLteUiccFileHandler)) {
+ // CdmaLteUicc File Handler deals with both USIM and CSIM.
+ // Do not lock onto one AID for now.
+ mIccFileHandler.setAid(getAid());
+ }
mIccRecords.onReady();
}
}
diff --git a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
index 69dd666..e4cfb23 100644
--- a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -146,6 +146,7 @@
"ci", // Cote d'Ivoire
"eh", // Western Sahara
"fo", // Faroe Islands, Denmark
+ "gb", // United Kingdom of Great Britain and Northern Ireland
"gh", // Ghana
"gm", // Gambia
"gn", // Guinea
@@ -161,7 +162,6 @@
"sn", // Senegal
"st", // Sao Tome and Principe
"tg", // Togo
- "uk", // U.K
};
/** Reason for registration denial. */
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index e4287c0..25cd112 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -293,8 +293,15 @@
EVENT_RUIM_RECORDS_LOADED, null);
mNeedToRegForRuimLoaded = false;
}
- if (DBG) log("Receive EVENT_RUIM_READY and Send Request getCDMASubscription.");
- getSubscriptionInfoAndStartPollingThreads();
+
+ if (phone.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE) {
+ // Subscription will be read from SIM I/O
+ if (DBG) log("Receive EVENT_RUIM_READY");
+ pollState();
+ } else {
+ if (DBG) log("Receive EVENT_RUIM_READY and Send Request getCDMASubscription.");
+ getSubscriptionInfoAndStartPollingThreads();
+ }
phone.prepareEri();
break;
@@ -878,19 +885,22 @@
// For NITZ string without time zone,
// need adjust time to reflect default time zone setting
zone = TimeZone.getDefault();
- long ctm = System.currentTimeMillis();
- long tzOffset = zone.getOffset(ctm);
- if (DBG) {
- log("fixTimeZone: tzOffset=" + tzOffset + " ltod=" + TimeUtils.logTimeOfDay(ctm));
- }
- if (getAutoTime()) {
- long adj = ctm - tzOffset;
- if (DBG) log("fixTimeZone: adj ltod=" + TimeUtils.logTimeOfDay(adj));
- setAndBroadcastNetworkSetTime(adj);
- } else {
- // Adjust the saved NITZ time to account for tzOffset.
- mSavedTime = mSavedTime - tzOffset;
- if (DBG) log("fixTimeZone: adj mSavedTime=" + mSavedTime);
+ if (mNeedFixZone) {
+ long ctm = System.currentTimeMillis();
+ long tzOffset = zone.getOffset(ctm);
+ if (DBG) {
+ log("fixTimeZone: tzOffset=" + tzOffset +
+ " ltod=" + TimeUtils.logTimeOfDay(ctm));
+ }
+ if (getAutoTime()) {
+ long adj = ctm - tzOffset;
+ if (DBG) log("fixTimeZone: adj ltod=" + TimeUtils.logTimeOfDay(adj));
+ setAndBroadcastNetworkSetTime(adj);
+ } else {
+ // Adjust the saved NITZ time to account for tzOffset.
+ mSavedTime = mSavedTime - tzOffset;
+ if (DBG) log("fixTimeZone: adj mSavedTime=" + mSavedTime);
+ }
}
if (DBG) log("fixTimeZone: using default TimeZone");
} else if (isoCountryCode.equals("")) {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 1aa17c7..b7569da 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -111,7 +111,7 @@
* are in. Keep the time zone information from the NITZ string so
* we can fix the time zone once know the country.
*/
- private boolean mNeedFixZone = false;
+ private boolean mNeedFixZoneAfterNitz = false;
private int mZoneOffset;
private boolean mZoneDst;
private long mZoneTime;
@@ -906,7 +906,7 @@
}
if (shouldFixTimeZoneNow(phone, operatorNumeric, prevOperatorNumeric,
- mNeedFixZone)) {
+ mNeedFixZoneAfterNitz)) {
// If the offset is (0, false) and the timezone property
// is set, use the timezone property rather than
// GMT.
@@ -917,25 +917,35 @@
" iso-cc='" + iso +
"' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
}
+
+ // "(mZoneOffset == 0) && (mZoneDst == false) &&
+ // (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)"
+ // means that we received a NITZ string telling
+ // it is in GMT+0 w/ DST time zone
+ // BUT iso tells is NOT, e.g, a wrong NITZ reporting
+ // local time w/ 0 offset.
if ((mZoneOffset == 0) && (mZoneDst == false) &&
(zoneName != null) && (zoneName.length() > 0) &&
(Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)) {
zone = TimeZone.getDefault();
- // For NITZ string without timezone,
- // need adjust time to reflect default timezone setting
- long ctm = System.currentTimeMillis();
- long tzOffset = zone.getOffset(ctm);
- if (DBG) {
- log("pollStateDone: tzOffset=" + tzOffset + " ltod=" +
+ if (mNeedFixZoneAfterNitz) {
+ // For wrong NITZ reporting local time w/ 0 offset,
+ // need adjust time to reflect default timezone setting
+ long ctm = System.currentTimeMillis();
+ long tzOffset = zone.getOffset(ctm);
+ if (DBG) {
+ log("pollStateDone: tzOffset=" + tzOffset + " ltod=" +
TimeUtils.logTimeOfDay(ctm));
- }
- if (getAutoTime()) {
- long adj = ctm - tzOffset;
- if (DBG) log("pollStateDone: adj ltod=" + TimeUtils.logTimeOfDay(adj));
- setAndBroadcastNetworkSetTime(adj);
- } else {
- // Adjust the saved NITZ time to account for tzOffset.
- mSavedTime = mSavedTime - tzOffset;
+ }
+ if (getAutoTime()) {
+ long adj = ctm - tzOffset;
+ if (DBG) log("pollStateDone: adj ltod=" +
+ TimeUtils.logTimeOfDay(adj));
+ setAndBroadcastNetworkSetTime(adj);
+ } else {
+ // Adjust the saved NITZ time to account for tzOffset.
+ mSavedTime = mSavedTime - tzOffset;
+ }
}
if (DBG) log("pollStateDone: using default TimeZone");
} else if (iso.equals("")){
@@ -948,7 +958,7 @@
if (DBG) log("pollStateDone: using getTimeZone(off, dst, time, iso)");
}
- mNeedFixZone = false;
+ mNeedFixZoneAfterNitz = false;
if (zone != null) {
log("pollStateDone: zone != null zone.getID=" + zone.getID());
@@ -1440,7 +1450,7 @@
// so we don't know how to identify the DST rules yet. Save
// the information and hope to fix it up later.
- mNeedFixZone = true;
+ mNeedFixZoneAfterNitz = true;
mZoneOffset = tzOffset;
mZoneDst = dst != 0;
mZoneTime = c.getTimeInMillis();
@@ -1696,7 +1706,7 @@
pw.println(" mGsmRoaming=" + mGsmRoaming);
pw.println(" mDataRoaming=" + mDataRoaming);
pw.println(" mEmergencyOnly=" + mEmergencyOnly);
- pw.println(" mNeedFixZone=" + mNeedFixZone);
+ pw.println(" mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz);
pw.println(" mZoneOffset=" + mZoneOffset);
pw.println(" mZoneDst=" + mZoneDst);
pw.println(" mZoneTime=" + mZoneTime);
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index ec61403..73705ae 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -56,55 +56,92 @@
return true;
}
+// The default to use if no other ignore pattern is defined.
+const char * const gDefaultIgnoreAssets =
+ "!.svn:!.git:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~";
+// The ignore pattern that can be passed via --ignore-assets in Main.cpp
+const char * gUserIgnoreAssets = NULL;
+
static bool isHidden(const char *root, const char *path)
{
- const char *ext = NULL;
- const char *type = NULL;
+ // Patterns syntax:
+ // - Delimiter is :
+ // - Entry can start with the flag ! to avoid printing a warning
+ // about the file being ignored.
+ // - Entry can have the flag "<dir>" to match only directories
+ // or <file> to match only files. Default is to match both.
+ // - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
+ // where prefix/suffix must have at least 1 character (so that
+ // we don't match a '*' catch-all pattern.)
+ // - The special filenames "." and ".." are always ignored.
+ // - Otherwise the full string is matched.
+ // - match is not case-sensitive.
- // Skip all hidden files.
- if (path[0] == '.') {
- // Skip ., .. and .svn but don't chatter about it.
- if (strcmp(path, ".") == 0
- || strcmp(path, "..") == 0
- || strcmp(path, ".svn") == 0) {
- return true;
- }
- type = "hidden";
- } else if (path[0] == '_') {
- // skip directories starting with _ (don't chatter about it)
- String8 subdirName(root);
- subdirName.appendPath(path);
- if (getFileType(subdirName.string()) == kFileTypeDirectory) {
- return true;
- }
- } else if (strcmp(path, "CVS") == 0) {
- // Skip CVS but don't chatter about it.
+ if (strcmp(path, ".") == 0 || strcmp(path, "..") == 0) {
return true;
- } else if (strcasecmp(path, "thumbs.db") == 0
- || strcasecmp(path, "picasa.ini") == 0) {
- // Skip suspected image indexes files.
- type = "index";
- } else if (path[strlen(path)-1] == '~') {
- // Skip suspected emacs backup files.
- type = "backup";
- } else if ((ext = strrchr(path, '.')) != NULL && strcmp(ext, ".scc") == 0) {
- // Skip VisualSourceSafe files and don't chatter about it
- return true;
- } else {
- // Let everything else through.
- return false;
}
- /* If we get this far, "type" should be set and the file
- * should be skipped.
- */
- String8 subdirName(root);
- subdirName.appendPath(path);
- fprintf(stderr, " (skipping %s %s '%s')\n", type,
- getFileType(subdirName.string())==kFileTypeDirectory ? "dir":"file",
- subdirName.string());
+ const char *delim = ":";
+ const char *p = gUserIgnoreAssets;
+ if (!p || !p[0]) {
+ p = getenv("ANDROID_AAPT_IGNORE");
+ }
+ if (!p || !p[0]) {
+ p = gDefaultIgnoreAssets;
+ }
+ char *patterns = strdup(p);
- return true;
+ bool ignore = false;
+ bool chatty = true;
+ char *matchedPattern = NULL;
+
+ String8 fullPath(root);
+ fullPath.appendPath(path);
+ FileType type = getFileType(fullPath);
+
+ int plen = strlen(path);
+
+ // Note: we don't have strtok_r under mingw.
+ for(char *token = strtok(patterns, delim);
+ !ignore && token != NULL;
+ token = strtok(NULL, delim)) {
+ chatty = token[0] != '!';
+ if (!chatty) token++; // skip !
+ if (strncasecmp(token, "<dir>" , 5) == 0) {
+ if (type != kFileTypeDirectory) continue;
+ token += 5;
+ }
+ if (strncasecmp(token, "<file>", 6) == 0) {
+ if (type != kFileTypeRegular) continue;
+ token += 6;
+ }
+
+ matchedPattern = token;
+ int n = strlen(token);
+
+ if (token[0] == '*') {
+ // Match *suffix
+ token++;
+ if (n <= plen) {
+ ignore = strncasecmp(token, path + plen - n, n) == 0;
+ }
+ } else if (n > 1 && token[n - 1] == '*') {
+ // Match prefix*
+ ignore = strncasecmp(token, path, n - 1) == 0;
+ } else {
+ ignore = strcasecmp(token, path) == 0;
+ }
+ }
+
+ if (ignore && chatty) {
+ fprintf(stderr, " (skipping %s '%s' due to ANDROID_AAPT_IGNORE pattern '%s')\n",
+ type == kFileTypeDirectory ? "dir" : "file",
+ path,
+ matchedPattern ? matchedPattern : "");
+ }
+
+ free(patterns);
+ return ignore;
}
// =========================================================================
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index 5924952..d5f296c 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -22,6 +22,10 @@
using namespace android;
+
+extern const char * const gDefaultIgnoreAssets;
+extern const char * gUserIgnoreAssets;
+
bool valid_symbol_name(const String8& str);
class AaptAssets;
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 50c828d..9f05c6a 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -178,7 +178,11 @@
" --non-constant-id\n"
" Make the resources ID non constant. This is required to make an R java class\n"
" that does not contain the final value but is used to make reusable compiled\n"
- " libraries that need to access resources.\n");
+ " libraries that need to access resources.\n"
+ " --ignore-assets\n"
+ " Assets to be ignored. Default pattern is:\n"
+ " %s\n",
+ gDefaultIgnoreAssets);
}
/*
@@ -556,7 +560,16 @@
bundle.setNonConstantId(true);
} else if (strcmp(cp, "-no-crunch") == 0) {
bundle.setUseCrunchCache(true);
- }else {
+ } else if (strcmp(cp, "-ignore-assets") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--ignore-assets' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ gUserIgnoreAssets = argv[0];
+ } else {
fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp);
wantUsage = true;
goto bail;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index b9ec30c..a69adc1 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -2089,6 +2089,23 @@
keep->add(rule, location);
}
+void
+addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName,
+ const char* pkg, const String8& srcName, int line)
+{
+ String8 rule("-keepclassmembers class * { *** ");
+ rule += memberName;
+ rule += "(...); }";
+
+ String8 location("onClick ");
+ location += srcName;
+ char lineno[20];
+ sprintf(lineno, ":%d", line);
+ location += lineno;
+
+ keep->add(rule, location);
+}
+
status_t
writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
{
@@ -2251,6 +2268,13 @@
}
}
}
+ ssize_t attrIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "onClick");
+ if (attrIndex >= 0) {
+ size_t len;
+ addProguardKeepMethodRule(keep,
+ String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
+ layoutFile->getPrintableSource(), tree.getLineNumber());
+ }
}
return NO_ERROR;
@@ -2289,6 +2313,9 @@
} else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) {
startTag = "PreferenceScreen";
tagAttrPairs = &kXmlTagAttrPairs;
+ } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) {
+ startTag = "menu";
+ tagAttrPairs = NULL;
} else {
continue;
}