diff options
Diffstat (limited to 'core/java')
24 files changed, 601 insertions, 139 deletions
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 6e85665..dd50f3c 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -16,6 +16,7 @@ package android.accessibilityservice; +import android.os.Bundle; import android.accessibilityservice.AccessibilityServiceInfo; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; @@ -147,14 +148,15 @@ interface IAccessibilityServiceConnection { * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} * to start from the root. * @param action The action to perform. + * @param arguments Optional action arguments. * @param interactionId The id of the interaction for matching with the callback result. * @param callback Callback which to receive the result. * @param threadId The id of the calling thread. * @return Whether the action was performed. */ boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, - int action, int interactionId, IAccessibilityInteractionConnectionCallback callback, - long threadId); + int action, in Bundle arguments, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long threadId); /** * @return The associated accessibility service info. diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java index 1697df0..4d4bfeb 100644 --- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java +++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java @@ -19,6 +19,7 @@ package android.accessibilityservice; import android.accessibilityservice.AccessibilityService.Callbacks; import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper; import android.content.Context; +import android.os.Bundle; import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; @@ -444,10 +445,12 @@ public class UiTestAutomationBridge { * * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id). * @param action The action to perform. + * @param arguments Optional action arguments. * @return Whether the action was performed. */ - public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action) { - return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action); + public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action, + Bundle arguments) { + return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action, arguments); } /** @@ -457,15 +460,16 @@ public class UiTestAutomationBridge { * {@link #ACTIVE_WINDOW_ID} to query the currently active window. * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id). * @param action The action to perform. + * @param arguments Optional action arguments. * @return Whether the action was performed. */ public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, - int action) { + int action, Bundle arguments) { // Cache the id to avoid locking final int connectionId = mConnectionId; ensureValidConnection(connectionId); return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId, - accessibilityWindowId, accessibilityNodeId, action); + accessibilityWindowId, accessibilityNodeId, action, arguments); } /** diff --git a/core/java/android/net/DhcpInfoInternal.java b/core/java/android/net/DhcpInfoInternal.java index 7ab8047..c87c34b 100644 --- a/core/java/android/net/DhcpInfoInternal.java +++ b/core/java/android/net/DhcpInfoInternal.java @@ -142,6 +142,14 @@ public class DhcpInfoInternal { } } + /** + * Test if this DHCP lease includes vendor hint that network link is + * metered, and sensitive to heavy data transfers. + */ + public boolean hasMeteredHint() { + return "ANDROID_METERED".equals(vendorInfo); + } + public String toString() { String routeString = ""; for (RouteInfo route : mRoutes) routeString += route.toString() + " | "; diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index ddd00a4..39810ba 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -34,7 +34,7 @@ interface INfcAdapter INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg); int getState(); - boolean disable(); + boolean disable(boolean saveState); boolean enable(); boolean enableNdefPush(); boolean disableNdefPush(); diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 90f5bef..7bf9feb 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -574,9 +574,10 @@ public final class NfcAdapter { * * @hide */ + public boolean disable() { try { - return sService.disable(); + return sService.disable(true); } catch (RemoteException e) { attemptDeadServiceRecovery(e); return false; diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index d78bbbf..c783e6a 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -69,7 +69,7 @@ public class SearchManagerService extends ISearchManager.Stub { private synchronized Searchables getSearchables() { if (mSearchables == null) { Log.i(TAG, "Building list of searchable activities"); - new MyPackageMonitor().register(mContext, true); + new MyPackageMonitor().register(mContext, null, true); mSearchables = new Searchables(mContext); mSearchables.buildSearchableList(); } diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index ff5a467..dc58ef2 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -117,7 +117,7 @@ public class DynamicLayout extends Layout mObjects = new PackedObjectVector<Directions>(1); - mBlockEnds = new int[] { 0 }; + mBlockEndLines = new int[] { 0 }; mBlockIndices = new int[] { INVALID_BLOCK_INDEX }; mNumberOfBlocks = 1; @@ -391,23 +391,23 @@ public class DynamicLayout extends Layout int firstBlock = -1; int lastBlock = -1; for (int i = 0; i < mNumberOfBlocks; i++) { - if (mBlockEnds[i] >= startLine) { + if (mBlockEndLines[i] >= startLine) { firstBlock = i; break; } } for (int i = firstBlock; i < mNumberOfBlocks; i++) { - if (mBlockEnds[i] >= endLine) { + if (mBlockEndLines[i] >= endLine) { lastBlock = i; break; } } - final int lastBlockEndLine = mBlockEnds[lastBlock]; + final int lastBlockEndLine = mBlockEndLines[lastBlock]; boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 : - mBlockEnds[firstBlock - 1] + 1); + mBlockEndLines[firstBlock - 1] + 1); boolean createBlock = newLineCount > 0; - boolean createBlockAfter = endLine < mBlockEnds[lastBlock]; + boolean createBlockAfter = endLine < mBlockEndLines[lastBlock]; int numAddedBlocks = 0; if (createBlockBefore) numAddedBlocks++; @@ -419,27 +419,27 @@ public class DynamicLayout extends Layout if (newNumberOfBlocks == 0) { // Even when text is empty, there is actually one line and hence one block - mBlockEnds[0] = 0; + mBlockEndLines[0] = 0; mBlockIndices[0] = INVALID_BLOCK_INDEX; mNumberOfBlocks = 1; return; } - if (newNumberOfBlocks > mBlockEnds.length) { + if (newNumberOfBlocks > mBlockEndLines.length) { final int newSize = ArrayUtils.idealIntArraySize(newNumberOfBlocks); - int[] blockEnds = new int[newSize]; + int[] blockEndLines = new int[newSize]; int[] blockIndices = new int[newSize]; - System.arraycopy(mBlockEnds, 0, blockEnds, 0, firstBlock); + System.arraycopy(mBlockEndLines, 0, blockEndLines, 0, firstBlock); System.arraycopy(mBlockIndices, 0, blockIndices, 0, firstBlock); - System.arraycopy(mBlockEnds, lastBlock + 1, - blockEnds, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1); + System.arraycopy(mBlockEndLines, lastBlock + 1, + blockEndLines, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1); System.arraycopy(mBlockIndices, lastBlock + 1, blockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1); - mBlockEnds = blockEnds; + mBlockEndLines = blockEndLines; mBlockIndices = blockIndices; } else { - System.arraycopy(mBlockEnds, lastBlock + 1, - mBlockEnds, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1); + System.arraycopy(mBlockEndLines, lastBlock + 1, + mBlockEndLines, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1); System.arraycopy(mBlockIndices, lastBlock + 1, mBlockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1); } @@ -447,24 +447,24 @@ public class DynamicLayout extends Layout mNumberOfBlocks = newNumberOfBlocks; final int deltaLines = newLineCount - (endLine - startLine + 1); for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) { - mBlockEnds[i] += deltaLines; + mBlockEndLines[i] += deltaLines; } int blockIndex = firstBlock; if (createBlockBefore) { - mBlockEnds[blockIndex] = startLine - 1; + mBlockEndLines[blockIndex] = startLine - 1; mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX; blockIndex++; } if (createBlock) { - mBlockEnds[blockIndex] = startLine + newLineCount - 1; + mBlockEndLines[blockIndex] = startLine + newLineCount - 1; mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX; blockIndex++; } if (createBlockAfter) { - mBlockEnds[blockIndex] = lastBlockEndLine + deltaLines; + mBlockEndLines[blockIndex] = lastBlockEndLine + deltaLines; mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX; } } @@ -473,10 +473,10 @@ public class DynamicLayout extends Layout * This package private method is used for test purposes only * @hide */ - void setBlocksDataForTest(int[] blockEnds, int[] blockIndices, int numberOfBlocks) { - mBlockEnds = new int[blockEnds.length]; + void setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks) { + mBlockEndLines = new int[blockEndLines.length]; mBlockIndices = new int[blockIndices.length]; - System.arraycopy(blockEnds, 0, mBlockEnds, 0, blockEnds.length); + System.arraycopy(blockEndLines, 0, mBlockEndLines, 0, blockEndLines.length); System.arraycopy(blockIndices, 0, mBlockIndices, 0, blockIndices.length); mNumberOfBlocks = numberOfBlocks; } @@ -484,8 +484,8 @@ public class DynamicLayout extends Layout /** * @hide */ - public int[] getBlockEnds() { - return mBlockEnds; + public int[] getBlockEndLines() { + return mBlockEndLines; } /** @@ -633,8 +633,8 @@ public class DynamicLayout extends Layout * @hide */ public static final int INVALID_BLOCK_INDEX = -1; - // Stores the line numbers of the last line of each block - private int[] mBlockEnds; + // Stores the line numbers of the last line of each block (inclusive) + private int[] mBlockEndLines; // The indices of this block's display list in TextView's internal display list array or // INVALID_BLOCK_INDEX if this block has been invalidated during an edition private int[] mBlockIndices; diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index e3f5b96..7d569ad 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -18,6 +18,7 @@ package android.view; import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -514,8 +515,9 @@ final class AccessibilityInteractionController { } public void performAccessibilityActionClientThread(long accessibilityNodeId, int action, - int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interogatingPid, long interrogatingTid) { + Bundle arguments, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, + long interrogatingTid) { Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION; message.arg1 = flags; @@ -525,6 +527,7 @@ final class AccessibilityInteractionController { args.argi2 = action; args.argi3 = interactionId; args.arg1 = callback; + args.arg2 = arguments; message.obj = args; // If the interrogation is performed by the same thread as the main UI // thread in this process, set the message as a static reference so @@ -547,6 +550,7 @@ final class AccessibilityInteractionController { final int interactionId = args.argi3; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg1; + Bundle arguments = (Bundle) args.arg2; mPool.release(args); boolean succeeded = false; try { @@ -564,9 +568,10 @@ final class AccessibilityInteractionController { if (target != null && target.isDisplayedOnScreen()) { AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); if (provider != null) { - succeeded = provider.performAccessibilityAction(action, virtualDescendantId); + succeeded = provider.performAction(virtualDescendantId, action, + arguments); } else if (virtualDescendantId == View.NO_ID) { - succeeded = target.performAccessibilityAction(action); + succeeded = target.performAccessibilityAction(action, arguments); } } } finally { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ce02113..a299141 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -39,6 +39,7 @@ import android.graphics.Region; import android.graphics.Shader; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Parcel; @@ -1044,12 +1045,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int ACCESSIBILITY_FOCUS_DOWN = FOCUS_DOWN | FOCUS_ACCESSIBILITY; /** - * Use with {@link #focusSearch(int)}. Move acessibility focus to the next view. + * Use with {@link #focusSearch(int)}. Move acessibility focus forward. */ public static final int ACCESSIBILITY_FOCUS_FORWARD = FOCUS_FORWARD | FOCUS_ACCESSIBILITY; /** - * Use with {@link #focusSearch(int)}. Move acessibility focus to the previous view. + * Use with {@link #focusSearch(int)}. Move acessibility focus backward. */ public static final int ACCESSIBILITY_FOCUS_BACKWARD = FOCUS_BACKWARD | FOCUS_ACCESSIBILITY; @@ -1064,6 +1065,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int ACCESSIBILITY_FOCUS_OUT = 0x00000008 | FOCUS_ACCESSIBILITY; /** + * Use with {@link #focusSearch(int)}. Move acessibility focus to the next view. + */ + public static final int ACCESSIBILITY_FOCUS_NEXT = 0x00000010 | FOCUS_ACCESSIBILITY; + + /** + * Use with {@link #focusSearch(int)}. Move acessibility focus to the previous view. + */ + public static final int ACCESSIBILITY_FOCUS_PREVIOUS = 0x00000020 | FOCUS_ACCESSIBILITY; + + /** * Bits of {@link #getMeasuredWidthAndState()} and * {@link #getMeasuredWidthAndState()} that provide the actual measured size. */ @@ -6390,7 +6401,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param action The action to perform. * @return Whether the action was performed. */ - public boolean performAccessibilityAction(int action) { + public boolean performAccessibilityAction(int action, Bundle args) { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { if (isClickable()) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 247f673..e3681df 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -59,6 +59,7 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.TypedValue; +import android.view.KeyCharacterMap.FallbackAction; import android.view.View.AttachInfo; import android.view.View.MeasureSpec; import android.view.accessibility.AccessibilityEvent; @@ -323,6 +324,8 @@ public final class ViewRootImpl implements ViewParent, private final int mDensity; + final KeyCharacterMap.FallbackAction mFallbackAction = new KeyCharacterMap.FallbackAction(); + /** * Consistency verifier for debugging purposes. */ @@ -4383,6 +4386,31 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } + public void dispatchUnhandledKey(KeyEvent event) { + if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { + final KeyCharacterMap kcm = event.getKeyCharacterMap(); + final int keyCode = event.getKeyCode(); + final int metaState = event.getMetaState(); + + KeyEvent fallbackEvent = null; + synchronized (mFallbackAction) { + // Check for fallback actions specified by the key character map. + if (kcm.getFallbackAction(keyCode, metaState, mFallbackAction)) { + int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK; + fallbackEvent = KeyEvent.obtain( + event.getDownTime(), event.getEventTime(), + event.getAction(), mFallbackAction.keyCode, + event.getRepeatCount(), mFallbackAction.metaState, + event.getDeviceId(), event.getScanCode(), + flags, event.getSource(), null); + } + } + if (fallbackEvent != null) { + dispatchKey(fallbackEvent); + } + } + } + public void dispatchAppVisibility(boolean visible) { Message msg = mHandler.obtainMessage(MSG_DISPATCH_APP_VISIBILITY); msg.arg1 = visible ? 1 : 0; @@ -5132,12 +5160,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void performAccessibilityAction(long accessibilityNodeId, int action, - int interactionId, IAccessibilityInteractionConnectionCallback callback, - int flags, int interogatingPid, long interrogatingTid) { + Bundle arguments, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, + int interogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() - .performAccessibilityActionClientThread(accessibilityNodeId, action, + .performAccessibilityActionClientThread(accessibilityNodeId, action, arguments, interactionId, callback, flags, interogatingPid, interrogatingTid); } else { // We cannot make the call and notify the caller so it does not wait. diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index f73faf3..24e90fd 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -19,6 +19,7 @@ package android.view.accessibility; import android.accessibilityservice.IAccessibilityServiceConnection; import android.graphics.Rect; import android.os.Binder; +import android.os.Bundle; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -408,17 +409,18 @@ public final class AccessibilityInteractionClient * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} * to start from the root. * @param action The action to perform. + * @param arguments Optional action arguments. * @return Whether the action was performed. */ public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId, - long accessibilityNodeId, int action) { + long accessibilityNodeId, int action, Bundle arguments) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final boolean success = connection.performAccessibilityAction( - accessibilityWindowId, accessibilityNodeId, action, interactionId, this, - Thread.currentThread().getId()); + accessibilityWindowId, accessibilityNodeId, action, arguments, + interactionId, this, Thread.currentThread().getId()); if (success) { return getPerformAccessibilityActionResultAndClear(interactionId); } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index c5f2062..cdb9e77 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -17,9 +17,9 @@ package android.view.accessibility; import android.graphics.Rect; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; import android.util.SparseLongArray; import android.view.View; @@ -122,6 +122,80 @@ public class AccessibilityNodeInfo implements Parcelable { public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080; /** + * Action that requests from the node to go to the next entity in its content + * at a given granularity. For example, move to the next word, link, etc. + * <p> + * <strong>Arguments:</strong> + * <ul> + * <li> + * {@link #ACTION_ARGUMENT_GRANULARITY} + * </li> + * <li> + * </p> + * <p> + * <strong>Example:</strong> + * <code><pre><p> + * // Assume the first granularity was presented to the user and she is + * // making an explicit action to traverse the node at that granularity. + * CharSequence granularity = info.getGranularity(0); + * Bundle arguments = new Bundle(); + * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY, granularity); + * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY, arguments); + * </code></pre></p> + * </li> + * </ul> + * </p> + * @see #setGranularities(CharSequence[]) + * @see #getGranularities() + */ + public static final int ACTION_NEXT_AT_GRANULARITY = 0x00000100; + + /** + * Action that requests from the node to go to the previous entity in its content + * at a given granularity. For example, move to the next word, link, etc. + * <p> + * <strong>Arguments:</strong> + * <ul> + * <li> + * {@link #ACTION_ARGUMENT_GRANULARITY} + * </li> + * <li> + * </p> + * <p> + * <strong>Example:</strong> + * <code><pre><p> + * // Assume the first granularity was presented to the user and she is + * // making an explicit action to traverse the node at that granularity. + * CharSequence granularity = info.getGranularity(0); + * Bundle arguments = new Bundle(); + * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_GRANULARITY, granularity); + * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY, arguments); + * </code></pre></p> + * </li> + * </ul> + * </p> + * @see #setGranularities(CharSequence[]) + * @see #getGranularities() + */ + public static final int ACTION_PREVIOUS_AT_GRANULARITY = 0x00000200; + + /** + * Argument for which content granularity to be used when traversing the node content. + * <p> + * <strong>Actions:</strong> + * <ul> + * <li> + * {@link #ACTION_PREVIOUS_AT_GRANULARITY} + * </li> + * <li> + * {@link #ACTION_PREVIOUS_AT_GRANULARITY} + * </li> + * </ul> + * </p> + */ + public static final String ACTION_ARGUMENT_GRANULARITY = "ACTION_ARGUMENT_GRANULARITY"; + + /** * The input focus. */ public static final int FOCUS_INPUT = 1; @@ -231,9 +305,11 @@ public class AccessibilityNodeInfo implements Parcelable { private CharSequence mText; private CharSequence mContentDescription; - private SparseLongArray mChildNodeIds = new SparseLongArray(); + private final SparseLongArray mChildNodeIds = new SparseLongArray(); private int mActions; + private CharSequence[] mGranularities; + private int mConnectionId = UNDEFINED; /** @@ -458,6 +534,32 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Sets the granularities for traversing the content of this node. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param granularities The granularity names. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setGranularities(CharSequence[] granularities) { + enforceNotSealed(); + mGranularities = granularities; + } + + /** + * Gets the granularities for traversing the content of this node. + * + * @return The count. + */ + public CharSequence[] getGranularities() { + return mGranularities; + } + + /** * Performs an action on the node. * <p> * <strong>Note:</strong> An action can be performed only if the request is made @@ -475,7 +577,31 @@ public class AccessibilityNodeInfo implements Parcelable { return false; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, action); + return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, + action, null); + } + + /** + * Performs an action on the node. + * <p> + * <strong>Note:</strong> An action can be performed only if the request is made + * from an {@link android.accessibilityservice.AccessibilityService}. + * </p> + * + * @param action The action to perform. + * @param arguments A bundle with additional arguments. + * @return True if the action was performed. + * + * @throws IllegalStateException If called outside of an AccessibilityService. + */ + public boolean performAction(int action, Bundle arguments) { + enforceSealed(); + if (!canPerformRequestOverConnection(mSourceNodeId)) { + return false; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, + action, arguments); } /** @@ -1215,6 +1341,8 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeLong(mParentNodeId); parcel.writeInt(mConnectionId); + parcel.writeCharSequenceArray(mGranularities); + SparseLongArray childIds = mChildNodeIds; final int childIdsSize = childIds.size(); parcel.writeInt(childIdsSize); @@ -1236,10 +1364,10 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeInt(mBooleanProperties); - TextUtils.writeToParcel(mPackageName, parcel, flags); - TextUtils.writeToParcel(mClassName, parcel, flags); - TextUtils.writeToParcel(mText, parcel, flags); - TextUtils.writeToParcel(mContentDescription, parcel, flags); + parcel.writeCharSequence(mPackageName); + parcel.writeCharSequence(mClassName); + parcel.writeCharSequence(mText); + parcel.writeCharSequence(mContentDescription); // Since instances of this class are fetched via synchronous i.e. blocking // calls in IPCs we always recycle as soon as the instance is marshaled. @@ -1251,6 +1379,7 @@ public class AccessibilityNodeInfo implements Parcelable { * * @param other The other instance. */ + @SuppressWarnings("unchecked") private void init(AccessibilityNodeInfo other) { mSealed = other.mSealed; mSourceNodeId = other.mSourceNodeId; @@ -1265,7 +1394,11 @@ public class AccessibilityNodeInfo implements Parcelable { mContentDescription = other.mContentDescription; mActions= other.mActions; mBooleanProperties = other.mBooleanProperties; - mChildNodeIds = other.mChildNodeIds.clone(); + mGranularities = (other.mGranularities) != null ? other.mGranularities.clone() : null; + final int otherChildIdCount = other.mChildNodeIds.size(); + for (int i = 0; i < otherChildIdCount; i++) { + mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i)); + } } /** @@ -1280,6 +1413,8 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = parcel.readLong(); mConnectionId = parcel.readInt(); + mGranularities = parcel.readCharSequenceArray(); + SparseLongArray childIds = mChildNodeIds; final int childrenSize = parcel.readInt(); for (int i = 0; i < childrenSize; i++) { @@ -1301,10 +1436,10 @@ public class AccessibilityNodeInfo implements Parcelable { mBooleanProperties = parcel.readInt(); - mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); - mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); - mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); - mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + mPackageName = parcel.readCharSequence(); + mClassName = parcel.readCharSequence(); + mText = parcel.readCharSequence(); + mContentDescription = parcel.readCharSequence(); } /** @@ -1316,6 +1451,7 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = ROOT_NODE_ID; mWindowId = UNDEFINED; mConnectionId = UNDEFINED; + mGranularities = null; mChildNodeIds.clear(); mBoundsInParent.set(0, 0, 0, 0); mBoundsInScreen.set(0, 0, 0, 0); @@ -1394,6 +1530,17 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); builder.append("; mParentNodeId: " + mParentNodeId); + + CharSequence[] granularities = mGranularities; + builder.append("; granularities: ["); + for (int i = 0, count = granularities.length; i < count; i++) { + builder.append(granularities[i]); + if (i < count - 1) { + builder.append(", "); + } + } + builder.append("]"); + SparseLongArray childIds = mChildNodeIds; builder.append("; childAccessibilityIds: ["); for (int i = 0, count = childIds.size(); i < count; i++) { @@ -1401,8 +1548,8 @@ public class AccessibilityNodeInfo implements Parcelable { if (i < count - 1) { builder.append(", "); } - } - builder.append("]"); + } + builder.append("]"); } builder.append("; boundsInParent: " + mBoundsInParent); diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java index 19e35dd..ba6433f 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java +++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java @@ -17,6 +17,7 @@ package android.view.accessibility; import android.accessibilityservice.AccessibilityService; +import android.os.Bundle; import android.view.View; import java.util.List; @@ -47,12 +48,13 @@ import java.util.List; * getAccessibilityNodeProvider( * if (mAccessibilityNodeProvider == null) { * mAccessibilityNodeProvider = new AccessibilityNodeProvider() { - * public boolean performAccessibilityAction(int action, int virtualDescendantId) { + * public boolean performAction(int action, int virtualDescendantId) { * // Implementation. * return false; * } * - * public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text, int virtualDescendantId) { + * public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text, + * int virtualDescendantId) { * // Implementation. * return null; * } @@ -99,15 +101,16 @@ public abstract class AccessibilityNodeProvider { * host View, with the given <code>virtualViewId</code> or the host View itself * if <code>virtualViewId</code> equals to {@link View#NO_ID}. * - * @param action The action to perform. * @param virtualViewId A client defined virtual view id. + * @param action The action to perform. + * @param arguments Optional action arguments. * @return True if the action was performed. * - * @see View#performAccessibilityAction(int) + * @see View#performAccessibilityAction(int, Bundle) * @see #createAccessibilityNodeInfo(int) * @see AccessibilityNodeInfo */ - public boolean performAccessibilityAction(int action, int virtualViewId) { + public boolean performAction(int virtualViewId, int action, Bundle arguments) { return false; } diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index 8182d29..9d7a928 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.os.Bundle; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; @@ -47,7 +48,7 @@ oneway interface IAccessibilityInteractionConnection { IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid); - void performAccessibilityAction(long accessibilityNodeId, int action, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, - long interrogatingTid); + void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, + int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, + int interrogatingPid, long interrogatingTid); } diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index f429e09..2793081 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -3925,6 +3925,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mSelectCursorExtent.offset(dx, dy); mSelectCursorExtentTextQuad.offset(dx, dy); } + } else if (mHandleAlpha.getAlpha() > 0) { + // stop fading as we're not going to move with the layer. + mHandleAlphaAnimator.end(); } if (mAutoCompletePopup != null && mCurrentScrollingLayerId == mEditTextLayerId) { @@ -4418,9 +4421,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } canvas.restoreToCount(saveCount); - if (mSelectingText) { - drawTextSelectionHandles(canvas); - } + drawTextSelectionHandles(canvas); if (extras == DRAW_EXTRAS_CURSOR_RING) { if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { @@ -4658,6 +4659,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } private void onZoomAnimationStart() { + if (!mSelectingText && mHandleAlpha.getAlpha() > 0) { + mHandleAlphaAnimator.end(); + } } private void onZoomAnimationEnd() { @@ -4688,6 +4692,36 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private final DrawFilter mScrollFilter = new PaintFlagsDrawFilter(SCROLL_BITS, 0); + private class SelectionHandleAlpha { + private int mAlpha = 0; + public void setAlpha(int alpha) { + mAlpha = alpha; + if (mSelectHandleCenter != null) { + mSelectHandleCenter.setAlpha(alpha); + mSelectHandleLeft.setAlpha(alpha); + mSelectHandleRight.setAlpha(alpha); + // TODO: Use partial invalidate + invalidate(); + } + } + + public int getAlpha() { + return mAlpha; + } + + } + + private void startSelectingText() { + mSelectingText = true; + mHandleAlphaAnimator.setIntValues(255); + mHandleAlphaAnimator.start(); + } + private void endSelectingText() { + mSelectingText = false; + mHandleAlphaAnimator.setIntValues(0); + mHandleAlphaAnimator.start(); + } + private void ensureSelectionHandles() { if (mSelectHandleCenter == null) { mSelectHandleCenter = mContext.getResources().getDrawable( @@ -4696,6 +4730,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc com.android.internal.R.drawable.text_select_handle_left); mSelectHandleRight = mContext.getResources().getDrawable( com.android.internal.R.drawable.text_select_handle_right); + mHandleAlpha.setAlpha(mHandleAlpha.getAlpha()); mSelectHandleCenterOffset = new Point(0, -mSelectHandleCenter.getIntrinsicHeight()); mSelectHandleLeftOffset = new Point(0, @@ -4707,31 +4742,40 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } private void drawTextSelectionHandles(Canvas canvas) { + if (mHandleAlpha.getAlpha() == 0) { + return; + } ensureSelectionHandles(); - int[] handles = new int[4]; - getSelectionHandles(handles); - int start_x = contentToViewDimension(handles[0]); - int start_y = contentToViewDimension(handles[1]); - int end_x = contentToViewDimension(handles[2]); - int end_y = contentToViewDimension(handles[3]); + if (mSelectingText) { + int[] handles = new int[4]; + getSelectionHandles(handles); + int start_x = contentToViewDimension(handles[0]); + int start_y = contentToViewDimension(handles[1]); + int end_x = contentToViewDimension(handles[2]); + int end_y = contentToViewDimension(handles[3]); + + if (mIsCaretSelection) { + // Caret handle is centered + start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2); + mSelectHandleCenter.setBounds(start_x, start_y, + start_x + mSelectHandleCenter.getIntrinsicWidth(), + start_y + mSelectHandleCenter.getIntrinsicHeight()); + } else { + // Magic formula copied from TextView + start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4; + mSelectHandleLeft.setBounds(start_x, start_y, + start_x + mSelectHandleLeft.getIntrinsicWidth(), + start_y + mSelectHandleLeft.getIntrinsicHeight()); + end_x -= mSelectHandleRight.getIntrinsicWidth() / 4; + mSelectHandleRight.setBounds(end_x, end_y, + end_x + mSelectHandleRight.getIntrinsicWidth(), + end_y + mSelectHandleRight.getIntrinsicHeight()); + } + } if (mIsCaretSelection) { - // Caret handle is centered - start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2); - mSelectHandleCenter.setBounds(start_x, start_y, - start_x + mSelectHandleCenter.getIntrinsicWidth(), - start_y + mSelectHandleCenter.getIntrinsicHeight()); mSelectHandleCenter.draw(canvas); } else { - // Magic formula copied from TextView - start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4; - mSelectHandleLeft.setBounds(start_x, start_y, - start_x + mSelectHandleLeft.getIntrinsicWidth(), - start_y + mSelectHandleLeft.getIntrinsicHeight()); - end_x -= mSelectHandleRight.getIntrinsicWidth() / 4; - mSelectHandleRight.setBounds(end_x, end_y, - end_x + mSelectHandleRight.getIntrinsicWidth(), - end_y + mSelectHandleRight.getIntrinsicHeight()); mSelectHandleLeft.draw(canvas); mSelectHandleRight.draw(canvas); } @@ -5385,7 +5429,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc selectionDone(); return false; } - mSelectingText = true; + startSelectingText(); mTouchMode = TOUCH_DRAG_MODE; return true; } @@ -5439,7 +5483,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc void selectionDone() { if (mSelectingText) { hidePasteButton(); - mSelectingText = false; + endSelectingText(); // finish is idempotent, so this is fine even if selectionDone was // called by mSelectCallback.onDestroyActionMode if (mSelectCallback != null) { @@ -6571,6 +6615,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private long mTrackballUpTime = 0; private long mLastCursorTime = 0; private Rect mLastCursorBounds; + private SelectionHandleAlpha mHandleAlpha = new SelectionHandleAlpha(); + private ObjectAnimator mHandleAlphaAnimator = + ObjectAnimator.ofInt(mHandleAlpha, "alpha", 0); // Set by default; BrowserActivity clears to interpret trackball data // directly for movement. Currently, the framework only passes diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 0c34037..6aff10a 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -20,6 +20,7 @@ import android.graphics.Bitmap; import android.net.http.SslError; import android.os.Message; import android.view.KeyEvent; +import android.view.ViewRootImpl; public class WebViewClient { @@ -273,6 +274,10 @@ public class WebViewClient { * @param event The key event. */ public void onUnhandledKeyEvent(WebView view, KeyEvent event) { + ViewRootImpl root = view.getViewRootImpl(); + if (root != null) { + root.dispatchUnhandledKey(event); + } } /** diff --git a/core/java/android/widget/ActivityChooserModel.java b/core/java/android/widget/ActivityChooserModel.java index bc44521..c53b5f6 100644 --- a/core/java/android/widget/ActivityChooserModel.java +++ b/core/java/android/widget/ActivityChooserModel.java @@ -365,7 +365,7 @@ public class ActivityChooserModel extends DataSetObservable { } else { mHistoryFileName = historyFileName; } - mPackageMonitor.register(mContext, true); + mPackageMonitor.register(mContext, null, true); } /** diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 040a385..8d199d7 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1241,24 +1241,21 @@ public class Editor { } DynamicLayout dynamicLayout = (DynamicLayout) layout; - int[] blockEnds = dynamicLayout.getBlockEnds(); + int[] blockEndLines = dynamicLayout.getBlockEndLines(); int[] blockIndices = dynamicLayout.getBlockIndices(); final int numberOfBlocks = dynamicLayout.getNumberOfBlocks(); - final int mScrollX = mTextView.getScrollX(); - final int mScrollY = mTextView.getScrollY(); - canvas.translate(mScrollX, mScrollY); int endOfPreviousBlock = -1; int searchStartIndex = 0; for (int i = 0; i < numberOfBlocks; i++) { - int blockEnd = blockEnds[i]; + int blockEndLine = blockEndLines[i]; int blockIndex = blockIndices[i]; final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX; if (blockIsInvalid) { blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks, searchStartIndex); - // Dynamic layout internal block indices structure is updated from Editor + // Note how dynamic layout's internal block indices get updated from Editor blockIndices[i] = blockIndex; searchStartIndex = blockIndex + 1; } @@ -1272,28 +1269,38 @@ public class Editor { } if (!blockDisplayList.isValid()) { + final int blockBeginLine = endOfPreviousBlock + 1; + final int top = layout.getLineTop(blockBeginLine); + final int bottom = layout.getLineBottom(blockEndLine); + final HardwareCanvas hardwareCanvas = blockDisplayList.start(); try { - hardwareCanvas.setViewport(width, height); + hardwareCanvas.setViewport(width, bottom - top); // The dirty rect should always be null for a display list hardwareCanvas.onPreDraw(null); - hardwareCanvas.translate(-mScrollX, -mScrollY); - layout.drawText(hardwareCanvas, endOfPreviousBlock + 1, blockEnd); - hardwareCanvas.translate(mScrollX, mScrollY); + // drawText is always relative to TextView's origin, this translation brings + // this range of text back to the top of the viewport + hardwareCanvas.translate(0, -top); + layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine); + hardwareCanvas.translate(0, top); } finally { hardwareCanvas.onPostDraw(); blockDisplayList.end(); if (View.USE_DISPLAY_LIST_PROPERTIES) { - blockDisplayList.setLeftTopRightBottom(0, 0, width, height); + blockDisplayList.setLeftTopRightBottom(0, top, width, bottom); + // Same as drawDisplayList below, handled by our TextView's parent + blockDisplayList.setClipChildren(false); } } } + // TODO When View.USE_DISPLAY_LIST_PROPERTIES is the only code path, the + // width and height parameters should be removed and the bounds set above in + // setLeftTopRightBottom should be used instead for quick rejection. ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, width, height, null, - DisplayList.FLAG_CLIP_CHILDREN); - endOfPreviousBlock = blockEnd; + 0 /* no child clipping, our TextView parent enforces it */); + endOfPreviousBlock = blockEndLine; } - canvas.translate(-mScrollX, -mScrollY); } else { // Boring layout is used for empty and hint text layout.drawText(canvas, firstLine, lastLine); @@ -1332,6 +1339,38 @@ public class Editor { if (translate) canvas.translate(0, -cursorOffsetVertical); } + /** + * Invalidates all the sub-display lists that overlap the specified character range + */ + void invalidateTextDisplayList(Layout layout, int start, int end) { + if (mTextDisplayLists != null && layout instanceof DynamicLayout) { + final int firstLine = layout.getLineForOffset(start); + final int lastLine = layout.getLineForOffset(end); + + DynamicLayout dynamicLayout = (DynamicLayout) layout; + int[] blockEndLines = dynamicLayout.getBlockEndLines(); + int[] blockIndices = dynamicLayout.getBlockIndices(); + final int numberOfBlocks = dynamicLayout.getNumberOfBlocks(); + + int i = 0; + // Skip the blocks before firstLine + while (i < numberOfBlocks) { + if (blockEndLines[i] >= firstLine) break; + i++; + } + + // Invalidate all subsequent blocks until lastLine is passed + while (i < numberOfBlocks) { + final int blockIndex = blockIndices[i]; + if (blockIndex != DynamicLayout.INVALID_BLOCK_INDEX) { + mTextDisplayLists[blockIndex].invalidate(); + } + if (blockEndLines[i] >= lastLine) break; + i++; + } + } + } + void invalidateTextDisplayList() { if (mTextDisplayLists != null) { for (int i = 0; i < mTextDisplayLists.length; i++) { @@ -1572,11 +1611,9 @@ public class Editor { } void onScrollChanged() { - if (mPositionListener != null) { - mPositionListener.onScrollChanged(); - } - // Internal scroll affects the clip boundaries - invalidateTextDisplayList(); + if (mPositionListener != null) { + mPositionListener.onScrollChanged(); + } } /** diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 992849d..11d1ed0 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -26,6 +26,7 @@ import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.text.InputFilter; import android.text.InputType; import android.text.Spanned; @@ -2068,7 +2069,7 @@ public class NumberPicker extends LinearLayout { } @Override - public boolean performAccessibilityAction(int action, int virtualViewId) { + public boolean performAction(int virtualViewId, int action, Bundle arguments) { switch (virtualViewId) { case VIRTUAL_VIEW_ID_INPUT: { switch (action) { @@ -2086,7 +2087,7 @@ public class NumberPicker extends LinearLayout { } } break; } - return super.performAccessibilityAction(action, virtualViewId); + return super.performAction(virtualViewId, action, arguments); } public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 37d9db7..0e7fe7f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6860,7 +6860,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { ims.mContentChanged = true; } - if (mEditor != null) getEditor().invalidateTextDisplayList(); + if (mEditor != null) { + if (oldStart >= 0) getEditor().invalidateTextDisplayList(mLayout, oldStart, oldEnd); + if (newStart >= 0) getEditor().invalidateTextDisplayList(mLayout, newStart, newEnd); + } } if (MetaKeyKeyListener.isMetaTracker(buf, what)) { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index af722a8..5862d3e 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -99,7 +99,7 @@ public class ResolverActivity extends AlertActivity implements ap.mTitle = title; ap.mOnClickListener = this; - mPackageMonitor.register(this, false); + mPackageMonitor.register(this, getMainLooper(), false); if (alwaysUseOption) { LayoutInflater inflater = (LayoutInflater) getSystemService( @@ -135,7 +135,7 @@ public class ResolverActivity extends AlertActivity implements @Override protected void onRestart() { super.onRestart(); - mPackageMonitor.register(this, false); + mPackageMonitor.register(this, getMainLooper(), false); mAdapter.handlePackagesChanged(); } diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java index 6a46929..d867ff9 100644 --- a/core/java/com/android/internal/app/ShutdownThread.java +++ b/core/java/com/android/internal/app/ShutdownThread.java @@ -24,6 +24,8 @@ import android.app.IActivityManager; import android.app.ProgressDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.IBluetooth; +import android.nfc.NfcAdapter; +import android.nfc.INfcAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -48,7 +50,7 @@ import android.view.WindowManager; public final class ShutdownThread extends Thread { // constants private static final String TAG = "ShutdownThread"; - private static final int MAX_NUM_PHONE_STATE_READS = 16; + private static final int MAX_NUM_PHONE_STATE_READS = 24; private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500; // maximum time we wait for the shutdown broadcast before going on. private static final int MAX_BROADCAST_TIME = 10*1000; @@ -62,11 +64,15 @@ public final class ShutdownThread extends Thread { private static boolean sIsStarted = false; private static boolean mReboot; + private static boolean mRebootSafeMode; private static String mRebootReason; // Provides shutdown assurance in case the system_server is killed public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested"; + // Indicates whether we are rebooting into safe mode + public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode"; + // static instance of this thread private static final ShutdownThread sInstance = new ShutdownThread(); @@ -90,6 +96,12 @@ public final class ShutdownThread extends Thread { * @param confirm true if user confirmation is needed before shutting down. */ public static void shutdown(final Context context, boolean confirm) { + mReboot = false; + mRebootSafeMode = false; + shutdownInner(context, confirm); + } + + static void shutdownInner(final Context context, boolean confirm) { // ensure that only one thread is trying to power down. // any additional calls are just returned synchronized (sIsStartedGuard) { @@ -101,16 +113,20 @@ public final class ShutdownThread extends Thread { final int longPressBehavior = context.getResources().getInteger( com.android.internal.R.integer.config_longPressOnPowerBehavior); - final int resourceId = longPressBehavior == 2 - ? com.android.internal.R.string.shutdown_confirm_question - : com.android.internal.R.string.shutdown_confirm; + final int resourceId = mRebootSafeMode + ? com.android.internal.R.string.reboot_safemode_confirm + : (longPressBehavior == 2 + ? com.android.internal.R.string.shutdown_confirm_question + : com.android.internal.R.string.shutdown_confirm); Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior); if (confirm) { final CloseDialogReceiver closer = new CloseDialogReceiver(context); final AlertDialog dialog = new AlertDialog.Builder(context) - .setTitle(com.android.internal.R.string.power_off) + .setTitle(mRebootSafeMode + ? com.android.internal.R.string.reboot_safemode_title + : com.android.internal.R.string.power_off) .setMessage(resourceId) .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { @@ -160,8 +176,23 @@ public final class ShutdownThread extends Thread { */ public static void reboot(final Context context, String reason, boolean confirm) { mReboot = true; + mRebootSafeMode = false; mRebootReason = reason; - shutdown(context, confirm); + shutdownInner(context, confirm); + } + + /** + * Request a reboot into safe mode. Must be called from a Looper thread in which its UI + * is shown. + * + * @param context Context used to display the shutdown progress dialog. + * @param confirm true if user confirmation is needed before shutting down. + */ + public static void rebootSafeMode(final Context context, boolean confirm) { + mReboot = true; + mRebootSafeMode = true; + mRebootReason = null; + shutdownInner(context, confirm); } private static void beginShutdownSequence(Context context) { @@ -231,6 +262,7 @@ public final class ShutdownThread extends Thread { * Shuts off power regardless of radio and bluetooth state if the alloted time has passed. */ public void run() { + boolean nfcOff; boolean bluetoothOff; boolean radioOff; @@ -251,6 +283,14 @@ public final class ShutdownThread extends Thread { SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); } + /* + * If we are rebooting into safe mode, write a system property + * indicating so. + */ + if (mRebootSafeMode) { + SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); + } + Log.i(TAG, "Sending shutdown broadcast..."); // First send the high-level shut down broadcast. @@ -284,16 +324,29 @@ public final class ShutdownThread extends Thread { } } + final INfcAdapter nfc = + INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc")); final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); final IBluetooth bluetooth = IBluetooth.Stub.asInterface(ServiceManager.checkService( BluetoothAdapter.BLUETOOTH_SERVICE)); - final IMountService mount = IMountService.Stub.asInterface( ServiceManager.checkService("mount")); - + + try { + nfcOff = nfc == null || + nfc.getState() == NfcAdapter.STATE_OFF; + if (!nfcOff) { + Log.w(TAG, "Turning off NFC..."); + nfc.disable(false); // Don't persist new state + } + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during NFC shutdown", ex); + nfcOff = true; + } + try { bluetoothOff = bluetooth == null || bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF; @@ -317,7 +370,7 @@ public final class ShutdownThread extends Thread { radioOff = true; } - Log.i(TAG, "Waiting for Bluetooth and Radio..."); + Log.i(TAG, "Waiting for NFC, Bluetooth and Radio..."); // Wait a max of 32 seconds for clean shutdown for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) { @@ -338,8 +391,17 @@ public final class ShutdownThread extends Thread { radioOff = true; } } - if (radioOff && bluetoothOff) { - Log.i(TAG, "Radio and Bluetooth shutdown complete."); + if (!nfcOff) { + try { + nfcOff = nfc.getState() == NfcAdapter.STATE_OFF; + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during NFC shutdown", ex); + nfcOff = true; + } + } + + if (radioOff && bluetoothOff && nfcOff) { + Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete."); break; } SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC); diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index 266728b..61866e5 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -16,15 +16,24 @@ package com.android.internal.content; -import android.os.storage.IMountService; - +import android.os.FileUtils; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.storage.IMountService; import android.os.storage.StorageResultCode; import android.util.Log; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import libcore.io.IoUtils; /** * Constants used internally between the PackageManager @@ -196,4 +205,64 @@ public class PackageHelper { } return false; } + + public static void extractPublicFiles(String packagePath, File publicZipFile) + throws IOException { + final FileOutputStream fstr = new FileOutputStream(publicZipFile); + final ZipOutputStream publicZipOutStream = new ZipOutputStream(fstr); + try { + final ZipFile privateZip = new ZipFile(packagePath); + try { + // Copy manifest, resources.arsc and res directory to public zip + for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) { + final String zipEntryName = zipEntry.getName(); + if ("AndroidManifest.xml".equals(zipEntryName) + || "resources.arsc".equals(zipEntryName) + || zipEntryName.startsWith("res/")) { + copyZipEntry(zipEntry, privateZip, publicZipOutStream); + } + } + } finally { + try { + privateZip.close(); + } catch (IOException e) { + } + } + + publicZipOutStream.finish(); + publicZipOutStream.flush(); + FileUtils.sync(fstr); + publicZipOutStream.close(); + FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR + | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1); + } finally { + IoUtils.closeQuietly(publicZipOutStream); + } + } + + private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile, + ZipOutputStream outZipStream) throws IOException { + byte[] buffer = new byte[4096]; + int num; + + ZipEntry newEntry; + if (zipEntry.getMethod() == ZipEntry.STORED) { + // Preserve the STORED method of the input entry. + newEntry = new ZipEntry(zipEntry); + } else { + // Create a new entry so that the compressed len is recomputed. + newEntry = new ZipEntry(zipEntry.getName()); + } + outZipStream.putNextEntry(newEntry); + + final InputStream data = inZipFile.getInputStream(zipEntry); + try { + while ((num = data.read(buffer)) > 0) { + outZipStream.write(buffer, 0, num); + } + outZipStream.flush(); + } finally { + IoUtils.closeQuietly(data); + } + } } diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index 32d8641..f41fcc6 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -21,6 +21,9 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import java.util.HashSet; @@ -32,7 +35,11 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { static final IntentFilter sPackageFilt = new IntentFilter(); static final IntentFilter sNonDataFilt = new IntentFilter(); static final IntentFilter sExternalFilt = new IntentFilter(); - + + static final Object sLock = new Object(); + static HandlerThread sBackgroundThread; + static Handler sBackgroundHandler; + static { sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED); sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -49,6 +56,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { final HashSet<String> mUpdatingPackages = new HashSet<String>(); Context mRegisteredContext; + Handler mRegisteredHandler; String[] mDisappearingPackages; String[] mAppearingPackages; String[] mModifiedPackages; @@ -57,18 +65,35 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { String[] mTempArray = new String[1]; - public void register(Context context, boolean externalStorage) { + public void register(Context context, Looper thread, boolean externalStorage) { if (mRegisteredContext != null) { throw new IllegalStateException("Already registered"); } mRegisteredContext = context; - context.registerReceiver(this, sPackageFilt); - context.registerReceiver(this, sNonDataFilt); + if (thread == null) { + synchronized (sLock) { + if (sBackgroundThread == null) { + sBackgroundThread = new HandlerThread("PackageMonitor", + android.os.Process.THREAD_PRIORITY_BACKGROUND); + sBackgroundThread.start(); + sBackgroundHandler = new Handler(sBackgroundThread.getLooper()); + } + mRegisteredHandler = sBackgroundHandler; + } + } else { + mRegisteredHandler = new Handler(thread); + } + context.registerReceiver(this, sPackageFilt, null, mRegisteredHandler); + context.registerReceiver(this, sNonDataFilt, null, mRegisteredHandler); if (externalStorage) { - context.registerReceiver(this, sExternalFilt); + context.registerReceiver(this, sExternalFilt, null, mRegisteredHandler); } } - + + public Handler getRegisteredHandler() { + return mRegisteredHandler; + } + public void unregister() { if (mRegisteredContext == null) { throw new IllegalStateException("Not registered"); |
