diff options
49 files changed, 1335 insertions, 499 deletions
diff --git a/api/current.txt b/api/current.txt index 9d89bdf..a309fe6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5526,8 +5526,8 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE"; field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID"; - field public static int FLAG_MANAGED_CAN_ACCESS_PARENT; - field public static int FLAG_PARENT_CAN_ACCESS_MANAGED; + field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2 + field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1 field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0 field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20 @@ -27428,11 +27428,12 @@ package android.speech.tts { ctor public TextToSpeech(android.content.Context, android.speech.tts.TextToSpeech.OnInitListener); ctor public TextToSpeech(android.content.Context, android.speech.tts.TextToSpeech.OnInitListener, java.lang.String); method public int addEarcon(java.lang.String, java.lang.String, int); - method public int addEarcon(java.lang.String, java.lang.String); + method public deprecated int addEarcon(java.lang.String, java.lang.String); + method public int addEarcon(java.lang.String, java.io.File); method public int addSpeech(java.lang.String, java.lang.String, int); method public int addSpeech(java.lang.CharSequence, java.lang.String, int); method public int addSpeech(java.lang.String, java.lang.String); - method public int addSpeech(java.lang.CharSequence, java.lang.String); + method public int addSpeech(java.lang.CharSequence, java.io.File); method public deprecated boolean areDefaultsEnforced(); method public java.util.Set<java.util.Locale> getAvailableLanguages(); method public java.lang.String getDefaultEngine(); @@ -27446,7 +27447,7 @@ package android.speech.tts { method public java.util.Set<android.speech.tts.Voice> getVoices(); method public int isLanguageAvailable(java.util.Locale); method public boolean isSpeaking(); - method public int playEarcon(java.lang.String, int, java.util.HashMap<java.lang.String, java.lang.String>, java.lang.String); + method public int playEarcon(java.lang.String, int, android.os.Bundle, java.lang.String); method public deprecated int playEarcon(java.lang.String, int, java.util.HashMap<java.lang.String, java.lang.String>); method public int playSilence(long, int, java.util.HashMap<java.lang.String, java.lang.String>, java.lang.String); method public deprecated int playSilence(long, int, java.util.HashMap<java.lang.String, java.lang.String>); @@ -27459,10 +27460,10 @@ package android.speech.tts { method public int setSpeechRate(float); method public int setVoice(android.speech.tts.Voice); method public void shutdown(); - method public int speak(java.lang.CharSequence, int, java.util.HashMap<java.lang.String, java.lang.String>, java.lang.String); + method public int speak(java.lang.CharSequence, int, android.os.Bundle, java.lang.String); method public deprecated int speak(java.lang.String, int, java.util.HashMap<java.lang.String, java.lang.String>); method public int stop(); - method public int synthesizeToFile(java.lang.CharSequence, java.util.HashMap<java.lang.String, java.lang.String>, java.lang.String, java.lang.String); + method public int synthesizeToFile(java.lang.CharSequence, android.os.Bundle, java.io.File, java.lang.String); method public deprecated int synthesizeToFile(java.lang.String, java.util.HashMap<java.lang.String, java.lang.String>, java.lang.String); field public static final java.lang.String ACTION_TTS_QUEUE_PROCESSING_COMPLETED = "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED"; field public static final int ERROR = -1; // 0xffffffff @@ -27535,15 +27536,15 @@ package android.speech.tts { public abstract class TextToSpeechService extends android.app.Service { ctor public TextToSpeechService(); - method protected int isValidVoiceName(java.lang.String); method public android.os.IBinder onBind(android.content.Intent); method protected java.lang.String onGetDefaultVoiceNameFor(java.lang.String, java.lang.String, java.lang.String); method protected java.util.Set<java.lang.String> onGetFeaturesForLanguage(java.lang.String, java.lang.String, java.lang.String); method protected abstract java.lang.String[] onGetLanguage(); - method protected java.util.List<android.speech.tts.Voice> onGetVoices(); + method public java.util.List<android.speech.tts.Voice> onGetVoices(); method protected abstract int onIsLanguageAvailable(java.lang.String, java.lang.String, java.lang.String); + method public int onIsValidVoiceName(java.lang.String); method protected abstract int onLoadLanguage(java.lang.String, java.lang.String, java.lang.String); - method protected int onLoadVoice(java.lang.String); + method public int onLoadVoice(java.lang.String); method protected abstract void onStop(); method protected abstract void onSynthesizeText(android.speech.tts.SynthesisRequest, android.speech.tts.SynthesisCallback); } @@ -27564,7 +27565,7 @@ package android.speech.tts { method public java.util.Locale getLocale(); method public java.lang.String getName(); method public int getQuality(); - method public boolean getRequiresNetworkConnection(); + method public boolean isNetworkConnectionRequired(); method public void writeToParcel(android.os.Parcel, int); field public static final int LATENCY_HIGH = 400; // 0x190 field public static final int LATENCY_LOW = 200; // 0xc8 @@ -28221,7 +28222,6 @@ package android.telecomm { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; - field public static final int ROUTE_ALL = 15; // 0xf field public static final int ROUTE_BLUETOOTH = 2; // 0x2 field public static final int ROUTE_EARPIECE = 1; // 0x1 field public static final int ROUTE_SPEAKER = 8; // 0x8 @@ -28238,7 +28238,7 @@ package android.telecomm { method public final void destroy(); method public final int getCapabilities(); method public final java.util.List<android.telecomm.Connection> getConnections(); - method public final android.telecomm.PhoneAccountHandle getPhoneAccount(); + method public final android.telecomm.PhoneAccountHandle getPhoneAccountHandle(); method public final int getState(); method public void onDisconnect(); method public void onHold(); @@ -28313,12 +28313,11 @@ package android.telecomm { } public final class ConnectionRequest implements android.os.Parcelable { - ctor public ConnectionRequest(android.telecomm.PhoneAccountHandle, android.net.Uri, int, android.os.Bundle); + ctor public ConnectionRequest(android.telecomm.PhoneAccountHandle, android.net.Uri, android.os.Bundle); method public int describeContents(); method public android.telecomm.PhoneAccountHandle getAccountHandle(); + method public android.net.Uri getAddress(); method public android.os.Bundle getExtras(); - method public android.net.Uri getHandle(); - method public int getHandlePresentation(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -28496,11 +28495,11 @@ package android.telecomm { public final class StatusHints implements android.os.Parcelable { ctor public StatusHints(android.content.ComponentName, java.lang.CharSequence, int, android.os.Bundle); method public int describeContents(); - method public android.content.ComponentName getComponentName(); method public android.os.Bundle getExtras(); method public android.graphics.drawable.Drawable getIcon(android.content.Context); method public int getIconResId(); method public java.lang.CharSequence getLabel(); + method public android.content.ComponentName getPackageName(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 5f7a17d..27a03b6 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -54,6 +54,10 @@ interface IAccessibilityServiceConnection { int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); + boolean computeClickPointInScreen(int accessibilityWindowId, long accessibilityNodeId, + int interactionId, IAccessibilityInteractionConnectionCallback callback, + long threadId); + AccessibilityWindowInfo getWindow(int windowId); List<AccessibilityWindowInfo> getWindows(); diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 9e80a4b..9f49194 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -205,6 +205,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { private boolean mIsStartingTransition; private ArrayList<GhostViewListeners> mGhostViewListeners = new ArrayList<GhostViewListeners>(); + private ArrayMap<View, Float> mOriginalAlphas = new ArrayMap<View, Float>(); public ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, @@ -580,6 +581,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { mWindow = null; mSharedElements.clear(); mTransitioningViews.clear(); + mOriginalAlphas.clear(); mResultReceiver = null; mPendingTransition = null; mListener = null; @@ -589,10 +591,27 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return getWindow().getTransitionBackgroundFadeDuration(); } - protected static void setTransitionAlpha(ArrayList<View> views, float alpha) { + protected void hideViews(ArrayList<View> views) { int count = views.size(); for (int i = 0; i < count; i++) { - views.get(i).setTransitionAlpha(alpha); + View view = views.get(i); + if (!mOriginalAlphas.containsKey(view)) { + mOriginalAlphas.put(view, view.getAlpha()); + } + view.setAlpha(0f); + view.setTransitionAlpha(0f); + } + } + + protected void showViews(ArrayList<View> views) { + int count = views.size(); + for (int i = 0; i < count; i++) { + View view = views.get(i); + Float alpha = mOriginalAlphas.remove(view); + if (alpha != null) { + view.setAlpha(alpha); + view.setTransitionAlpha(1f); + } } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 91a0aed..cc5c643 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2121,7 +2121,9 @@ class ContextImpl extends Context { public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) throws NameNotFoundException { final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED; - if (packageName.equals("system") || packageName.equals("android")) { + if ((packageName.equals("system") || packageName.equals("android")) + && user.getIdentifier() == UserHandle.getUserId( + mPackageInfo.getApplicationInfo().uid)) { return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, restricted, mDisplay, mOverrideConfiguration); } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index f432c49..f38c108 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -112,9 +112,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { protected void viewsReady(ArrayMap<String, View> sharedElements) { super.viewsReady(sharedElements); mIsReadyForTransition = true; - setTransitionAlpha(mSharedElements, 0); + hideViews(mSharedElements); if (getViewsTransition() != null) { - setTransitionAlpha(mTransitioningViews, 0); + hideViews(mTransitioningViews); } if (mIsReturning) { sendSharedElementDestination(); @@ -240,7 +240,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (!mIsCanceled) { mIsCanceled = true; if (getViewsTransition() == null || mIsViewsTransitionStarted) { - setTransitionAlpha(mSharedElements, 1); + showViews(mSharedElements); } else { mTransitioningViews.addAll(mSharedElements); } @@ -300,7 +300,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { // Now start shared element transition ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, mSharedElementNames); - setTransitionAlpha(mSharedElements, 1); + showViews(mSharedElements); scheduleSetSharedElementEnd(sharedElementSnapshots); ArrayList<SharedElementOriginalState> originalImageViewState = setSharedElementState(sharedElementState, sharedElementSnapshots); @@ -411,7 +411,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { @Override public void onTransitionStart(Transition transition) { mEnterViewsTransition = transition; - setTransitionAlpha(mTransitioningViews, 1); + showViews(mTransitioningViews); super.onTransitionStart(transition); } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index a59a927..982bbc4 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -126,8 +126,8 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } public void resetViews() { - setTransitionAlpha(mTransitioningViews, 1); - setTransitionAlpha(mSharedElements, 1); + showViews(mTransitioningViews); + showViews(mSharedElements); mIsHidden = true; if (!mIsReturning && getDecor() != null) { getDecor().suppressLayout(false); @@ -187,7 +187,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private void hideSharedElements() { moveSharedElementsFromOverlay(); if (!mIsHidden) { - setTransitionAlpha(mSharedElements, 0); + hideViews(mSharedElements); } mSharedElementsHidden = true; finishIfNecessary(); @@ -296,7 +296,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { transition.removeListener(this); exitTransitionComplete(); if (mIsHidden) { - setTransitionAlpha(mTransitioningViews, 1); + showViews(mTransitioningViews); } if (mSharedElementBundle != null) { delayCancel(); @@ -323,7 +323,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { transition.removeListener(this); sharedElementTransitionComplete(); if (mIsHidden) { - setTransitionAlpha(mSharedElements, 1); + showViews(mSharedElements); } } }); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 112fc82..926a348 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -441,13 +441,13 @@ public class DevicePolicyManager { * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from a * managed profile to its parent. */ - public static int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001; + public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001; /** * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from the * parent to its managed profile. */ - public static int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002; + public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002; /** * Return true if the given administrator component is currently diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 0a211cf..06d4c4a 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -285,6 +285,9 @@ public class PackageInstaller { * * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. + * @throws SecurityException when installation services are unavailable, + * such as when called from a restricted user. + * @throws IllegalArgumentException when {@link SessionParams} is invalid. * @return positive, non-zero unique ID that represents the created session. * This ID remains consistent across device reboots until the * session is finalized. IDs are not reused during a given boot. @@ -303,6 +306,11 @@ public class PackageInstaller { /** * Open an existing session to actively perform work. To succeed, the caller * must be the owner of the install session. + * + * @throws IOException if parameters were unsatisfiable, such as lack of + * disk space or unavailable media. + * @throws SecurityException when the caller does not own the session, or + * the session is invalid. */ public @NonNull Session openSession(int sessionId) throws IOException { try { @@ -319,6 +327,9 @@ public class PackageInstaller { * Update the icon representing the app being installed in a specific * session. This should be roughly * {@link ActivityManager#getLauncherLargeIconSize()} in both dimensions. + * + * @throws SecurityException when the caller does not own the session, or + * the session is invalid. */ public void updateSessionAppIcon(int sessionId, @Nullable Bitmap appIcon) { try { @@ -331,6 +342,9 @@ public class PackageInstaller { /** * Update the label representing the app being installed in a specific * session. + * + * @throws SecurityException when the caller does not own the session, or + * the session is invalid. */ public void updateSessionAppLabel(int sessionId, @Nullable CharSequence appLabel) { try { @@ -341,6 +355,15 @@ public class PackageInstaller { } } + /** + * Completely abandon the given session, destroying all staged data and + * rendering it invalid. Abandoned sessions will be reported to + * {@link SessionCallback} listeners as failures. This is equivalent to + * opening the session and calling {@link Session#abandon()}. + * + * @throws SecurityException when the caller does not own the session, or + * the session is invalid. + */ public void abandonSession(int sessionId) { try { mInstaller.abandonSession(sessionId); @@ -350,7 +373,11 @@ public class PackageInstaller { } /** - * Return details for a specific session. + * Return details for a specific session. No special permissions are + * required to retrieve these details. + * + * @return details for the requested session, or {@code null} if the session + * does not exist. */ public @Nullable SessionInfo getSessionInfo(int sessionId) { try { @@ -361,7 +388,7 @@ public class PackageInstaller { } /** - * Return list of all active install sessions, regardless of the installer. + * Return list of all known install sessions, regardless of the installer. */ public @NonNull List<SessionInfo> getAllSessions() { final ApplicationInfo info = mContext.getApplicationInfo(); @@ -379,7 +406,7 @@ public class PackageInstaller { } /** - * Return list of all install sessions owned by the calling app. + * Return list of all known install sessions owned by the calling app. */ public @NonNull List<SessionInfo> getMySessions() { try { @@ -547,7 +574,8 @@ public class PackageInstaller { } /** - * Register to watch for session lifecycle events. + * Register to watch for session lifecycle events. No special permissions + * are required to watch for these events. */ public void registerSessionCallback(@NonNull SessionCallback callback) { registerSessionCallback(callback, new Handler()); @@ -560,7 +588,8 @@ public class PackageInstaller { } /** - * Register to watch for session lifecycle events. + * Register to watch for session lifecycle events. No special permissions + * are required to watch for these events. * * @param handler to dispatch callback events through, otherwise uses * calling thread. @@ -593,7 +622,7 @@ public class PackageInstaller { } /** - * Unregister an existing callback. + * Unregister a previously registered callback. */ public void unregisterSessionCallback(@NonNull SessionCallback callback) { synchronized (mDelegates) { @@ -686,6 +715,12 @@ public class PackageInstaller { * start at the beginning of the file. * @param lengthBytes total size of the file being written, used to * preallocate the underlying disk space, or -1 if unknown. + * The system may clear various caches as needed to allocate + * this space. + * @throws IOException if trouble opening the file for writing, such as + * lack of disk space or unavailable media. + * @throws SecurityException if called after the session has been + * committed or abandoned. */ public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, long lengthBytes) throws IOException { @@ -719,6 +754,9 @@ public class PackageInstaller { * <p> * This returns all names which have been previously written through * {@link #openWrite(String, long, long)} as part of this session. + * + * @throws SecurityException if called after the session has been + * committed or abandoned. */ public @NonNull String[] getNames() throws IOException { try { @@ -738,6 +776,9 @@ public class PackageInstaller { * through {@link #openWrite(String, long, long)} as part of this * session. For example, this stream may be used to calculate a * {@link MessageDigest} of a written APK before committing. + * + * @throws SecurityException if called after the session has been + * committed or abandoned. */ public @NonNull InputStream openRead(@NonNull String name) throws IOException { try { @@ -759,6 +800,9 @@ public class PackageInstaller { * Once this method is called, no additional mutations may be performed * on the session. If the device reboots before the session has been * finalized, you may commit the session again. + * + * @throws SecurityException if streams opened through + * {@link #openWrite(String, long, long)} are still open. */ public void commit(@NonNull IntentSender statusReceiver) { try { @@ -783,7 +827,9 @@ public class PackageInstaller { /** * Completely abandon this session, destroying all staged data and - * rendering it invalid. + * rendering it invalid. Abandoned sessions will be reported to + * {@link SessionCallback} listeners as failures. This is equivalent to + * opening the session and calling {@link Session#abandon()}. */ public void abandon() { try { @@ -937,6 +983,18 @@ public class PackageInstaller { } /** {@hide} */ + public void setInstallFlagsInternal() { + installFlags |= PackageManager.INSTALL_INTERNAL; + installFlags &= ~PackageManager.INSTALL_EXTERNAL; + } + + /** {@hide} */ + public void setInstallFlagsExternal() { + installFlags |= PackageManager.INSTALL_EXTERNAL; + installFlags &= ~PackageManager.INSTALL_INTERNAL; + } + + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); pw.printHexPair("installFlags", installFlags); diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java index 022a106..0acf24b 100644 --- a/core/java/android/os/FileBridge.java +++ b/core/java/android/os/FileBridge.java @@ -75,6 +75,13 @@ public class FileBridge extends Thread { return mClosed; } + public void forceClose() { + IoUtils.closeQuietly(mTarget); + IoUtils.closeQuietly(mServer); + IoUtils.closeQuietly(mClient); + mClosed = true; + } + public void setTargetFile(FileDescriptor target) { mTarget = target; } @@ -89,7 +96,6 @@ public class FileBridge extends Thread { try { while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) { final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN); - if (cmd == CMD_WRITE) { // Shuttle data into local file int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN); @@ -118,15 +124,10 @@ public class FileBridge extends Thread { } } - } catch (ErrnoException e) { - Log.wtf(TAG, "Failed during bridge", e); - } catch (IOException e) { + } catch (ErrnoException | IOException e) { Log.wtf(TAG, "Failed during bridge", e); } finally { - IoUtils.closeQuietly(mTarget); - IoUtils.closeQuietly(mServer); - IoUtils.closeQuietly(mClient); - mClosed = true; + forceClose(); } } @@ -151,6 +152,7 @@ public class FileBridge extends Thread { writeCommandAndBlock(CMD_CLOSE, "close()"); } finally { IoBridge.closeAndSignalBlockedThreads(mClient); + IoUtils.closeQuietly(mClientPfd); } } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 4de5f41..8aa2689 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -468,19 +468,6 @@ public class AlwaysOnHotwordDetector { } /** - * FIXME: Remove once the prebuilts are updated. - * - * @hide - */ - @Deprecated - public Intent createIntentToEnroll() { - if (DBG) Slog.d(TAG, "createIntentToEnroll"); - synchronized (mLock) { - return getManageIntentLocked(MANAGE_ACTION_ENROLL); - } - } - - /** * Creates an intent to start the un-enrollment for the associated keyphrase. * This intent must be invoked using {@link Activity#startActivityForResult(Intent, int)}. * Starting re-enrollment is only valid if the keyphrase is already enrolled, @@ -502,19 +489,6 @@ public class AlwaysOnHotwordDetector { } /** - * FIXME: Remove once the prebuilts are updated. - * - * @hide - */ - @Deprecated - public Intent createIntentToUnEnroll() { - if (DBG) Slog.d(TAG, "createIntentToUnEnroll"); - synchronized (mLock) { - return getManageIntentLocked(MANAGE_ACTION_UN_ENROLL); - } - } - - /** * Creates an intent to start the re-enrollment for the associated keyphrase. * This intent must be invoked using {@link Activity#startActivityForResult(Intent, int)}. * Starting re-enrollment is only valid if the keyphrase is already enrolled, @@ -535,19 +509,6 @@ public class AlwaysOnHotwordDetector { } } - /** - * FIXME: Remove once the prebuilts are updated. - * - * @hide - */ - @Deprecated - public Intent createIntentToReEnroll() { - if (DBG) Slog.d(TAG, "createIntentToReEnroll"); - synchronized (mLock) { - return getManageIntentLocked(MANAGE_ACTION_RE_ENROLL); - } - } - private Intent getManageIntentLocked(int action) { if (mAvailability == STATE_INVALID) { throw new IllegalStateException("getManageIntent called on an invalid detector"); diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 7245975..a4b6e92 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -578,7 +578,7 @@ public class TextToSpeech { * * @deprecated Starting from API level 20, to select network synthesis, call * ({@link TextToSpeech#getVoices()}, find a suitable network voice - * ({@link Voice#getRequiresNetworkConnection()}) and pass it + * ({@link Voice#isNetworkConnectionRequired()}) and pass it * to {@link TextToSpeech#setVoice(Voice)}). */ @Deprecated @@ -596,7 +596,7 @@ public class TextToSpeech { * @deprecated Starting from API level 20, to select embedded synthesis, call * ({@link TextToSpeech#getVoices()}, find a suitable embedded voice - * ({@link Voice#getRequiresNetworkConnection()}) and pass it + * ({@link Voice#isNetworkConnectionRequired()}) and pass it * to {@link TextToSpeech#setVoice(Voice)}). */ @Deprecated @@ -957,20 +957,18 @@ public class TextToSpeech { * * @param text * The string of text. Example: <code>"south_south_east"</code> - * @param filename - * The full path to the sound file (for example: - * "/sdcard/mysounds/hello.wav") + * @param file + * File object pointing to the sound file. * * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. */ - public int addSpeech(CharSequence text, String filename) { + public int addSpeech(CharSequence text, File file) { synchronized (mStartLock) { - mUtterances.put(text, Uri.parse(filename)); + mUtterances.put(text, Uri.fromFile(file)); return SUCCESS; } } - /** * Adds a mapping between a string of text and a sound resource in a * package. Use this to add custom earcons. @@ -1017,7 +1015,11 @@ public class TextToSpeech { * "/sdcard/mysounds/tick.wav") * * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * + * @deprecated As of API level 20, replaced by + * {@link #addEarcon(String, File)}. */ + @Deprecated public int addEarcon(String earcon, String filename) { synchronized(mStartLock) { mEarcons.put(earcon, Uri.parse(filename)); @@ -1025,6 +1027,27 @@ public class TextToSpeech { } } + /** + * Adds a mapping between a string of text and a sound file. + * Use this to add custom earcons. + * + * @see #playEarcon(String, int, HashMap) + * + * @param earcon + * The name of the earcon. + * Example: <code>"[tick]"</code> + * @param file + * File object pointing to the sound file. + * + * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + */ + public int addEarcon(String earcon, File file) { + synchronized(mStartLock) { + mEarcons.put(earcon, Uri.fromFile(file)); + return SUCCESS; + } + } + private Uri makeResourceUri(String packageName, int resourceId) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) @@ -1061,7 +1084,7 @@ public class TextToSpeech { */ public int speak(final CharSequence text, final int queueMode, - final HashMap<String, String> params, + final Bundle params, final String utteranceId) { return runAction(new Action<Integer>() { @Override @@ -1103,11 +1126,11 @@ public class TextToSpeech { * * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation. * @deprecated As of API level 20, replaced by - * {@link #speak(CharSequence, int, HashMap, String)}. + * {@link #speak(CharSequence, int, Bundle, String)}. */ @Deprecated public int speak(final String text, final int queueMode, final HashMap<String, String> params) { - return speak(text, queueMode, params, + return speak(text, queueMode, convertParamsHashMaptoBundle(params), params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID)); } @@ -1135,7 +1158,7 @@ public class TextToSpeech { * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation. */ public int playEarcon(final String earcon, final int queueMode, - final HashMap<String, String> params, final String utteranceId) { + final Bundle params, final String utteranceId) { return runAction(new Action<Integer>() { @Override public Integer run(ITextToSpeechService service) throws RemoteException { @@ -1173,12 +1196,12 @@ public class TextToSpeech { * * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation. * @deprecated As of API level 20, replaced by - * {@link #playEarcon(String, int, HashMap, String)}. + * {@link #playEarcon(String, int, Bundle, String)}. */ @Deprecated public int playEarcon(final String earcon, final int queueMode, final HashMap<String, String> params) { - return playEarcon(earcon, queueMode, params, + return playEarcon(earcon, queueMode, convertParamsHashMaptoBundle(params), params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID)); } @@ -1757,22 +1780,20 @@ public class TextToSpeech { * must be prefixed by the name of the engine they are intended for. For example * the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the * engine named "com.svox.pico" if it is being used. - * @param filename Absolute file filename to write the generated audio data to.It should be - * something like "/sdcard/myappsounds/mysound.wav". + * @param file File to write the generated audio data to. * @param utteranceId An unique identifier for this request. * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation. */ - public int synthesizeToFile(final CharSequence text, final HashMap<String, String> params, - final String filename, final String utteranceId) { + public int synthesizeToFile(final CharSequence text, final Bundle params, + final File file, final String utteranceId) { return runAction(new Action<Integer>() { @Override public Integer run(ITextToSpeechService service) throws RemoteException { ParcelFileDescriptor fileDescriptor; int returnValue; try { - File file = new File(filename); if(file.exists() && !file.canWrite()) { - Log.e(TAG, "Can't write to " + filename); + Log.e(TAG, "Can't write to " + file); return ERROR; } fileDescriptor = ParcelFileDescriptor.open(file, @@ -1784,10 +1805,10 @@ public class TextToSpeech { fileDescriptor.close(); return returnValue; } catch (FileNotFoundException e) { - Log.e(TAG, "Opening file " + filename + " failed", e); + Log.e(TAG, "Opening file " + file + " failed", e); return ERROR; } catch (IOException e) { - Log.e(TAG, "Closing file " + filename + " failed", e); + Log.e(TAG, "Closing file " + file + " failed", e); return ERROR; } } @@ -1817,16 +1838,18 @@ public class TextToSpeech { * * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation. * @deprecated As of API level 20, replaced by - * {@link #synthesizeToFile(CharSequence, HashMap, String, String)}. + * {@link #synthesizeToFile(CharSequence, Bundle, File, String)}. */ + @Deprecated public int synthesizeToFile(final String text, final HashMap<String, String> params, final String filename) { - return synthesizeToFile(text, params, filename, params.get(Engine.KEY_PARAM_UTTERANCE_ID)); + return synthesizeToFile(text, convertParamsHashMaptoBundle(params), + new File(filename), params.get(Engine.KEY_PARAM_UTTERANCE_ID)); } - private Bundle getParams(HashMap<String, String> params) { + private Bundle convertParamsHashMaptoBundle(HashMap<String, String> params) { if (params != null && !params.isEmpty()) { - Bundle bundle = new Bundle(mParams); + Bundle bundle = new Bundle(); copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM); copyIntParam(bundle, params, Engine.KEY_PARAM_SESSION_ID); copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID); @@ -1852,11 +1875,85 @@ public class TextToSpeech { } return bundle; + } + return null; + } + + private Bundle getParams(Bundle params) { + if (params != null && !params.isEmpty()) { + Bundle bundle = new Bundle(mParams); + bundle.putAll(params); + + verifyIntegerBundleParam(bundle, Engine.KEY_PARAM_STREAM); + verifyIntegerBundleParam(bundle, Engine.KEY_PARAM_SESSION_ID); + verifyStringBundleParam(bundle, Engine.KEY_PARAM_UTTERANCE_ID); + verifyFloatBundleParam(bundle, Engine.KEY_PARAM_VOLUME); + verifyFloatBundleParam(bundle, Engine.KEY_PARAM_PAN); + + // Copy feature strings defined by the framework. + verifyBooleanBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_SYNTHESIS); + verifyBooleanBundleParam(bundle, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS); + verifyIntegerBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_TIMEOUT_MS); + verifyIntegerBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_RETRIES_COUNT); + + return bundle; } else { return mParams; } } + private static boolean verifyIntegerBundleParam(Bundle bundle, String key) { + if (bundle.containsKey(key)) { + if (!(bundle.get(key) instanceof Integer || + bundle.get(key) instanceof Long)) { + bundle.remove(key); + Log.w(TAG, "Synthesis request paramter " + key + " containst value " + + " with invalid type. Should be an Integer or a Long"); + return false; + } + } + return true; + } + + private static boolean verifyStringBundleParam(Bundle bundle, String key) { + if (bundle.containsKey(key)) { + if (!(bundle.get(key) instanceof String)) { + bundle.remove(key); + Log.w(TAG, "Synthesis request paramter " + key + " containst value " + + " with invalid type. Should be a String"); + return false; + } + } + return true; + } + + private static boolean verifyBooleanBundleParam(Bundle bundle, String key) { + if (bundle.containsKey(key)) { + if (!(bundle.get(key) instanceof Boolean || + bundle.get(key) instanceof String)) { + bundle.remove(key); + Log.w(TAG, "Synthesis request paramter " + key + " containst value " + + " with invalid type. Should be a Boolean or String"); + return false; + } + } + return true; + } + + + private static boolean verifyFloatBundleParam(Bundle bundle, String key) { + if (bundle.containsKey(key)) { + if (!(bundle.get(key) instanceof Float || + bundle.get(key) instanceof Double)) { + bundle.remove(key); + Log.w(TAG, "Synthesis request paramter " + key + " containst value " + + " with invalid type. Should be a Float or a Double"); + return false; + } + } + return true; + } + private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) { String value = params.get(key); if (value != null) { diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 4fea109..d00a433 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -84,7 +84,7 @@ import java.util.Set; * the following methods: * <ul> * <li>{@link #onGetVoices()}</li> - * <li>{@link #isValidVoiceName(String)}</li> + * <li>{@link #onIsValidVoiceName(String)}</li> * <li>{@link #onLoadVoice(String)}</li> * <li>{@link #onGetDefaultVoiceNameFor(String, String, String)}</li> * </ul> @@ -278,7 +278,7 @@ public abstract class TextToSpeechService extends Service { * * @return A list of voices supported. */ - protected List<Voice> onGetVoices() { + public List<Voice> onGetVoices() { // Enumerate all locales and check if they are available ArrayList<Voice> voices = new ArrayList<Voice>(); for (Locale locale : Locale.getAvailableLocales()) { @@ -335,7 +335,7 @@ public abstract class TextToSpeechService extends Service { } Locale properLocale = TtsEngines.normalizeTTSLocale(iso3Locale); String voiceName = properLocale.toLanguageTag(); - if (isValidVoiceName(voiceName) == TextToSpeech.SUCCESS) { + if (onIsValidVoiceName(voiceName) == TextToSpeech.SUCCESS) { return voiceName; } else { return null; @@ -357,7 +357,7 @@ public abstract class TextToSpeechService extends Service { * @param voiceName Name of the voice. * @return {@link TextToSpeech#ERROR} or {@link TextToSpeech#SUCCESS}. */ - protected int onLoadVoice(String voiceName) { + public int onLoadVoice(String voiceName) { Locale locale = Locale.forLanguageTag(voiceName); if (locale == null) { return TextToSpeech.ERROR; @@ -388,7 +388,7 @@ public abstract class TextToSpeechService extends Service { * @param voiceName Name of the voice. * @return {@link TextToSpeech#ERROR} or {@link TextToSpeech#SUCCESS}. */ - protected int isValidVoiceName(String voiceName) { + public int onIsValidVoiceName(String voiceName) { Locale locale = Locale.forLanguageTag(voiceName); if (locale == null) { return TextToSpeech.ERROR; @@ -1275,7 +1275,7 @@ public abstract class TextToSpeechService extends Service { if (!checkNonNull(voiceName)) { return TextToSpeech.ERROR; } - int retVal = isValidVoiceName(voiceName); + int retVal = onIsValidVoiceName(voiceName); if (retVal == TextToSpeech.SUCCESS) { SpeechItem item = new LoadVoiceItem(caller, Binder.getCallingUid(), diff --git a/core/java/android/speech/tts/Voice.java b/core/java/android/speech/tts/Voice.java index a97141c..a1fa51d 100644 --- a/core/java/android/speech/tts/Voice.java +++ b/core/java/android/speech/tts/Voice.java @@ -162,7 +162,7 @@ public class Voice implements Parcelable { /** * @return Does the Voice require a network connection to work. */ - public boolean getRequiresNetworkConnection() { + public boolean isNetworkConnectionRequired() { return mRequiresNetworkConnection; } diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index 0d1b568..40bb6ec 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1417,9 +1417,9 @@ public abstract class Transition implements Cloneable { } capturePropagationValues(values); if (start) { - addViewValues(mStartValues, view, values); + addViewValues(mStartValues, view, values, true); } else { - addViewValues(mEndValues, view, values); + addViewValues(mEndValues, view, values, true); } } } @@ -1460,7 +1460,7 @@ public abstract class Transition implements Cloneable { } static void addViewValues(TransitionValuesMaps transitionValuesMaps, - View view, TransitionValues transitionValues) { + View view, TransitionValues transitionValues, boolean setTransientState) { transitionValuesMaps.viewValues.put(view, transitionValues); int id = view.getId(); if (id >= 0) { @@ -1489,11 +1489,15 @@ public abstract class Transition implements Cloneable { // Duplicate item IDs: cannot match by item ID. View alreadyMatched = transitionValuesMaps.itemIdValues.get(itemId); if (alreadyMatched != null) { - alreadyMatched.setHasTransientState(false); + if (setTransientState) { + alreadyMatched.setHasTransientState(false); + } transitionValuesMaps.itemIdValues.put(itemId, null); } } else { - view.setHasTransientState(true); + if (setTransientState) { + view.setHasTransientState(true); + } transitionValuesMaps.itemIdValues.put(itemId, view); } } @@ -1560,9 +1564,9 @@ public abstract class Transition implements Cloneable { } capturePropagationValues(values); if (start) { - addViewValues(mStartValues, view, values); + addViewValues(mStartValues, view, values, true); } else { - addViewValues(mEndValues, view, values); + addViewValues(mEndValues, view, values, true); } } if (view instanceof ViewGroup) { diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index f6499ae..56db674 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -408,7 +408,7 @@ public class TransitionSet extends Transition { for (int i = 0; i < numValues; i++) { View view = values.viewValues.keyAt(i); if (isValidTarget(view)) { - addViewValues(included, view, values.viewValues.valueAt(i)); + addViewValues(included, view, values.viewValues.valueAt(i), false); } } return included; diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java index 0b02552..d648ca6 100644 --- a/core/java/android/transition/Visibility.java +++ b/core/java/android/transition/Visibility.java @@ -358,12 +358,16 @@ public abstract class Visibility extends Transition { overlayView = startView; } else if (startView.getParent() instanceof View) { View startParent = (View) startView.getParent(); - if (!isValidTarget(startParent)) { - if (startView.isAttachedToWindow()) { - overlayView = copyViewImage(startView); - } else { - overlayView = startView; - } + VisibilityInfo parentVisibilityInfo = null; + TransitionValues endParentValues = getMatchedTransitionValues(startParent, + true); + if (endParentValues != null) { + TransitionValues startParentValues = getTransitionValues(startParent, true); + parentVisibilityInfo = + getVisibilityChangeInfo(startParentValues, endParentValues); + } + if (parentVisibilityInfo == null || !parentVisibilityInfo.visibilityChange) { + overlayView = copyViewImage(startView); } else if (startParent.getParent() == null) { int id = startParent.getId(); if (id != View.NO_ID && sceneRoot.findViewById(id) != null diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index a10dda3..a283b91 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -19,7 +19,6 @@ package android.view; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -101,7 +100,7 @@ final class AccessibilityInteractionController { IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); - message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID; + message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID; message.arg1 = flags; SomeArgs args = SomeArgs.obtain(); @@ -176,7 +175,7 @@ final class AccessibilityInteractionController { IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); - message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID; + message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID; message.arg1 = flags; message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); @@ -261,7 +260,7 @@ final class AccessibilityInteractionController { IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); - message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT; + message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT; message.arg1 = flags; SomeArgs args = SomeArgs.obtain(); @@ -637,6 +636,95 @@ final class AccessibilityInteractionController { } } + public void computeClickPointInScreenClientThread(long accessibilityNodeId, + Region interactiveRegion, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, + long interrogatingTid, MagnificationSpec spec) { + Message message = mHandler.obtainMessage(); + message.what = PrivateHandler.MSG_COMPUTE_CLICK_POINT_IN_SCREEN; + + SomeArgs args = SomeArgs.obtain(); + args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); + args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); + args.argi3 = interactionId; + args.arg1 = callback; + args.arg2 = spec; + args.arg3 = interactiveRegion; + + 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 + // after this call completes the same thread but in the interrogating + // client can handle the message to generate the result. + if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { + AccessibilityInteractionClient.getInstanceForThread( + interrogatingTid).setSameThreadMessage(message); + } else { + mHandler.sendMessage(message); + } + } + + private void computeClickPointInScreenUiThread(Message message) { + SomeArgs args = (SomeArgs) message.obj; + final int accessibilityViewId = args.argi1; + final int virtualDescendantId = args.argi2; + final int interactionId = args.argi3; + final IAccessibilityInteractionConnectionCallback callback = + (IAccessibilityInteractionConnectionCallback) args.arg1; + final MagnificationSpec spec = (MagnificationSpec) args.arg2; + final Region interactiveRegion = (Region) args.arg3; + args.recycle(); + + boolean succeeded = false; + Point point = mTempPoint; + try { + if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { + return; + } + View target = null; + if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + target = findViewByAccessibilityId(accessibilityViewId); + } else { + target = mViewRootImpl.mView; + } + if (target != null && isShown(target)) { + AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); + if (provider != null) { + // For virtual views just use the center of the bounds in screen. + AccessibilityNodeInfo node = null; + if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + node = provider.createAccessibilityNodeInfo(virtualDescendantId); + } else { + node = provider.createAccessibilityNodeInfo( + AccessibilityNodeProvider.HOST_VIEW_ID); + } + if (node != null) { + succeeded = true; + Rect boundsInScreen = mTempRect; + node.getBoundsInScreen(boundsInScreen); + point.set(boundsInScreen.centerX(), boundsInScreen.centerY()); + } + } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + // For a real view, ask the view to compute the click point. + succeeded = target.computeClickPointInScreenForAccessibility( + interactiveRegion, point); + } + } + } finally { + try { + Point result = null; + if (succeeded) { + applyAppScaleAndMagnificationSpecIfNeeded(point, spec); + result = point; + } + callback.setComputeClickPointInScreenActionResult(result, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } + } + } + private View findViewByAccessibilityId(int accessibilityId) { View root = mViewRootImpl.mView; if (root == null) { @@ -688,6 +776,26 @@ final class AccessibilityInteractionController { } } + private void applyAppScaleAndMagnificationSpecIfNeeded(Point point, + MagnificationSpec spec) { + final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; + if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { + return; + } + + if (applicationScale != 1.0f) { + point.x *= applicationScale; + point.y *= applicationScale; + } + + if (spec != null) { + point.x *= spec.scale; + point.y *= spec.scale; + point.x += (int) spec.offsetX; + point.y += (int) spec.offsetY; + } + } + private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec) { if (info == null) { @@ -1080,11 +1188,12 @@ final class AccessibilityInteractionController { private class PrivateHandler extends Handler { private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; - private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; - private final static int MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID = 3; - private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4; + private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; + private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3; + private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4; private final static int MSG_FIND_FOCUS = 5; private final static int MSG_FOCUS_SEARCH = 6; + private final static int MSG_COMPUTE_CLICK_POINT_IN_SCREEN = 7; public PrivateHandler(Looper looper) { super(looper); @@ -1096,16 +1205,18 @@ final class AccessibilityInteractionController { switch (type) { case MSG_PERFORM_ACCESSIBILITY_ACTION: return "MSG_PERFORM_ACCESSIBILITY_ACTION"; - case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: - return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID"; - case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: - return "MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID"; - case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: - return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT"; + case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: + return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID"; + case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: + return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID"; + case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: + return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT"; case MSG_FIND_FOCUS: return "MSG_FIND_FOCUS"; case MSG_FOCUS_SEARCH: return "MSG_FOCUS_SEARCH"; + case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: + return "MSG_COMPUTE_CLICK_POINT_IN_SCREEN"; default: throw new IllegalArgumentException("Unknown message type: " + type); } @@ -1115,16 +1226,16 @@ final class AccessibilityInteractionController { public void handleMessage(Message message) { final int type = message.what; switch (type) { - case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: { + case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: { findAccessibilityNodeInfoByAccessibilityIdUiThread(message); } break; case MSG_PERFORM_ACCESSIBILITY_ACTION: { perfromAccessibilityActionUiThread(message); } break; - case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: { + case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: { findAccessibilityNodeInfosByViewIdUiThread(message); } break; - case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: { + case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: { findAccessibilityNodeInfosByTextUiThread(message); } break; case MSG_FIND_FOCUS: { @@ -1133,6 +1244,9 @@ final class AccessibilityInteractionController { case MSG_FOCUS_SEARCH: { focusSearchUiThread(message); } break; + case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: { + computeClickPointInScreenUiThread(message); + } break; default: throw new IllegalArgumentException("Unknown message type: " + type); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 770e78c..82c5425 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -35,6 +35,8 @@ import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathMeasure; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PorterDuff; @@ -5731,6 +5733,136 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Computes a point on which a sequence of a down/up event can be sent to + * trigger clicking this view. This method is for the exclusive use by the + * accessibility layer to determine where to send a click event in explore + * by touch mode. + * + * @param interactiveRegion The interactive portion of this window. + * @param outPoint The point to populate. + * @return True of such a point exists. + */ + boolean computeClickPointInScreenForAccessibility(Region interactiveRegion, + Point outPoint) { + // Since the interactive portion of the view is a region but as a view + // may have a transformation matrix which cannot be applied to a + // region we compute the view bounds rectangle and all interactive + // predecessor's and sibling's (siblings of predecessors included) + // rectangles that intersect the view bounds. At the + // end if the view was partially covered by another interactive + // view we compute the view's interactive region and pick a point + // on its boundary path as regions do not offer APIs to get inner + // points. Note that the the code is optimized to fail early and + // avoid unnecessary allocations plus computations. + + // The current approach has edge cases that may produce false + // positives or false negatives. For example, a portion of the + // view may be covered by an interactive descendant of a + // predecessor, which we do not compute. Also a view may be handling + // raw touch events instead registering click listeners, which + // we cannot compute. Despite these limitations this approach will + // work most of the time and it is a huge improvement over just + // blindly sending the down and up events in the center of the + // view. + + // Cannot click on an unattached view. + if (mAttachInfo == null) { + return false; + } + + // Attached to an invisible window means this view is not visible. + if (mAttachInfo.mWindowVisibility != View.VISIBLE) { + return false; + } + + RectF bounds = mAttachInfo.mTmpTransformRect; + bounds.set(0, 0, getWidth(), getHeight()); + List<RectF> intersections = mAttachInfo.mTmpRectList; + intersections.clear(); + + if (mParent instanceof ViewGroup) { + ViewGroup parentGroup = (ViewGroup) mParent; + if (!parentGroup.translateBoundsAndIntersectionsInWindowCoordinates( + this, bounds, intersections)) { + intersections.clear(); + return false; + } + } + + // Take into account the window location. + final int dx = mAttachInfo.mWindowLeft; + final int dy = mAttachInfo.mWindowTop; + bounds.offset(dx, dy); + offsetRects(intersections, dx, dy); + + if (intersections.isEmpty() && interactiveRegion == null) { + outPoint.set((int) bounds.centerX(), (int) bounds.centerY()); + } else { + // This view is partially covered by other views, then compute + // the not covered region and pick a point on its boundary. + Region region = new Region(); + region.set((int) bounds.left, (int) bounds.top, + (int) bounds.right, (int) bounds.bottom); + + final int intersectionCount = intersections.size(); + for (int i = intersectionCount - 1; i >= 0; i--) { + RectF intersection = intersections.remove(i); + region.op((int) intersection.left, (int) intersection.top, + (int) intersection.right, (int) intersection.bottom, + Region.Op.DIFFERENCE); + } + + // If the view is completely covered, done. + if (region.isEmpty()) { + return false; + } + + // Take into account the interactive portion of the window + // as the rest is covered by other windows. If no such a region + // then the whole window is interactive. + if (interactiveRegion != null) { + region.op(interactiveRegion, Region.Op.INTERSECT); + } + + // If the view is completely covered, done. + if (region.isEmpty()) { + return false; + } + + // Try a shortcut here. + if (region.isRect()) { + Rect regionBounds = mAttachInfo.mTmpInvalRect; + region.getBounds(regionBounds); + outPoint.set(regionBounds.centerX(), regionBounds.centerY()); + return true; + } + + // Get the a point on the region boundary path. + Path path = region.getBoundaryPath(); + PathMeasure pathMeasure = new PathMeasure(path, false); + final float[] coordinates = mAttachInfo.mTmpTransformLocation; + + // Without loss of generality pick a point. + final float point = pathMeasure.getLength() * 0.01f; + if (!pathMeasure.getPosTan(point, coordinates, null)) { + return false; + } + + outPoint.set(Math.round(coordinates[0]), Math.round(coordinates[1])); + } + + return true; + } + + static void offsetRects(List<RectF> rects, float offsetX, float offsetY) { + final int rectCount = rects.size(); + for (int i = 0; i < rectCount; i++) { + RectF intersection = rects.get(i); + intersection.offset(offsetX, offsetY); + } + } + + /** * Returns the delegate for implementing accessibility support via * composition. For more details see {@link AccessibilityDelegate}. * @@ -20154,6 +20286,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final RectF mTmpTransformRect = new RectF(); /** + * Temporary for use in computing hit areas with transformed views + */ + final RectF mTmpTransformRect1 = new RectF(); + + /** + * Temporary list of rectanges. + */ + final List<RectF> mTmpRectList = new ArrayList<>(); + + /** * Temporary for use in transforming invalidation rect */ final Matrix mTmpMatrix = new Matrix(); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index c1e66de..4e1db90 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -771,6 +771,112 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Translates the given bounds and intersections from child coordinates to + * local coordinates. In case any interactive sibling of the calling child + * covers the latter, a new intersections is added to the intersection list. + * This method is for the exclusive use by the accessibility layer to compute + * a point where a sequence of down and up events would click on a view. + * + * @param child The child making the call. + * @param bounds The bounds to translate in child coordinates. + * @param intersections The intersections of interactive views covering the child. + * @return True if the bounds and intersections were computed, false otherwise. + */ + boolean translateBoundsAndIntersectionsInWindowCoordinates(View child, + RectF bounds, List<RectF> intersections) { + // Not attached, done. + if (mAttachInfo == null) { + return false; + } + + if (getAlpha() <= 0 || getTransitionAlpha() <= 0 || + getVisibility() != VISIBLE) { + // Cannot click on a view with an invisible predecessor. + return false; + } + + // Compensate for the child transformation. + if (!child.hasIdentityMatrix()) { + Matrix matrix = child.getMatrix(); + matrix.mapRect(bounds); + final int intersectionCount = intersections.size(); + for (int i = 0; i < intersectionCount; i++) { + RectF intersection = intersections.get(i); + matrix.mapRect(intersection); + } + } + + // Translate the bounds from child to parent coordinates. + final int dx = child.mLeft - mScrollX; + final int dy = child.mTop - mScrollY; + bounds.offset(dx, dy); + offsetRects(intersections, dx, dy); + + // If the bounds do not intersect our bounds, done. + if (!bounds.intersects(0, 0, getWidth(), getHeight())) { + return false; + } + + // Check whether any clickable siblings cover the child + // view and if so keep track of the intersections. Also + // respect Z ordering when iterating over children. + ArrayList<View> orderedList = buildOrderedChildList(); + final boolean useCustomOrder = orderedList == null + && isChildrenDrawingOrderEnabled(); + + final int childCount = mChildrenCount; + for (int i = childCount - 1; i >= 0; i--) { + final int childIndex = useCustomOrder + ? getChildDrawingOrder(childCount, i) : i; + final View sibling = (orderedList == null) + ? mChildren[childIndex] : orderedList.get(childIndex); + + // We care only about siblings over the child. + if (sibling == child) { + break; + } + + // If sibling is not interactive we do not care. + if (!sibling.isClickable() && !sibling.isLongClickable()) { + continue; + } + + // Compute the sibling bounds in its coordinates. + RectF siblingBounds = mAttachInfo.mTmpTransformRect1; + siblingBounds.set(0, 0, sibling.getWidth(), sibling.getHeight()); + + // Take into account the sibling transformation matrix. + if (!sibling.hasIdentityMatrix()) { + sibling.getMatrix().mapRect(siblingBounds); + } + + // Offset the sibling to our coordinates. + final int siblingDx = sibling.mLeft - mScrollX; + final int siblingDy = sibling.mTop - mScrollY; + siblingBounds.offset(siblingDx, siblingDy); + + // Compute the intersection between the child and the sibling. + if (siblingBounds.intersect(bounds)) { + // If an interactive sibling completely covers the child, done. + if (siblingBounds.equals(bounds)) { + return false; + } + // Keep track of the intersection rectangle. + RectF intersection = new RectF(siblingBounds); + intersections.add(intersection); + } + } + + if (mParent instanceof ViewGroup) { + ViewGroup parentGroup = (ViewGroup) mParent; + return parentGroup.translateBoundsAndIntersectionsInWindowCoordinates( + this, bounds, intersections); + } + + return true; + } + + /** * Called when a child view has changed whether or not it is tracking transient state. */ public void childHasTransientStateChanged(View child, boolean childHasTransientState) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4299e2e..80b9ade 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6679,12 +6679,12 @@ public final class ViewRootImpl implements ViewParent, public void performAccessibilityAction(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interogatingPid, long interrogatingTid) { + int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .performAccessibilityActionClientThread(accessibilityNodeId, action, arguments, - interactionId, callback, flags, interogatingPid, interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -6696,6 +6696,26 @@ public final class ViewRootImpl implements ViewParent, } @Override + public void computeClickPointInScreen(long accessibilityNodeId, Region interactiveRegion, + int interactionId, IAccessibilityInteractionConnectionCallback callback, + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { + ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null && viewRootImpl.mView != null) { + viewRootImpl.getAccessibilityInteractionController() + .computeClickPointInScreenClientThread(accessibilityNodeId, + interactiveRegion, interactionId, callback, interrogatingPid, + interrogatingTid, spec); + } else { + // We cannot make the call and notify the caller so it does not wait. + try { + callback.setComputeClickPointInScreenActionResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } + } + } + + @Override public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index db78ec5..374f7e0 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -17,6 +17,7 @@ package android.view.accessibility; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.graphics.Point; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -98,6 +99,8 @@ public final class AccessibilityInteractionClient private boolean mPerformAccessibilityActionResult; + private Point mComputeClickPointResult; + private Message mSameThreadMessage; private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = @@ -519,6 +522,43 @@ public final class AccessibilityInteractionClient return false; } + /** + * Computes a point in screen coordinates where sending a down/up events would + * perform a click on an {@link AccessibilityNodeInfo}. + * + * @param connectionId The id of a connection for interacting with the system. + * @param accessibilityWindowId A unique window id. Use + * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id or virtual descendant id from + * where to start the search. Use + * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} + * to start from the root. + * @return Point the click point of null if no such point. + */ + public Point computeClickPointInScreen(int connectionId, int accessibilityWindowId, + long accessibilityNodeId) { + try { + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + final int interactionId = mInteractionIdCounter.getAndIncrement(); + final boolean success = connection.computeClickPointInScreen( + accessibilityWindowId, accessibilityNodeId, + interactionId, this, Thread.currentThread().getId()); + if (success) { + return getComputeClickPointInScreenResultAndClear(interactionId); + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } + } + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while calling remote computeClickPointInScreen", re); + } + return null; + } + public void clearCache() { sAccessibilityCache.clear(); } @@ -634,6 +674,34 @@ public final class AccessibilityInteractionClient } /** + * Gets the result of a request to compute a point in screen for clicking on a node. + * + * @param interactionId The interaction id to match the result with the request. + * @return The point or null if no such point. + */ + private Point getComputeClickPointInScreenResultAndClear(int interactionId) { + synchronized (mInstanceLock) { + final boolean success = waitForResultTimedLocked(interactionId); + Point result = success ? mComputeClickPointResult : null; + clearResultLocked(); + return result; + } + } + + /** + * {@inheritDoc} + */ + public void setComputeClickPointInScreenActionResult(Point point, int interactionId) { + synchronized (mInstanceLock) { + if (interactionId > mInteractionId) { + mComputeClickPointResult = point; + mInteractionId = interactionId; + } + mInstanceLock.notifyAll(); + } + } + + /** * Clears the result state. */ private void clearResultLocked() { @@ -641,6 +709,7 @@ public final class AccessibilityInteractionClient mFindAccessibilityNodeInfoResult = null; mFindAccessibilityNodeInfosResult = null; mPerformAccessibilityActionResult = false; + mComputeClickPointResult = null; } /** diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index faf7789..66a3f46 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -17,6 +17,7 @@ package android.view.accessibility; import android.graphics.Region; +import android.graphics.Point; import android.os.Bundle; import android.view.MagnificationSpec; import android.view.accessibility.AccessibilityNodeInfo; @@ -53,4 +54,8 @@ oneway interface IAccessibilityInteractionConnection { void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid); + + void computeClickPointInScreen(long accessibilityNodeId, in Region bounds, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, + long interrogatingTid, in MagnificationSpec spec); } diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl index c1a3ab7..f480216 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.graphics.Point; import android.view.accessibility.AccessibilityNodeInfo; import java.util.List; @@ -51,4 +52,12 @@ oneway interface IAccessibilityInteractionConnectionCallback { * @param interactionId The interaction id to match the result with the request. */ void setPerformAccessibilityActionResult(boolean succeeded, int interactionId); + + /** + * Sets the result of a request to compute a point for clicking in a view. + * + * @param point The point of null if no such point. + * @param interactionId The interaction id to match the result with the request. + */ + void setComputeClickPointInScreenActionResult(in Point point, int interactionId); } diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index c17f4ee..7bdb4be 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -390,7 +390,10 @@ public class PackageHelper { if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) { final File target = new UserEnvironment(UserHandle.USER_OWNER) .getExternalStorageDirectory(); - fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target)); + // External is only an option when size is known + if (sizeBytes > 0) { + fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target)); + } } if (prefer == RECOMMEND_INSTALL_INTERNAL) { diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java index 7db70ba..45d790b 100644 --- a/core/java/com/android/internal/util/XmlUtils.java +++ b/core/java/com/android/internal/util/XmlUtils.java @@ -1440,6 +1440,16 @@ public class XmlUtils { return Boolean.parseBoolean(value); } + public static boolean readBooleanAttribute(XmlPullParser in, String name, + boolean defaultValue) { + final String value = in.getAttributeValue(null, name); + if (value == null) { + return defaultValue; + } else { + return Boolean.parseBoolean(value); + } + } + public static void writeBooleanAttribute(XmlSerializer out, String name, boolean value) throws IOException { out.attribute(null, name, Boolean.toString(value)); diff --git a/core/tests/inputmethodtests/run_core_inputmethod_test.sh b/core/tests/inputmethodtests/run_core_inputmethod_test.sh index ed8b7f7..a11e49b 100755 --- a/core/tests/inputmethodtests/run_core_inputmethod_test.sh +++ b/core/tests/inputmethodtests/run_core_inputmethod_test.sh @@ -15,7 +15,7 @@ fi if [[ $rebuild == true ]]; then make -j4 FrameworksCoreInputMethodTests - TESTAPP=${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreInputMethodTests.apk + TESTAPP=${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreInputMethodTests/FrameworksCoreInputMethodTests.apk COMMAND="adb install -r $TESTAPP" echo $COMMAND $COMMAND diff --git a/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java b/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java index cc4a7c4..b6a03d9 100644 --- a/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java +++ b/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java @@ -26,14 +26,12 @@ import android.view.inputmethod.CursorAnchorInfo.Builder; import java.util.Objects; -import static android.view.inputmethod.CursorAnchorInfo.CHARACTER_RECT_TYPE_FULLY_VISIBLE; -import static android.view.inputmethod.CursorAnchorInfo.CHARACTER_RECT_TYPE_INVISIBLE; -import static android.view.inputmethod.CursorAnchorInfo.CHARACTER_RECT_TYPE_NOT_FEASIBLE; -import static android.view.inputmethod.CursorAnchorInfo.CHARACTER_RECT_TYPE_PARTIALLY_VISIBLE; -import static android.view.inputmethod.CursorAnchorInfo.CHARACTER_RECT_TYPE_UNSPECIFIED; +import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; +import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; +import static android.view.inputmethod.CursorAnchorInfo.FLAG_IS_RTL; public class CursorAnchorInfoTest extends InstrumentationTestCase { - private static final RectF[] MANY_RECTS = new RectF[] { + private static final RectF[] MANY_BOUNDS = new RectF[] { new RectF(101.0f, 201.0f, 301.0f, 401.0f), new RectF(102.0f, 202.0f, 302.0f, 402.0f), new RectF(103.0f, 203.0f, 303.0f, 403.0f), @@ -55,25 +53,25 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { new RectF(119.0f, 219.0f, 319.0f, 419.0f), }; private static final int[] MANY_FLAGS_ARRAY = new int[] { - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_INVISIBLE, - CHARACTER_RECT_TYPE_PARTIALLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_NOT_FEASIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_FULLY_VISIBLE, - CHARACTER_RECT_TYPE_NOT_FEASIBLE, - CHARACTER_RECT_TYPE_NOT_FEASIBLE, + FLAG_HAS_INVISIBLE_REGION, + FLAG_HAS_INVISIBLE_REGION | FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL, + FLAG_HAS_INVISIBLE_REGION | FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL, + FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL, + FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL, + FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL, + FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL, + FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL, + FLAG_HAS_VISIBLE_REGION, + FLAG_HAS_INVISIBLE_REGION, + FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL, }; @SmallTest @@ -82,11 +80,13 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { final int SELECTION_END = 40; final int COMPOSING_TEXT_START = 32; final String COMPOSING_TEXT = "test"; - final boolean INSERTION_MARKER_CLIPPED = true; + final int INSERTION_MARKER_FLAGS = + FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL; final float INSERTION_MARKER_HORIZONTAL = 10.5f; final float INSERTION_MARKER_TOP = 100.1f; final float INSERTION_MARKER_BASELINE = 110.4f; final float INSERTION_MARKER_BOTOM = 111.0f; + Matrix TRANSFORM_MATRIX = new Matrix(Matrix.IDENTITY_MATRIX); TRANSFORM_MATRIX.setScale(10.0f, 20.0f); @@ -94,13 +94,13 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { builder.setSelectionRange(SELECTION_START, SELECTION_END) .setComposingText(COMPOSING_TEXT_START, COMPOSING_TEXT) .setInsertionMarkerLocation(INSERTION_MARKER_HORIZONTAL, INSERTION_MARKER_TOP, - INSERTION_MARKER_BASELINE, INSERTION_MARKER_BOTOM, - INSERTION_MARKER_CLIPPED) + INSERTION_MARKER_BASELINE, INSERTION_MARKER_BOTOM, INSERTION_MARKER_FLAGS) .setMatrix(TRANSFORM_MATRIX); - for (int i = 0; i < MANY_RECTS.length; i++) { - final RectF rect = MANY_RECTS[i]; + for (int i = 0; i < MANY_BOUNDS.length; i++) { + final RectF bounds = MANY_BOUNDS[i]; final int flags = MANY_FLAGS_ARRAY[i]; - builder.addCharacterRect(i, rect.left, rect.top, rect.right, rect.bottom, flags); + builder.addCharacterBounds(i, bounds.left, bounds.top, bounds.right, bounds.bottom, + flags); } final CursorAnchorInfo info = builder.build(); @@ -108,26 +108,24 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { assertEquals(SELECTION_END, info.getSelectionEnd()); assertEquals(COMPOSING_TEXT_START, info.getComposingTextStart()); assertTrue(TextUtils.equals(COMPOSING_TEXT, info.getComposingText())); - assertTrue(info.isInsertionMarkerClipped()); + assertEquals(INSERTION_MARKER_FLAGS, info.getInsertionMarkerFlags()); assertEquals(INSERTION_MARKER_HORIZONTAL, info.getInsertionMarkerHorizontal()); assertEquals(INSERTION_MARKER_TOP, info.getInsertionMarkerTop()); assertEquals(INSERTION_MARKER_BASELINE, info.getInsertionMarkerBaseline()); assertEquals(INSERTION_MARKER_BOTOM, info.getInsertionMarkerBottom()); assertEquals(TRANSFORM_MATRIX, info.getMatrix()); - for (int i = 0; i < MANY_RECTS.length; i++) { - final RectF expectedRect = MANY_RECTS[i]; - assertEquals(expectedRect, info.getCharacterRect(i)); + for (int i = 0; i < MANY_BOUNDS.length; i++) { + final RectF expectedBounds = MANY_BOUNDS[i]; + assertEquals(expectedBounds, info.getCharacterRect(i)); } assertNull(info.getCharacterRect(-1)); - assertNull(info.getCharacterRect(MANY_RECTS.length + 1)); + assertNull(info.getCharacterRect(MANY_BOUNDS.length + 1)); for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) { final int expectedFlags = MANY_FLAGS_ARRAY[i]; assertEquals(expectedFlags, info.getCharacterRectFlags(i)); } - assertEquals(CHARACTER_RECT_TYPE_UNSPECIFIED, - info.getCharacterRectFlags(-1)); - assertEquals(CHARACTER_RECT_TYPE_UNSPECIFIED, - info.getCharacterRectFlags(MANY_RECTS.length + 1)); + assertEquals(0, info.getCharacterRectFlags(-1)); + assertEquals(0, info.getCharacterRectFlags(MANY_BOUNDS.length + 1)); // Make sure that the builder can reproduce the same object. final CursorAnchorInfo info2 = builder.build(); @@ -135,25 +133,24 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { assertEquals(SELECTION_END, info2.getSelectionEnd()); assertEquals(COMPOSING_TEXT_START, info2.getComposingTextStart()); assertTrue(TextUtils.equals(COMPOSING_TEXT, info2.getComposingText())); - assertTrue(info2.isInsertionMarkerClipped()); + assertEquals(INSERTION_MARKER_FLAGS, info2.getInsertionMarkerFlags()); assertEquals(INSERTION_MARKER_HORIZONTAL, info2.getInsertionMarkerHorizontal()); assertEquals(INSERTION_MARKER_TOP, info2.getInsertionMarkerTop()); assertEquals(INSERTION_MARKER_BASELINE, info2.getInsertionMarkerBaseline()); assertEquals(INSERTION_MARKER_BOTOM, info2.getInsertionMarkerBottom()); assertEquals(TRANSFORM_MATRIX, info2.getMatrix()); - for (int i = 0; i < MANY_RECTS.length; i++) { - final RectF expectedRect = MANY_RECTS[i]; - assertEquals(expectedRect, info2.getCharacterRect(i)); + for (int i = 0; i < MANY_BOUNDS.length; i++) { + final RectF expectedBounds = MANY_BOUNDS[i]; + assertEquals(expectedBounds, info2.getCharacterRect(i)); } assertNull(info2.getCharacterRect(-1)); - assertNull(info2.getCharacterRect(MANY_RECTS.length + 1)); + assertNull(info2.getCharacterRect(MANY_BOUNDS.length + 1)); for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) { final int expectedFlags = MANY_FLAGS_ARRAY[i]; assertEquals(expectedFlags, info2.getCharacterRectFlags(i)); } - assertEquals(CHARACTER_RECT_TYPE_UNSPECIFIED, info2.getCharacterRectFlags(-1)); - assertEquals(CHARACTER_RECT_TYPE_UNSPECIFIED, - info2.getCharacterRectFlags(MANY_RECTS.length + 1)); + assertEquals(0, info2.getCharacterRectFlags(-1)); + assertEquals(0, info2.getCharacterRectFlags(MANY_BOUNDS.length + 1)); assertEquals(info, info2); assertEquals(info.hashCode(), info2.hashCode()); @@ -163,25 +160,24 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { assertEquals(SELECTION_END, info3.getSelectionEnd()); assertEquals(COMPOSING_TEXT_START, info3.getComposingTextStart()); assertTrue(TextUtils.equals(COMPOSING_TEXT, info3.getComposingText())); - assertTrue(info3.isInsertionMarkerClipped()); + assertEquals(INSERTION_MARKER_FLAGS, info3.getInsertionMarkerFlags()); assertEquals(INSERTION_MARKER_HORIZONTAL, info3.getInsertionMarkerHorizontal()); assertEquals(INSERTION_MARKER_TOP, info3.getInsertionMarkerTop()); assertEquals(INSERTION_MARKER_BASELINE, info3.getInsertionMarkerBaseline()); assertEquals(INSERTION_MARKER_BOTOM, info3.getInsertionMarkerBottom()); assertEquals(TRANSFORM_MATRIX, info3.getMatrix()); - for (int i = 0; i < MANY_RECTS.length; i++) { - final RectF expectedRect = MANY_RECTS[i]; - assertEquals(expectedRect, info3.getCharacterRect(i)); + for (int i = 0; i < MANY_BOUNDS.length; i++) { + final RectF expectedBounds = MANY_BOUNDS[i]; + assertEquals(expectedBounds, info3.getCharacterRect(i)); } assertNull(info3.getCharacterRect(-1)); - assertNull(info3.getCharacterRect(MANY_RECTS.length + 1)); + assertNull(info3.getCharacterRect(MANY_BOUNDS.length + 1)); for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) { final int expectedFlags = MANY_FLAGS_ARRAY[i]; assertEquals(expectedFlags, info3.getCharacterRectFlags(i)); } - assertEquals(CHARACTER_RECT_TYPE_UNSPECIFIED, info3.getCharacterRectFlags(-1)); - assertEquals(CHARACTER_RECT_TYPE_UNSPECIFIED, - info3.getCharacterRectFlags(MANY_RECTS.length + 1)); + assertEquals(0, info3.getCharacterRectFlags(-1)); + assertEquals(0, info3.getCharacterRectFlags(MANY_BOUNDS.length + 1)); assertEquals(info.hashCode(), info3.hashCode()); builder.reset(); @@ -190,7 +186,7 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { assertEquals(-1, uninitializedInfo.getSelectionEnd()); assertEquals(-1, uninitializedInfo.getComposingTextStart()); assertNull(uninitializedInfo.getComposingText()); - assertFalse(uninitializedInfo.isInsertionMarkerClipped()); + assertEquals(0, uninitializedInfo.getInsertionMarkerFlags()); assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerHorizontal()); assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerTop()); assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBaseline()); @@ -218,7 +214,7 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { final int SELECTION_END1 = 7; final String COMPOSING_TEXT1 = "0123456789"; final int COMPOSING_TEXT_START1 = 0; - final boolean INSERTION_MARKER_CLIPPED1 = true; + final int INSERTION_MARKER_FLAGS1 = FLAG_HAS_VISIBLE_REGION; final float INSERTION_MARKER_HORIZONTAL1 = 10.5f; final float INSERTION_MARKER_TOP1 = 100.1f; final float INSERTION_MARKER_BASELINE1 = 110.4f; @@ -227,7 +223,8 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { final int SELECTION_END2 = 8; final String COMPOSING_TEXT2 = "9876543210"; final int COMPOSING_TEXT_START2 = 3; - final boolean INSERTION_MARKER_CLIPPED2 = false; + final int INSERTION_MARKER_FLAGS2 = + FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL; final float INSERTION_MARKER_HORIZONTAL2 = 14.5f; final float INSERTION_MARKER_TOP2 = 200.1f; final float INSERTION_MARKER_BASELINE2 = 210.4f; @@ -265,10 +262,10 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { assertEquals( new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( Float.NaN, Float.NaN, Float.NaN, Float.NaN, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( Float.NaN, Float.NaN, Float.NaN, Float.NaN, - INSERTION_MARKER_CLIPPED1).build()); + INSERTION_MARKER_FLAGS1).build()); // Check Matrix. assertEquals( @@ -290,74 +287,74 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build()); + INSERTION_MARKER_FLAGS1).build()); assertNotEquals( new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( Float.NaN, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build()); + INSERTION_MARKER_FLAGS1).build()); assertNotEquals( new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL2, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build()); + INSERTION_MARKER_FLAGS1).build()); assertNotEquals( new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP2, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build()); + INSERTION_MARKER_FLAGS1).build()); assertNotEquals( new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE2, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build()); + INSERTION_MARKER_FLAGS1).build()); assertNotEquals( new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL2, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build()); + INSERTION_MARKER_FLAGS1).build()); assertNotEquals( new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM2, - INSERTION_MARKER_CLIPPED1).build()); + INSERTION_MARKER_FLAGS1).build()); assertNotEquals( new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED1).build(), + INSERTION_MARKER_FLAGS1).build(), new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation( INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1, INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1, - INSERTION_MARKER_CLIPPED2).build()); + INSERTION_MARKER_FLAGS2).build()); } @SmallTest @@ -394,7 +391,7 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { final int SELECTION_END = 40; final int COMPOSING_TEXT_START = 32; final String COMPOSING_TEXT = "test"; - final boolean INSERTION_MARKER_CLIPPED = true; + final int INSERTION_MARKER_FLAGS = FLAG_HAS_VISIBLE_REGION; final float INSERTION_MARKER_HORIZONTAL = 10.5f; final float INSERTION_MARKER_TOP = 100.1f; final float INSERTION_MARKER_BASELINE = 110.4f; @@ -416,7 +413,7 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { } builder.setInsertionMarkerLocation(INSERTION_MARKER_HORIZONTAL, INSERTION_MARKER_TOP, - INSERTION_MARKER_BASELINE, INSERTION_MARKER_BOTOM, INSERTION_MARKER_CLIPPED); + INSERTION_MARKER_BASELINE, INSERTION_MARKER_BOTOM, INSERTION_MARKER_FLAGS); try { // Coordinate transformation matrix is required if no positional information is // specified. @@ -438,19 +435,10 @@ public class CursorAnchorInfoTest extends InstrumentationTestCase { } @SmallTest - public void testBuilderAddCharacterRect() throws Exception { + public void testBuilderAddCharacterBounds() throws Exception { // A negative index should be rejected. try { - new Builder().addCharacterRect(-1, 0.0f, 0.0f, 0.0f, 0.0f, - CHARACTER_RECT_TYPE_FULLY_VISIBLE); - assertTrue(false); - } catch (IllegalArgumentException ex) { - } - - // CHARACTER_RECT_TYPE_UNSPECIFIED is not allowed. - try { - new Builder().addCharacterRect(0, 0.0f, 0.0f, 0.0f, 0.0f, - CHARACTER_RECT_TYPE_UNSPECIFIED); + new Builder().addCharacterBounds(-1, 0.0f, 0.0f, 0.0f, 0.0f, FLAG_HAS_VISIBLE_REGION); assertTrue(false); } catch (IllegalArgumentException ex) { } diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index a10e70f..254492f 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -872,6 +872,8 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { handler(new (alloc) DrawLayerOp(mLayer, 0, 0), renderer.getSaveCount() - 1, properties().getClipToBounds()); } else { + const int saveCountOffset = renderer.getSaveCount() - 1; + const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex; DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance(); for (size_t chunkIndex = 0; chunkIndex < mDisplayListData->getChunks().size(); chunkIndex++) { const DisplayListData::Chunk& chunk = mDisplayListData->getChunks()[chunkIndex]; @@ -882,8 +884,6 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { issueOperationsOf3dChildren(kNegativeZChildren, initialTransform, zTranslatedNodes, renderer, handler); - const int saveCountOffset = renderer.getSaveCount() - 1; - const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex; for (int opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { DisplayListOp *op = mDisplayListData->displayListOps[opIndex]; diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 082a158..2c805bb 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -1109,9 +1109,9 @@ public class LocationManager { * {@link #requestLocationUpdates(String, long, float, LocationListener)}. * * <p> - * Before API version 20, this method would throw {@link SecurityException} - * if the location permissions were not sufficient to use the specified - * provider. + * Before API version {@link android.os.Build.VERSION_CODES#L}, this + * method would throw {@link SecurityException} if the location permissions + * were not sufficient to use the specified provider. * * @param provider the name of the provider * @return true if the provider exists and is enabled @@ -1119,7 +1119,6 @@ public class LocationManager { * @throws IllegalArgumentException if provider is null */ public boolean isProviderEnabled(String provider) { - // STOPSHIP: finalize API version number in javadoc checkProvider(provider); try { diff --git a/packages/SystemUI/res/drawable/stat_sys_vpn_ic.xml b/packages/SystemUI/res/drawable/stat_sys_vpn_ic.xml new file mode 100644 index 0000000..7ca8c40 --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_vpn_ic.xml @@ -0,0 +1,24 @@ +<!-- +Copyright (C) 2014 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17.0dp" + android:height="17.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12.700000,10.000000c-0.800000,-2.300000 -3.000000,-4.000000 -5.700000,-4.000000c-3.300000,0.000000 -6.000000,2.700000 -6.000000,6.000000s2.700000,6.000000 6.000000,6.000000c2.600000,0.000000 4.800000,-1.700000 5.700000,-4.000000L17.000000,14.000000l0.000000,4.000000l4.000000,0.000000l0.000000,-4.000000l2.000000,0.000000l0.000000,-4.000000L12.700000,10.000000zM7.000000,14.000000c-1.100000,0.000000 -2.000000,-0.900000 -2.000000,-2.000000c0.000000,-1.100000 0.900000,-2.000000 2.000000,-2.000000s2.000000,0.900000 2.000000,2.000000C9.000000,13.100000 8.100000,14.000000 7.000000,14.000000z"/> +</vector> diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml index 347c8a9..3a8a17d 100644 --- a/packages/SystemUI/res/layout/signal_cluster_view.xml +++ b/packages/SystemUI/res/layout/signal_cluster_view.xml @@ -25,6 +25,13 @@ android:gravity="center_vertical" android:orientation="horizontal" > + <ImageView + android:id="@+id/vpn" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:paddingEnd="6dp" + android:src="@drawable/stat_sys_vpn_ic" + /> <FrameLayout android:id="@+id/wifi_combo" android:layout_height="wrap_content" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index 5883c26..740211f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -27,17 +27,21 @@ import android.widget.LinearLayout; import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkControllerImpl; +import com.android.systemui.statusbar.policy.SecurityController; // Intimately tied to the design of res/layout/signal_cluster_view.xml public class SignalClusterView extends LinearLayout - implements NetworkControllerImpl.SignalCluster { + implements NetworkControllerImpl.SignalCluster, + SecurityController.SecurityControllerCallback { static final String TAG = "SignalClusterView"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); NetworkControllerImpl mNC; + SecurityController mSC; + private boolean mVpnVisible = false; private boolean mWifiVisible = false; private int mWifiStrengthId = 0; private boolean mMobileVisible = false; @@ -48,7 +52,7 @@ public class SignalClusterView private boolean mRoaming; ViewGroup mWifiGroup, mMobileGroup; - ImageView mWifi, mMobile, mMobileType, mAirplane; + ImageView mVpn, mWifi, mMobile, mMobileType, mAirplane; View mWifiAirplaneSpacer; public SignalClusterView(Context context) { @@ -68,10 +72,18 @@ public class SignalClusterView mNC = nc; } + public void setSecurityController(SecurityController sc) { + if (DEBUG) Log.d(TAG, "SecurityController=" + sc); + mSC = sc; + mSC.addCallback(this); + mVpnVisible = mSC.isVpnEnabled(); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + mVpn = (ImageView) findViewById(R.id.vpn); mWifiGroup = (ViewGroup) findViewById(R.id.wifi_combo); mWifi = (ImageView) findViewById(R.id.wifi_signal); mMobileGroup = (ViewGroup) findViewById(R.id.mobile_combo); @@ -85,6 +97,7 @@ public class SignalClusterView @Override protected void onDetachedFromWindow() { + mVpn = null; mWifiGroup = null; mWifi = null; mMobileGroup = null; @@ -95,6 +108,18 @@ public class SignalClusterView super.onDetachedFromWindow(); } + // From SecurityController. + @Override + public void onStateChanged() { + post(new Runnable() { + @Override + public void run() { + mVpnVisible = mSC.isVpnEnabled(); + apply(); + } + }); + } + @Override public void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription) { mWifiVisible = visible; @@ -168,6 +193,8 @@ public class SignalClusterView private void apply() { if (mWifiGroup == null) return; + mVpn.setVisibility(mVpnVisible ? View.VISIBLE : View.GONE); + if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE")); if (mWifiVisible) { mWifi.setImageResource(mWifiStrengthId); mWifiGroup.setContentDescription(mWifiDescription); 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 861bf4a..8497d8a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -786,8 +786,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNetworkController.addSignalCluster(signalCluster); mNetworkController.addSignalCluster(signalClusterKeyguard); mNetworkController.addSignalCluster(signalClusterQs); + signalCluster.setSecurityController(mSecurityController); signalCluster.setNetworkController(mNetworkController); + signalClusterKeyguard.setSecurityController(mSecurityController); signalClusterKeyguard.setNetworkController(mNetworkController); + signalClusterQs.setSecurityController(mSecurityController); signalClusterQs.setNetworkController(mNetworkController); final boolean isAPhone = mNetworkController.hasVoiceCallingFeature(); if (isAPhone) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 4a6f1a8..f04d6a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -928,8 +928,8 @@ public class NetworkControllerImpl extends BroadcastReceiver intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); boolean wasConnected = mWifiConnected; mWifiConnected = networkInfo != null && networkInfo.isConnected(); - // If we just connected, grab the inintial signal strength and ssid - if (mWifiConnected && !wasConnected) { + // If Connected grab the signal strength and ssid + if (mWifiConnected) { // try getting it out of the intent first WifiInfo info = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); if (info == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index a15ddaf..2fbb812 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -139,14 +139,14 @@ public class SecurityControllerImpl implements SecurityController { } @Override - public void addCallback(SecurityControllerCallback callback) { + public void removeCallback(SecurityControllerCallback callback) { if (callback == null) return; if (DEBUG) Log.d(TAG, "removeCallback " + callback); mCallbacks.remove(callback); } @Override - public void removeCallback(SecurityControllerCallback callback) { + public void addCallback(SecurityControllerCallback callback) { if (callback == null || mCallbacks.contains(callback)) return; if (DEBUG) Log.d(TAG, "addCallback " + callback); mCallbacks.add(callback); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index a43a2a6..ebe21ff 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -746,14 +746,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } /** - * Gets the bounds of the accessibility focus in the active window. + * Gets a point within the accessibility focused node where we can send down + * and up events to perform a click. * - * @param outBounds The output to which to write the focus bounds. - * @return Whether accessibility focus was found and the bounds are populated. + * @param outPoint The click point to populate. + * @return Whether accessibility a click point was found and set. */ // TODO: (multi-display) Make sure this works for multiple displays. - boolean getAccessibilityFocusBounds(Rect outBounds) { - return getInteractionBridgeLocked().getAccessibilityFocusBoundsNotLocked(outBounds); + boolean getAccessibilityFocusClickPointInScreen(Point outPoint) { + return getInteractionBridgeLocked() + .getAccessibilityFocusClickPointInScreenNotLocked(outPoint); } /** @@ -2196,8 +2198,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName, - partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, - interrogatingTid, spec); + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2248,8 +2250,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, - partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, - interrogatingTid, spec); + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2352,8 +2354,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final long identityToken = Binder.clearCallingIdentity(); MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { - connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, interactionId, - callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); + connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, + interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, + spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2403,8 +2406,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final long identityToken = Binder.clearCallingIdentity(); MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { - connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, interactionId, - callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); + connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, + interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, + spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2460,6 +2464,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return true; } + @Override public boolean performGlobalAction(int action) { synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles @@ -2501,6 +2506,57 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override + public boolean computeClickPointInScreen(int accessibilityWindowId, + long accessibilityNodeId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) + throws RemoteException { + final int resolvedWindowId; + IAccessibilityInteractionConnection connection = null; + Region partialInteractiveRegion = mTempRegion; + synchronized (mLock) { + // We treat calls from a profile as if made by its parent as profiles + // share the accessibility state of the parent. The call below + // performs the current profile parent resolution. + final int resolvedUserId = mSecurityPolicy + .resolveCallingUserIdEnforcingPermissionsLocked( + UserHandle.getCallingUserId()); + if (resolvedUserId != mCurrentUserId) { + return false; + } + resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); + final boolean permissionGranted = + mSecurityPolicy.canRetrieveWindowContentLocked(this); + if (!permissionGranted) { + return false; + } else { + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { + return false; + } + } + if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( + resolvedWindowId, partialInteractiveRegion)) { + partialInteractiveRegion = null; + } + } + final int interrogatingPid = Binder.getCallingPid(); + final long identityToken = Binder.clearCallingIdentity(); + MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); + try { + connection.computeClickPointInScreen(accessibilityNodeId, partialInteractiveRegion, + interactionId, callback, interrogatingPid, interrogatingTid, spec); + return true; + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Error computeClickPointInScreen()."); + } + } finally { + Binder.restoreCallingIdentity(identityToken); + } + return false; + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); synchronized (mLock) { @@ -3119,30 +3175,43 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - public boolean getAccessibilityFocusBoundsNotLocked(Rect outBounds) { + public boolean getAccessibilityFocusClickPointInScreenNotLocked(Point outPoint) { AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked(); if (focus == null) { return false; } synchronized (mLock) { - focus.getBoundsInScreen(outBounds); + Point point = mClient.computeClickPointInScreen(mConnectionId, + focus.getWindowId(), focus.getSourceNodeId()); + + if (point == null) { + return false; + } MagnificationSpec spec = getCompatibleMagnificationSpecLocked(focus.getWindowId()); if (spec != null && !spec.isNop()) { - outBounds.offset((int) -spec.offsetX, (int) -spec.offsetY); - outBounds.scale(1 / spec.scale); + point.offset((int) -spec.offsetX, (int) -spec.offsetY); + point.x = (int) (point.x * (1 / spec.scale)); + point.y = (int) (point.y * (1 / spec.scale)); } - // Clip to the window rectangle. + // Make sure the point is within the window. Rect windowBounds = mTempRect; getActiveWindowBounds(windowBounds); - outBounds.intersect(windowBounds); + if (!windowBounds.contains(point.x, point.y)) { + return false; + } - // Clip to the screen rectangle. - mDefaultDisplay.getRealSize(mTempPoint); - outBounds.intersect(0, 0, mTempPoint.x, mTempPoint.y); + // Make sure the point is within the screen. + Point screenSize = mTempPoint; + mDefaultDisplay.getRealSize(screenSize); + if (point.x < 0 || point.x > screenSize.x + || point.y < 0 || point.y > screenSize.y) { + return false; + } + outPoint.set(point.x, point.y); return true; } } diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index ac0ca0a..9e63433 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -24,6 +24,7 @@ import android.gesture.GesturePoint; import android.gesture.GestureStore; import android.gesture.GestureStroke; import android.gesture.Prediction; +import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; @@ -171,6 +172,9 @@ class TouchExplorer implements EventStreamTransformation { // Temporary rectangle to avoid instantiation. private final Rect mTempRect = new Rect(); + // Temporary point to avoid instantiation. + private final Point mTempPoint = new Point(); + // Context in which this explorer operates. private final Context mContext; @@ -1157,18 +1161,18 @@ class TouchExplorer implements EventStreamTransformation { mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent == null) { // No last touch explored event but there is accessibility focus in - // the active window. We click in the middle of the focus bounds. - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); + // the active window. We click in the focus bounds. + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } else { // Out of luck - do nothing. return; } } else { // If the click is within the active window but not within the - // accessibility focus bounds we click in the focus center. + // accessibility focus bounds we click in the focus bounds. final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); @@ -1176,12 +1180,10 @@ class TouchExplorer implements EventStreamTransformation { if (mLastTouchedWindowId == mAms.getActiveWindowId()) { mAms.getActiveWindowBounds(activeWindowBounds); if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - if (!focusBounds.contains(clickLocationX, clickLocationY)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } } } @@ -1330,18 +1332,18 @@ class TouchExplorer implements EventStreamTransformation { mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent == null) { // No last touch explored event but there is accessibility focus in - // the active window. We click in the middle of the focus bounds. - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); + // the active window. We click in the focus bounds. + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } else { // Out of luck - do nothing. return; } } else { // If the click is within the active window but not within the - // accessibility focus bounds we click in the focus center. + // accessibility focus bounds we click in the focus bounds. final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); @@ -1349,12 +1351,10 @@ class TouchExplorer implements EventStreamTransformation { if (mLastTouchedWindowId == mAms.getActiveWindowId()) { mAms.getActiveWindowBounds(activeWindowBounds); if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - if (!focusBounds.contains(clickLocationX, clickLocationY)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } } } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 6168546..bcf3b17 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -425,13 +425,13 @@ final class ActivityStack { } final ActivityRecord topActivity() { - // Iterate to find the first non-empty task stack. Note that this code can - // be simplified once we stop storing tasks with empty mActivities lists. for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; - final int topActivityNdx = activities.size() - 1; - if (topActivityNdx >= 0) { - return activities.get(topActivityNdx); + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (!r.finishing) { + return r; + } } } return null; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index f821daf..45479e3 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1772,7 +1772,7 @@ public final class ActivityStackSupervisor implements DisplayListener { if (intentActivity != null) { if (isLockTaskModeViolation(intentActivity.task)) { showLockTaskToast(); - Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode"); + Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } if (r.task == null) { diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java index 317ebef..b71bad8 100644 --- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java +++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java @@ -43,6 +43,7 @@ import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.Objects; +import java.util.TimeZone; /** Built-in zen condition provider for managing downtime */ public class DowntimeConditionProvider extends ConditionProviderService { @@ -275,6 +276,9 @@ public class DowntimeConditionProvider extends ConditionProviderService { final long schTime = intent.getLongExtra(EXTRA_TIME, 0); if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", action, ts(schTime), ts(now), now - schTime)); + } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault()); + mCalendar.setTimeZone(TimeZone.getDefault()); } else { if (DEBUG) Slog.d(TAG, action + " fired at " + now); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 89ea905..496c136 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -42,7 +42,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; -import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; @@ -50,15 +49,11 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; -import android.content.pm.PackageParser.PackageLite; -import android.content.pm.PackageParser.PackageParserException; import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; -import android.os.Environment.UserEnvironment; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; @@ -70,7 +65,6 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; @@ -126,6 +120,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final String ATTR_CREATED_MILLIS = "createdMillis"; private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; + private static final String ATTR_PREPARED = "prepared"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; @@ -148,7 +143,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final PackageManagerService mPm; private final AppOpsManager mAppOps; - private final StorageManager mStorage; private final File mStagingDir; private final HandlerThread mInstallThread; @@ -190,7 +184,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mContext = context; mPm = pm; mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - mStorage = StorageManager.from(mContext); mStagingDir = stagingDir; @@ -266,7 +259,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { try { final int sessionId = allocateSessionIdLocked(); mLegacySessions.put(sessionId, true); - return prepareInternalStageDir(sessionId); + final File stageDir = buildInternalStageDir(sessionId); + prepareInternalStageDir(stageDir); + return stageDir; } catch (IllegalStateException e) { throw new IOException(e); } @@ -345,6 +340,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); + final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final SessionParams params = new SessionParams( @@ -362,7 +358,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, stageDir, stageCid, sealed); + createdMillis, stageDir, stageCid, prepared, sealed); } private void writeSessionsLocked() { @@ -410,6 +406,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { if (session.stageCid != null) { writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid); } + writeBooleanAttribute(out, ATTR_PREPARED, session.isPrepared()); writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); writeIntAttribute(out, ATTR_MODE, params.mode); @@ -483,14 +480,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - // TODO: treat INHERIT_EXISTING as install for user - - // Figure out where we're going to be staging session data - final boolean stageInternal; - - if (params.mode == SessionParams.MODE_FULL_INSTALL) { - // Brand new install, use best resolved location. This also verifies - // that target has enough free space for the install. + if (params.mode == SessionParams.MODE_FULL_INSTALL + || params.mode == SessionParams.MODE_INHERIT_EXISTING) { + // Resolve best location for install, based on combination of + // requested install flags, delta size, and manifest settings. final long ident = Binder.clearCallingIdentity(); try { final int resolved = PackageHelper.resolveInstallLocation(mContext, @@ -498,46 +491,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags); if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) { - stageInternal = true; + params.setInstallFlagsInternal(); } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { - stageInternal = false; + params.setInstallFlagsExternal(); } else { throw new IOException("No storage with enough free space; res=" + resolved); } } finally { Binder.restoreCallingIdentity(ident); } - } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { - // Inheriting existing install, so stay on the same storage medium. - final ApplicationInfo existingApp = mPm.getApplicationInfo(params.appPackageName, 0, - userId); - if (existingApp == null) { - throw new IllegalStateException( - "Missing existing app " + params.appPackageName); - } - - final long existingSize; - try { - final PackageLite existingPkg = PackageParser.parsePackageLite( - new File(existingApp.getCodePath()), 0); - existingSize = PackageHelper.calculateInstalledSize(existingPkg, false, - params.abiOverride); - } catch (PackageParserException e) { - throw new IllegalStateException( - "Failed to calculate size of " + params.appPackageName); - } - - if ((existingApp.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0) { - // Internal we can link existing install into place, so we only - // need enough space for the new data. - checkInternalStorage(params.sizeBytes); - stageInternal = true; - } else { - // External we're going to copy existing install into our - // container, so we need footprint of both. - checkExternalStorage(params.sizeBytes + existingSize); - stageInternal = false; - } } else { throw new IllegalArgumentException("Invalid install mode: " + params.mode); } @@ -563,15 +525,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // We're staging to exactly one location File stageDir = null; String stageCid = null; - if (stageInternal) { - stageDir = prepareInternalStageDir(sessionId); + if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { + stageDir = buildInternalStageDir(sessionId); } else { - stageCid = prepareExternalStageCid(sessionId, params.sizeBytes); + stageCid = buildExternalStageCid(sessionId); } session = new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, stageDir, stageCid, false); + createdMillis, stageDir, stageCid, false, false); mSessions.put(sessionId, session); } @@ -615,32 +577,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - private void checkInternalStorage(long sizeBytes) throws IOException { - if (sizeBytes <= 0) return; - - final File target = Environment.getDataDirectory(); - final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); - - mPm.freeStorage(targetBytes); - if (target.getUsableSpace() < targetBytes) { - throw new IOException("Not enough internal space to write " + sizeBytes + " bytes"); - } - } - - private void checkExternalStorage(long sizeBytes) throws IOException { - if (sizeBytes <= 0) return; - - final File target = new UserEnvironment(UserHandle.USER_OWNER) - .getExternalStorageDirectory(); - final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); - - if (target.getUsableSpace() < targetBytes) { - throw new IOException("Not enough external space to write " + sizeBytes + " bytes"); + @Override + public IPackageInstallerSession openSession(int sessionId) { + try { + return openSessionInternal(sessionId); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); } } - @Override - public IPackageInstallerSession openSession(int sessionId) { + private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException { synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); if (session == null || !isCallingUidOwner(session)) { @@ -665,40 +611,37 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IllegalStateException("Failed to allocate session ID"); } - private File prepareInternalStageDir(int sessionId) throws IOException { - final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); + private File buildInternalStageDir(int sessionId) { + return new File(mStagingDir, "vmdl" + sessionId + ".tmp"); + } - if (file.exists()) { - throw new IOException("Session dir already exists: " + file); + static void prepareInternalStageDir(File stageDir) throws IOException { + if (stageDir.exists()) { + throw new IOException("Session dir already exists: " + stageDir); } try { - Os.mkdir(file.getAbsolutePath(), 0755); - Os.chmod(file.getAbsolutePath(), 0755); + Os.mkdir(stageDir.getAbsolutePath(), 0755); + Os.chmod(stageDir.getAbsolutePath(), 0755); } catch (ErrnoException e) { // This purposefully throws if directory already exists - throw new IOException("Failed to prepare session dir", e); + throw new IOException("Failed to prepare session dir: " + stageDir, e); } - if (!SELinux.restorecon(file)) { - throw new IOException("Failed to restorecon session dir"); + if (!SELinux.restorecon(stageDir)) { + throw new IOException("Failed to restorecon session dir: " + stageDir); } - - return file; } - private String prepareExternalStageCid(int sessionId, long sizeBytes) throws IOException { - if (sizeBytes <= 0) { - throw new IOException("Session must provide valid size for ASEC"); - } + private String buildExternalStageCid(int sessionId) { + return "smdl" + sessionId + ".tmp"; + } - final String cid = "smdl" + sessionId + ".tmp"; - if (PackageHelper.createSdDir(sizeBytes, cid, PackageManagerService.getEncryptKey(), + static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException { + if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(), Process.SYSTEM_UID, true) == null) { - throw new IOException("Failed to create ASEC"); + throw new IOException("Failed to create session cid: " + stageCid); } - - return cid; } @Override @@ -1031,6 +974,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeSessionsAsync(); } + public void onSessionPrepared(PackageInstallerSession session) { + // We prepared the destination to write into; we want to persist + // this, but it's not critical enough to block for. + writeSessionsAsync(); + } + public void onSessionSealed(PackageInstallerSession session) { // It's very important that we block until we've recorded the // session as being sealed, since we never want to allow mutation diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f8273c0..adca46a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -17,15 +17,15 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; -import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; -import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_WRONLY; +import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid; +import static com.android.server.pm.PackageInstallerService.prepareInternalStageDir; import android.content.Context; import android.content.Intent; @@ -88,8 +88,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE - // TODO: treat INHERIT_EXISTING as installExistingPackage() - private final PackageInstallerService.InternalCallback mCallback; private final Context mContext; private final PackageManagerService mPm; @@ -108,18 +106,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** Note that UID is not persisted; it's always derived at runtime. */ final int installerUid; - private final AtomicInteger mOpenCount = new AtomicInteger(); + private final AtomicInteger mActiveCount = new AtomicInteger(); private final Object mLock = new Object(); @GuardedBy("mLock") private float mClientProgress = 0; @GuardedBy("mLock") + private float mInternalProgress = 0; + + @GuardedBy("mLock") private float mProgress = 0; @GuardedBy("mLock") private float mReportedProgress = -1; @GuardedBy("mLock") + private boolean mPrepared = false; + @GuardedBy("mLock") private boolean mSealed = false; @GuardedBy("mLock") private boolean mPermissionsAccepted = false; @@ -184,7 +187,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, String installerPackageName, SessionParams params, long createdMillis, - File stageDir, String stageCid, boolean sealed) { + File stageDir, String stageCid, boolean prepared, boolean sealed) { mCallback = callback; mContext = context; mPm = pm; @@ -203,6 +206,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Exactly one of stageDir or stageCid stage must be set"); } + mPrepared = prepared; mSealed = sealed; // Always derived at runtime @@ -214,8 +218,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } else { mPermissionsAccepted = false; } - - computeProgressLocked(); } public SessionInfo generateInfo() { @@ -227,7 +229,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mResolvedBaseFile.getAbsolutePath() : null; info.progress = mProgress; info.sealed = mSealed; - info.active = mOpenCount.get() > 0; + info.active = mActiveCount.get() > 0; info.mode = params.mode; info.sizeBytes = params.sizeBytes; @@ -238,14 +240,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return info; } + public boolean isPrepared() { + synchronized (mLock) { + return mPrepared; + } + } + public boolean isSealed() { synchronized (mLock) { return mSealed; } } - private void assertNotSealed(String cookie) { + private void assertPreparedAndNotSealed(String cookie) { synchronized (mLock) { + if (!mPrepared) { + throw new IllegalStateException(cookie + " before prepared"); + } if (mSealed) { throw new SecurityException(cookie + " not allowed after commit"); } @@ -278,30 +289,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void setClientProgress(float progress) { synchronized (mLock) { + // Always publish first staging movement + final boolean forcePublish = (mClientProgress == 0); mClientProgress = progress; - computeProgressLocked(); + computeProgressLocked(forcePublish); } - maybePublishProgress(); } @Override public void addClientProgress(float progress) { synchronized (mLock) { - mClientProgress += progress; - computeProgressLocked(); + setClientProgress(mClientProgress + progress); } - maybePublishProgress(); } - private void computeProgressLocked() { - if (mProgress <= 0.8f) { - mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f); - } - } + private void computeProgressLocked(boolean forcePublish) { + mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) + + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); - private void maybePublishProgress() { // Only publish when meaningful change - if (Math.abs(mProgress - mReportedProgress) > 0.01) { + if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) { mReportedProgress = mProgress; mCallback.onSessionProgressChanged(this, mProgress); } @@ -309,7 +316,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public String[] getNames() { - assertNotSealed("getNames"); + assertPreparedAndNotSealed("getNames"); try { return resolveStageDir().list(); } catch (IOException e) { @@ -333,7 +340,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // will block any attempted install transitions. final FileBridge bridge; synchronized (mLock) { - assertNotSealed("openWrite"); + assertPreparedAndNotSealed("openWrite"); bridge = new FileBridge(); mBridges.add(bridge); @@ -387,7 +394,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private ParcelFileDescriptor openReadInternal(String name) throws IOException { - assertNotSealed("openRead"); + assertPreparedAndNotSealed("openRead"); try { if (!FileUtils.isValidExtFilename(name)) { @@ -407,6 +414,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public void commit(IntentSender statusReceiver) { Preconditions.checkNotNull(statusReceiver); + synchronized (mLock) { + if (!mSealed) { + // Verify that all writers are hands-off + for (FileBridge bridge : mBridges) { + if (!bridge.isClosed()) { + throw new SecurityException("Files still open"); + } + } + + // Persist the fact that we've sealed ourselves to prevent + // mutations of any hard links we create. + mSealed = true; + mCallback.onSessionSealed(this); + } + } + + // Client staging is fully done at this point + mClientProgress = 1f; + computeProgressLocked(true); + + // This ongoing commit should keep session active, even though client + // will probably close their end. + mActiveCount.incrementAndGet(); + final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext, statusReceiver, sessionId); mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); @@ -414,22 +445,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void commitLocked() throws PackageManagerException { if (mDestroyed) { - throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session"); + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); } - - // Verify that all writers are hands-off if (!mSealed) { - for (FileBridge bridge : mBridges) { - if (!bridge.isClosed()) { - throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED, - "Files still open"); - } - } - mSealed = true; - - // Persist the fact that we've sealed ourselves to prevent mutations - // of any hard links we create below. - mCallback.onSessionSealed(this); + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); } try { @@ -458,6 +477,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mRemoteObserver.onUserActionRequired(intent); } catch (RemoteException ignored) { } + + // Commit was keeping session marked as active until now; release + // that extra refcount so session appears idle. + close(); return; } @@ -487,8 +510,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // TODO: surface more granular state from dexopt - mProgress = 0.9f; - maybePublishProgress(); + mInternalProgress = 0.5f; + computeProgressLocked(true); // Unpack native libraries extractNativeLibraries(mResolvedStageDir, params.abiOverride); @@ -831,15 +854,35 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - public void open() { - if (mOpenCount.getAndIncrement() == 0) { + public void open() throws IOException { + if (mActiveCount.getAndIncrement() == 0) { mCallback.onSessionActiveChanged(this, true); } + + synchronized (mLock) { + if (!mPrepared) { + if (stageDir != null) { + prepareInternalStageDir(stageDir); + } else if (stageCid != null) { + prepareExternalStageCid(stageCid, params.sizeBytes); + + // TODO: deliver more granular progress for ASEC allocation + mInternalProgress = 0.25f; + computeProgressLocked(true); + } else { + throw new IllegalArgumentException( + "Exactly one of stageDir or stageCid stage must be set"); + } + + mPrepared = true; + mCallback.onSessionPrepared(this); + } + } } @Override public void close() { - if (mOpenCount.decrementAndGet() == 0) { + if (mActiveCount.decrementAndGet() == 0) { mCallback.onSessionActiveChanged(this, false); } } @@ -869,6 +912,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { mSealed = true; mDestroyed = true; + + // Force shut down all bridges + for (FileBridge bridge : mBridges) { + bridge.forceClose(); + } } if (stageDir != null) { FileUtils.deleteContents(stageDir); diff --git a/telecomm/java/android/telecomm/AudioState.java b/telecomm/java/android/telecomm/AudioState.java index a5fda79..314704b 100644 --- a/telecomm/java/android/telecomm/AudioState.java +++ b/telecomm/java/android/telecomm/AudioState.java @@ -34,7 +34,7 @@ public final class AudioState implements Parcelable { /** Direct the audio stream through a wired headset. */ public static final int ROUTE_WIRED_HEADSET = 0x00000004; - /** Direct the audio stream through the device's spakerphone. */ + /** Direct the audio stream through the device's speakerphone. */ public static final int ROUTE_SPEAKER = 0x00000008; /** @@ -43,7 +43,10 @@ public final class AudioState implements Parcelable { */ public static final int ROUTE_WIRED_OR_EARPIECE = ROUTE_EARPIECE | ROUTE_WIRED_HEADSET; - /** Bit mask of all possible audio routes. */ + /** Bit mask of all possible audio routes. + * + * @hide + */ public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET | ROUTE_SPEAKER; diff --git a/telecomm/java/android/telecomm/Conference.java b/telecomm/java/android/telecomm/Conference.java index c838b48..f9c3ac3 100644 --- a/telecomm/java/android/telecomm/Conference.java +++ b/telecomm/java/android/telecomm/Conference.java @@ -54,7 +54,7 @@ public abstract class Conference { mPhoneAccount = phoneAccount; } - public final PhoneAccountHandle getPhoneAccount() { + public final PhoneAccountHandle getPhoneAccountHandle() { return mPhoneAccount; } @@ -183,7 +183,7 @@ public abstract class Conference { } /** - * Tears down the conference object and any of it's current connections. + * Tears down the conference object and any of its current connections. */ public final void destroy() { Log.d(this, "destroying conference : %s", this); diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java index 39ae59a..d5a6aa5 100644 --- a/telecomm/java/android/telecomm/ConnectionRequest.java +++ b/telecomm/java/android/telecomm/ConnectionRequest.java @@ -29,31 +29,25 @@ public final class ConnectionRequest implements Parcelable { // TODO: Token to limit recursive invocations private final PhoneAccountHandle mAccountHandle; - private final Uri mHandle; - private final int mHandlePresentation; + private final Uri mAddress; private final Bundle mExtras; private final int mVideoState; /** * @param accountHandle The accountHandle which should be used to place the call. * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect. - * @param handlePresentation The {@link PropertyPresentation} which controls how the handle - * is shown. * @param extras Application-specific extra data. */ public ConnectionRequest( PhoneAccountHandle accountHandle, Uri handle, - int handlePresentation, Bundle extras) { - this(accountHandle, handle, handlePresentation, extras, VideoProfile.VideoState.AUDIO_ONLY); + this(accountHandle, handle, extras, VideoProfile.VideoState.AUDIO_ONLY); } /** * @param accountHandle The accountHandle which should be used to place the call. * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect. - * @param handlePresentation The {@link PropertyPresentation} which controls how the handle - * is shown. * @param extras Application-specific extra data. * @param videoState Determines the video state for the connection. * @hide @@ -61,20 +55,17 @@ public final class ConnectionRequest implements Parcelable { public ConnectionRequest( PhoneAccountHandle accountHandle, Uri handle, - int handlePresentation, Bundle extras, int videoState) { mAccountHandle = accountHandle; - mHandle = handle; - mHandlePresentation = handlePresentation; + mAddress = handle; mExtras = extras; mVideoState = videoState; } private ConnectionRequest(Parcel in) { mAccountHandle = in.readParcelable(getClass().getClassLoader()); - mHandle = in.readParcelable(getClass().getClassLoader()); - mHandlePresentation = in.readInt(); + mAddress = in.readParcelable(getClass().getClassLoader()); mExtras = in.readParcelable(getClass().getClassLoader()); mVideoState = in.readInt(); } @@ -87,12 +78,7 @@ public final class ConnectionRequest implements Parcelable { /** * The handle (e.g., phone number) to which the {@link Connection} is to connect. */ - public Uri getHandle() { return mHandle; } - - /** - * The {@link PropertyPresentation} which controls how the handle is shown. - */ - public int getHandlePresentation() { return mHandlePresentation; } + public Uri getAddress() { return mAddress; } /** * Application-specific extra data. Used for passing back information from an incoming @@ -118,9 +104,9 @@ public final class ConnectionRequest implements Parcelable { @Override public String toString() { return String.format("ConnectionRequest %s %s", - mHandle == null + mAddress == null ? Uri.EMPTY - : Connection.toLogSafePhoneNumber(mHandle.toString()), + : Connection.toLogSafePhoneNumber(mAddress.toString()), mExtras == null ? "" : mExtras); } @@ -147,8 +133,7 @@ public final class ConnectionRequest implements Parcelable { @Override public void writeToParcel(Parcel destination, int flags) { destination.writeParcelable(mAccountHandle, 0); - destination.writeParcelable(mHandle, 0); - destination.writeInt(mHandlePresentation); + destination.writeParcelable(mAddress, 0); destination.writeParcelable(mExtras, 0); destination.writeInt(mVideoState); } diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index 833aa26..39365b6 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -781,7 +781,7 @@ public abstract class ConnectionService extends Service { } } ParcelableConference parcelableConference = new ParcelableConference( - conference.getPhoneAccount(), + conference.getPhoneAccountHandle(), conference.getState(), conference.getCapabilities(), connectionIds); diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java index 79193c2..8ad0ad0 100644 --- a/telecomm/java/android/telecomm/RemoteConnectionService.java +++ b/telecomm/java/android/telecomm/RemoteConnectionService.java @@ -325,8 +325,7 @@ final class RemoteConnectionService { final String id = UUID.randomUUID().toString(); final ConnectionRequest newRequest = new ConnectionRequest( request.getAccountHandle(), - request.getHandle(), - request.getHandlePresentation(), + request.getAddress(), request.getExtras(), request.getVideoState()); try { diff --git a/telecomm/java/android/telecomm/StatusHints.java b/telecomm/java/android/telecomm/StatusHints.java index f7c4f2f..ff96a5b 100644 --- a/telecomm/java/android/telecomm/StatusHints.java +++ b/telecomm/java/android/telecomm/StatusHints.java @@ -32,23 +32,24 @@ import java.util.Objects; */ public final class StatusHints implements Parcelable { - private final ComponentName mComponentName; + private final ComponentName mPackageName; private final CharSequence mLabel; private final int mIconResId; private final Bundle mExtras; - public StatusHints(ComponentName componentName, CharSequence label, int iconResId, Bundle extras) { - mComponentName = componentName; + public StatusHints(ComponentName packageName, CharSequence label, int iconResId, + Bundle extras) { + mPackageName = packageName; mLabel = label; mIconResId = iconResId; mExtras = extras; } /** - * @return A component used to load the icon. + * @return A package used to load the icon. */ - public ComponentName getComponentName() { - return mComponentName; + public ComponentName getPackageName() { + return mPackageName; } /** @@ -88,7 +89,7 @@ public final class StatusHints implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mComponentName, flags); + out.writeParcelable(mPackageName, flags); out.writeCharSequence(mLabel); out.writeInt(mIconResId); out.writeParcelable(mExtras, 0); @@ -106,7 +107,7 @@ public final class StatusHints implements Parcelable { }; private StatusHints(Parcel in) { - mComponentName = in.readParcelable(getClass().getClassLoader()); + mPackageName = in.readParcelable(getClass().getClassLoader()); mLabel = in.readCharSequence(); mIconResId = in.readInt(); mExtras = in.readParcelable(getClass().getClassLoader()); @@ -115,16 +116,16 @@ public final class StatusHints implements Parcelable { private Drawable getIcon(Context context, int resId) { Context packageContext; try { - packageContext = context.createPackageContext(mComponentName.getPackageName(), 0); + packageContext = context.createPackageContext(mPackageName.getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { - Log.e(this, e, "Cannot find package %s", mComponentName.getPackageName()); + Log.e(this, e, "Cannot find package %s", mPackageName.getPackageName()); return null; } try { return packageContext.getDrawable(resId); } catch (MissingResourceException e) { Log.e(this, e, "Cannot find icon %d in package %s", - resId, mComponentName.getPackageName()); + resId, mPackageName.getPackageName()); return null; } } @@ -133,7 +134,7 @@ public final class StatusHints implements Parcelable { public boolean equals(Object other) { if (other != null && other instanceof StatusHints) { StatusHints otherHints = (StatusHints) other; - return Objects.equals(otherHints.getComponentName(), getComponentName()) && + return Objects.equals(otherHints.getPackageName(), getPackageName()) && Objects.equals(otherHints.getLabel(), getLabel()) && otherHints.getIconResId() == getIconResId() && Objects.equals(otherHints.getExtras(), getExtras()); @@ -143,7 +144,7 @@ public final class StatusHints implements Parcelable { @Override public int hashCode() { - return Objects.hashCode(mComponentName) + Objects.hashCode(mLabel) + mIconResId + + return Objects.hashCode(mPackageName) + Objects.hashCode(mLabel) + mIconResId + Objects.hashCode(mExtras); } } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index ac2a176..21f200f 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -341,6 +341,12 @@ public class WifiConfiguration implements Parcelable { /** * @hide + * last time we connected, this configuration had no internet access + */ + public boolean noInternetAccess; + + /** + * @hide * Uid of app creating the configuration */ @SystemApi @@ -673,6 +679,49 @@ public class WifiConfiguration implements Parcelable { @SystemApi public int numAssociation; + /** + * @hide + * Number of time user disabled WiFi while associated to this configuration with Low RSSI. + */ + public int numUserTriggeredWifiDisableLowRSSI; + + /** + * @hide + * Number of time user disabled WiFi while associated to this configuration with Bad RSSI. + */ + public int numUserTriggeredWifiDisableBadRSSI; + + /** + * @hide + * Number of time user disabled WiFi while associated to this configuration + * and RSSI was not HIGH. + */ + public int numUserTriggeredWifiDisableNotHighRSSI; + + /** + * @hide + * Number of ticks associated to this configuration with Low RSSI. + */ + public int numTicksAtLowRSSI; + + /** + * @hide + * Number of ticks associated to this configuration with Bad RSSI. + */ + public int numTicksAtBadRSSI; + + /** + * @hide + * Number of ticks associated to this configuration + * and RSSI was not HIGH. + */ + public int numTicksAtNotHighRSSI; + /** + * @hide + * Number of time user (WifiManager) triggered association to this configuration. + * TODO: count this only for Wifi Settings uuid, so as to not count 3rd party apps + */ + public int numUserTriggeredJoinAttempts; /** * @hide @@ -725,6 +774,7 @@ public class WifiConfiguration implements Parcelable { selfAdded = false; didSelfAdd = false; ephemeral = false; + noInternetAccess = false; mIpConfiguration = new IpConfiguration(); } @@ -824,8 +874,10 @@ public class WifiConfiguration implements Parcelable { sbuf.append(" autoJoinStatus ").append(this.numConnectionFailures).append("\n"); } if (this.didSelfAdd || this.selfAdded) { - if (this.didSelfAdd) sbuf.append(" didSelfAdd "); - if (this.selfAdded) sbuf.append(" selfAdded "); + if (this.didSelfAdd) sbuf.append(" didSelfAdd"); + if (this.selfAdded) sbuf.append(" selfAdded"); + if (this.noInternetAccess) sbuf.append(" noInternetAccess"); + sbuf.append("\n"); } sbuf.append(" KeyMgmt:"); @@ -896,18 +948,44 @@ public class WifiConfiguration implements Parcelable { sbuf.append(mIpConfiguration.toString()); - if (selfAdded) sbuf.append("selfAdded"); - if (creatorUid != 0) sbuf.append("uid=" + Integer.toString(creatorUid)); + if (this.creatorUid != 0) sbuf.append("uid=" + Integer.toString(creatorUid)); - if (blackListTimestamp != 0) { + if (this.blackListTimestamp != 0) { long now_ms = System.currentTimeMillis(); - long diff = now_ms - blackListTimestamp; + long diff = now_ms - this.blackListTimestamp; if (diff <= 0) { sbuf.append("blackListed since <incorrect>"); } else { sbuf.append("blackListed since ").append(Long.toString(diff/1000)).append( "sec"); } } + sbuf.append('\n'); + if (this.linkedConfigurations != null) { + for(String key : this.linkedConfigurations.keySet()) { + sbuf.append(" linked: ").append(key); + sbuf.append('\n'); + } + } + if (this.connectChoices != null) { + for(String key : this.connectChoices.keySet()) { + Integer choice = this.connectChoices.get(key); + if (choice != null) { + sbuf.append(" choice: ").append(key); + sbuf.append(" = ").append(choice); + sbuf.append('\n'); + } + } + } + sbuf.append(" triggeredLow: ").append(numUserTriggeredWifiDisableLowRSSI); + sbuf.append(" triggeredBad: ").append(numUserTriggeredWifiDisableBadRSSI); + sbuf.append(" triggeredNotHigh: ").append(numUserTriggeredWifiDisableNotHighRSSI); + sbuf.append('\n'); + sbuf.append(" ticksLow: ").append(numTicksAtLowRSSI); + sbuf.append(" ticksBad: ").append(numTicksAtBadRSSI); + sbuf.append(" ticksNotHigh: ").append(numTicksAtNotHighRSSI); + sbuf.append('\n'); + sbuf.append(" triggeredJoin: ").append(numUserTriggeredJoinAttempts); + sbuf.append('\n'); return sbuf.toString(); } @@ -1197,7 +1275,7 @@ public class WifiConfiguration implements Parcelable { mCachedConfigKey = null; //force null configKey autoJoinStatus = source.autoJoinStatus; selfAdded = source.selfAdded; - + noInternetAccess = source.noInternetAccess; if (source.visibility != null) { visibility = new Visibility(source.visibility); } @@ -1217,6 +1295,13 @@ public class WifiConfiguration implements Parcelable { numScorerOverride = source.numScorerOverride; numScorerOverrideAndSwitchedNetwork = source.numScorerOverrideAndSwitchedNetwork; numAssociation = source.numAssociation; + numUserTriggeredWifiDisableLowRSSI = source.numUserTriggeredWifiDisableLowRSSI; + numUserTriggeredWifiDisableBadRSSI = source.numUserTriggeredWifiDisableBadRSSI; + numUserTriggeredWifiDisableNotHighRSSI = source.numUserTriggeredWifiDisableNotHighRSSI; + numTicksAtLowRSSI = source.numTicksAtLowRSSI; + numTicksAtBadRSSI = source.numTicksAtBadRSSI; + numTicksAtNotHighRSSI = source.numTicksAtNotHighRSSI; + numUserTriggeredJoinAttempts = source.numUserTriggeredJoinAttempts; } } @@ -1259,6 +1344,7 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(autoJoinStatus); dest.writeInt(selfAdded ? 1 : 0); dest.writeInt(didSelfAdd ? 1 : 0); + dest.writeInt(noInternetAccess ? 1 : 0); dest.writeInt(creatorUid); dest.writeInt(lastConnectUid); dest.writeInt(lastUpdateUid); @@ -1269,6 +1355,14 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(numScorerOverride); dest.writeInt(numScorerOverrideAndSwitchedNetwork); dest.writeInt(numAssociation); + dest.writeInt(numUserTriggeredWifiDisableLowRSSI); + dest.writeInt(numUserTriggeredWifiDisableBadRSSI); + dest.writeInt(numUserTriggeredWifiDisableNotHighRSSI); + dest.writeInt(numTicksAtLowRSSI); + dest.writeInt(numTicksAtBadRSSI); + dest.writeInt(numTicksAtNotHighRSSI); + dest.writeInt(numUserTriggeredJoinAttempts); + } /** Implement the Parcelable interface {@hide} */ @@ -1307,6 +1401,7 @@ public class WifiConfiguration implements Parcelable { config.autoJoinStatus = in.readInt(); config.selfAdded = in.readInt() != 0; config.didSelfAdd = in.readInt() != 0; + config.noInternetAccess = in.readInt() != 0; config.creatorUid = in.readInt(); config.lastConnectUid = in.readInt(); config.lastUpdateUid = in.readInt(); @@ -1317,6 +1412,13 @@ public class WifiConfiguration implements Parcelable { config.numScorerOverride = in.readInt(); config.numScorerOverrideAndSwitchedNetwork = in.readInt(); config.numAssociation = in.readInt(); + config.numUserTriggeredWifiDisableLowRSSI = in.readInt(); + config.numUserTriggeredWifiDisableBadRSSI = in.readInt(); + config.numUserTriggeredWifiDisableNotHighRSSI = in.readInt(); + config.numTicksAtLowRSSI = in.readInt(); + config.numTicksAtBadRSSI = in.readInt(); + config.numTicksAtNotHighRSSI = in.readInt(); + config.numUserTriggeredJoinAttempts = in.readInt(); return config; } |