diff options
82 files changed, 2995 insertions, 890 deletions
diff --git a/api/current.txt b/api/current.txt index dd22a402d362..4bd0fc0574c1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -728,6 +728,7 @@ package android { field public static final int label = 16842753; // 0x1010001 field public static final int labelFor = 16843718; // 0x10103c6 field public static final int labelTextSize = 16843317; // 0x1010235 + field public static final int languageTag = 16844041; // 0x1010509 field public static final int largeHeap = 16843610; // 0x101035a field public static final int largeScreens = 16843398; // 0x1010286 field public static final int largestWidthLimitDp = 16843622; // 0x1010366 @@ -20733,6 +20734,7 @@ package android.media { public static class MediaRouter.RouteInfo { method public android.media.MediaRouter.RouteCategory getCategory(); method public java.lang.CharSequence getDescription(); + method public int getDeviceType(); method public android.media.MediaRouter.RouteGroup getGroup(); method public android.graphics.drawable.Drawable getIconDrawable(); method public java.lang.CharSequence getName(); @@ -20751,6 +20753,10 @@ package android.media { method public void requestSetVolume(int); method public void requestUpdateVolume(int); method public void setTag(java.lang.Object); + field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3 + field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2 + field public static final int DEVICE_TYPE_TV = 1; // 0x1 + field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0 field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0 field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1 field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 @@ -41832,6 +41838,7 @@ package android.view { method public abstract void setContentView(int); method public abstract void setContentView(android.view.View); method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); + method public abstract void setDecorCaptionShade(int); method protected void setDefaultWindowFormat(int); method public void setDimAmount(float); method public void setElevation(float); @@ -41852,6 +41859,8 @@ package android.view { method public void setMediaController(android.media.session.MediaController); method public abstract void setNavigationBarColor(int); method public void setReenterTransition(android.transition.Transition); + method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable); + method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener); method public void setReturnTransition(android.transition.Transition); method public void setSharedElementEnterTransition(android.transition.Transition); method public void setSharedElementExitTransition(android.transition.Transition); @@ -41880,6 +41889,9 @@ package android.view { method public abstract void takeKeyEvents(boolean); method public abstract void takeSurface(android.view.SurfaceHolder.Callback2); method public abstract void togglePanel(int, android.view.KeyEvent); + field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0 + field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2 + field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1 field protected static final deprecated int DEFAULT_FEATURES = 65; // 0x41 field public static final int FEATURE_ACTION_BAR = 8; // 0x8 field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9 @@ -41934,6 +41946,10 @@ package android.view { method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int); } + public static abstract interface Window.RestrictedCaptionAreaListener { + method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect); + } + public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -43198,7 +43214,8 @@ package android.view.inputmethod { method public java.lang.String getExtraValue(); method public java.lang.String getExtraValueOf(java.lang.String); method public int getIconResId(); - method public java.lang.String getLocale(); + method public java.lang.String getLanguageTag(); + method public deprecated java.lang.String getLocale(); method public java.lang.String getMode(); method public int getNameResId(); method public boolean isAsciiCapable(); @@ -43213,6 +43230,7 @@ package android.view.inputmethod { method public android.view.inputmethod.InputMethodSubtype build(); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int); @@ -43276,7 +43294,8 @@ package android.view.textservice { method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo); method public java.lang.String getExtraValue(); method public java.lang.String getExtraValueOf(java.lang.String); - method public java.lang.String getLocale(); + method public java.lang.String getLanguageTag(); + method public deprecated java.lang.String getLocale(); method public int getNameResId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR; diff --git a/api/system-current.txt b/api/system-current.txt index ee20e5bca9fb..18dd49fe1aaf 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -822,6 +822,7 @@ package android { field public static final int label = 16842753; // 0x1010001 field public static final int labelFor = 16843718; // 0x10103c6 field public static final int labelTextSize = 16843317; // 0x1010235 + field public static final int languageTag = 16844041; // 0x1010509 field public static final int largeHeap = 16843610; // 0x101035a field public static final int largeScreens = 16843398; // 0x1010286 field public static final int largestWidthLimitDp = 16843622; // 0x1010366 @@ -22026,6 +22027,7 @@ package android.media { public static class MediaRouter.RouteInfo { method public android.media.MediaRouter.RouteCategory getCategory(); method public java.lang.CharSequence getDescription(); + method public int getDeviceType(); method public android.media.MediaRouter.RouteGroup getGroup(); method public android.graphics.drawable.Drawable getIconDrawable(); method public java.lang.CharSequence getName(); @@ -22044,6 +22046,10 @@ package android.media { method public void requestSetVolume(int); method public void requestUpdateVolume(int); method public void setTag(java.lang.Object); + field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3 + field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2 + field public static final int DEVICE_TYPE_TV = 1; // 0x1 + field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0 field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0 field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1 field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 @@ -44170,6 +44176,7 @@ package android.view { method public abstract void setContentView(int); method public abstract void setContentView(android.view.View); method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); + method public abstract void setDecorCaptionShade(int); method protected void setDefaultWindowFormat(int); method public void setDimAmount(float); method public void setDisableWallpaperTouchEvents(boolean); @@ -44191,6 +44198,8 @@ package android.view { method public void setMediaController(android.media.session.MediaController); method public abstract void setNavigationBarColor(int); method public void setReenterTransition(android.transition.Transition); + method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable); + method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener); method public void setReturnTransition(android.transition.Transition); method public void setSharedElementEnterTransition(android.transition.Transition); method public void setSharedElementExitTransition(android.transition.Transition); @@ -44219,6 +44228,9 @@ package android.view { method public abstract void takeKeyEvents(boolean); method public abstract void takeSurface(android.view.SurfaceHolder.Callback2); method public abstract void togglePanel(int, android.view.KeyEvent); + field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0 + field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2 + field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1 field protected static final deprecated int DEFAULT_FEATURES = 65; // 0x41 field public static final int FEATURE_ACTION_BAR = 8; // 0x8 field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9 @@ -44273,6 +44285,10 @@ package android.view { method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int); } + public static abstract interface Window.RestrictedCaptionAreaListener { + method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect); + } + public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -45539,7 +45555,8 @@ package android.view.inputmethod { method public java.lang.String getExtraValue(); method public java.lang.String getExtraValueOf(java.lang.String); method public int getIconResId(); - method public java.lang.String getLocale(); + method public java.lang.String getLanguageTag(); + method public deprecated java.lang.String getLocale(); method public java.lang.String getMode(); method public int getNameResId(); method public boolean isAsciiCapable(); @@ -45554,6 +45571,7 @@ package android.view.inputmethod { method public android.view.inputmethod.InputMethodSubtype build(); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int); @@ -45617,7 +45635,8 @@ package android.view.textservice { method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo); method public java.lang.String getExtraValue(); method public java.lang.String getExtraValueOf(java.lang.String); - method public java.lang.String getLocale(); + method public java.lang.String getLanguageTag(); + method public deprecated java.lang.String getLocale(); method public int getNameResId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR; diff --git a/api/test-current.txt b/api/test-current.txt index 032507b122ac..205b06df2027 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -728,6 +728,7 @@ package android { field public static final int label = 16842753; // 0x1010001 field public static final int labelFor = 16843718; // 0x10103c6 field public static final int labelTextSize = 16843317; // 0x1010235 + field public static final int languageTag = 16844041; // 0x1010509 field public static final int largeHeap = 16843610; // 0x101035a field public static final int largeScreens = 16843398; // 0x1010286 field public static final int largestWidthLimitDp = 16843622; // 0x1010366 @@ -20733,6 +20734,7 @@ package android.media { public static class MediaRouter.RouteInfo { method public android.media.MediaRouter.RouteCategory getCategory(); method public java.lang.CharSequence getDescription(); + method public int getDeviceType(); method public android.media.MediaRouter.RouteGroup getGroup(); method public android.graphics.drawable.Drawable getIconDrawable(); method public java.lang.CharSequence getName(); @@ -20751,6 +20753,10 @@ package android.media { method public void requestSetVolume(int); method public void requestUpdateVolume(int); method public void setTag(java.lang.Object); + field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3 + field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2 + field public static final int DEVICE_TYPE_TV = 1; // 0x1 + field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0 field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0 field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1 field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 @@ -41834,6 +41840,7 @@ package android.view { method public abstract void setContentView(int); method public abstract void setContentView(android.view.View); method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams); + method public abstract void setDecorCaptionShade(int); method protected void setDefaultWindowFormat(int); method public void setDimAmount(float); method public void setElevation(float); @@ -41854,6 +41861,8 @@ package android.view { method public void setMediaController(android.media.session.MediaController); method public abstract void setNavigationBarColor(int); method public void setReenterTransition(android.transition.Transition); + method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable); + method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener); method public void setReturnTransition(android.transition.Transition); method public void setSharedElementEnterTransition(android.transition.Transition); method public void setSharedElementExitTransition(android.transition.Transition); @@ -41882,6 +41891,9 @@ package android.view { method public abstract void takeKeyEvents(boolean); method public abstract void takeSurface(android.view.SurfaceHolder.Callback2); method public abstract void togglePanel(int, android.view.KeyEvent); + field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0 + field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2 + field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1 field protected static final deprecated int DEFAULT_FEATURES = 65; // 0x41 field public static final int FEATURE_ACTION_BAR = 8; // 0x8 field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9 @@ -41936,6 +41948,10 @@ package android.view { method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int); } + public static abstract interface Window.RestrictedCaptionAreaListener { + method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect); + } + public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -43200,7 +43216,8 @@ package android.view.inputmethod { method public java.lang.String getExtraValue(); method public java.lang.String getExtraValueOf(java.lang.String); method public int getIconResId(); - method public java.lang.String getLocale(); + method public java.lang.String getLanguageTag(); + method public deprecated java.lang.String getLocale(); method public java.lang.String getMode(); method public int getNameResId(); method public boolean isAsciiCapable(); @@ -43215,6 +43232,7 @@ package android.view.inputmethod { method public android.view.inputmethod.InputMethodSubtype build(); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean); + method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int); @@ -43278,7 +43296,8 @@ package android.view.textservice { method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo); method public java.lang.String getExtraValue(); method public java.lang.String getExtraValueOf(java.lang.String); - method public java.lang.String getLocale(); + method public java.lang.String getLanguageTag(); + method public deprecated java.lang.String getLocale(); method public int getNameResId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR; diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 1f378da4afd3..175b9799c5db 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.SystemApi; +import android.os.Build; import android.os.Bundle; /** @@ -28,15 +29,28 @@ import android.os.Bundle; @SystemApi public class BroadcastOptions { private long mTemporaryAppWhitelistDuration; + private int mMinManifestReceiverApiLevel = 0; + private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; /** * How long to temporarily put an app on the power whitelist when executing this broadcast * to it. - * @hide */ - public static final String KEY_TEMPORARY_APP_WHITELIST_DURATION + static final String KEY_TEMPORARY_APP_WHITELIST_DURATION = "android:broadcast.temporaryAppWhitelistDuration"; + /** + * Corresponds to {@link #setMinManifestReceiverApiLevel}. + */ + static final String KEY_MIN_MANIFEST_RECEIVER_API_LEVEL + = "android:broadcast.minManifestReceiverApiLevel"; + + /** + * Corresponds to {@link #setMaxManifestReceiverApiLevel}. + */ + static final String KEY_MAX_MANIFEST_RECEIVER_API_LEVEL + = "android:broadcast.maxManifestReceiverApiLevel"; + public static BroadcastOptions makeBasic() { BroadcastOptions opts = new BroadcastOptions(); return opts; @@ -48,6 +62,9 @@ public class BroadcastOptions { /** @hide */ public BroadcastOptions(Bundle opts) { mTemporaryAppWhitelistDuration = opts.getLong(KEY_TEMPORARY_APP_WHITELIST_DURATION); + mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); + mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, + Build.VERSION_CODES.CUR_DEVELOPMENT); } /** @@ -68,10 +85,46 @@ public class BroadcastOptions { } /** + * Set the minimum target API level of receivers of the broadcast. If an application + * is targeting an API level less than this, the broadcast will not be delivered to + * them. This only applies to receivers declared in the app's AndroidManifest.xml. + * @hide + */ + public void setMinManifestReceiverApiLevel(int apiLevel) { + mMinManifestReceiverApiLevel = apiLevel; + } + + /** + * Return {@link #setMinManifestReceiverApiLevel}. + * @hide + */ + public int getMinManifestReceiverApiLevel() { + return mMinManifestReceiverApiLevel; + } + + /** + * Set the maximum target API level of receivers of the broadcast. If an application + * is targeting an API level greater than this, the broadcast will not be delivered to + * them. This only applies to receivers declared in the app's AndroidManifest.xml. + * @hide + */ + public void setMaxManifestReceiverApiLevel(int apiLevel) { + mMaxManifestReceiverApiLevel = apiLevel; + } + + /** + * Return {@link #setMaxManifestReceiverApiLevel}. + * @hide + */ + public int getMaxManifestReceiverApiLevel() { + return mMaxManifestReceiverApiLevel; + } + + /** * Returns the created options as a Bundle, which can be passed to * {@link android.content.Context#sendBroadcast(android.content.Intent) * Context.sendBroadcast(Intent)} and related methods. - * Note that the returned Bundle is still owned by the ActivityOptions + * Note that the returned Bundle is still owned by the BroadcastOptions * object; you must not modify it, but can supply it to the sendBroadcast * methods that take an options Bundle. */ @@ -80,6 +133,12 @@ public class BroadcastOptions { if (mTemporaryAppWhitelistDuration > 0) { b.putLong(KEY_TEMPORARY_APP_WHITELIST_DURATION, mTemporaryAppWhitelistDuration); } + if (mMinManifestReceiverApiLevel != 0) { + b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel); + } + if (mMaxManifestReceiverApiLevel != Build.VERSION_CODES.CUR_DEVELOPMENT) { + b.putInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, mMaxManifestReceiverApiLevel); + } return b.isEmpty() ? null : b; } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index d90ed9f6e639..569ab1116b5b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1093,7 +1093,23 @@ class ContextImpl extends Context { intent.prepareToLeaveProcess(); ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, - Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true, user.getIdentifier()); + Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true, + user.getIdentifier()); + } catch (RemoteException e) { + throw new RuntimeException("Failure from system", e); + } + } + + @Override + @Deprecated + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.prepareToLeaveProcess(); + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options, false, true, + user.getIdentifier()); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 099a5fe9352e..e7dd5ff89517 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -46,6 +46,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.SparseArray; import android.util.TypedValue; import android.view.Gravity; import android.view.NotificationHeaderView; @@ -64,6 +65,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * A class that represents how a persistent notification is to be presented to @@ -1693,11 +1695,21 @@ public class Notification implements Parcelable bigContentView = null; headsUpContentView = null; mLargeIcon = null; - if (extras != null) { - extras.remove(Notification.EXTRA_LARGE_ICON); - extras.remove(Notification.EXTRA_LARGE_ICON_BIG); - extras.remove(Notification.EXTRA_PICTURE); - extras.remove(Notification.EXTRA_BIG_TEXT); + if (extras != null && !extras.isEmpty()) { + final Set<String> keyset = extras.keySet(); + final int N = keyset.size(); + final String[] keys = keyset.toArray(new String[N]); + for (int i=0; i<N; i++) { + final String key = keys[i]; + final Object obj = extras.get(key); + if (obj != null && + ( obj instanceof Parcelable + || obj instanceof Parcelable[] + || obj instanceof SparseArray + || obj instanceof ArrayList)) { + extras.remove(key); + } + } } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index c61f20444813..38a4475064af 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2116,6 +2116,14 @@ public abstract class Context { UserHandle user); /** + * @hide + * This is just here for sending CONNECTIVITY_ACTION. + */ + @Deprecated + public abstract void sendStickyBroadcastAsUser(@RequiresPermission Intent intent, + UserHandle user, Bundle options); + + /** * <p>Version of * {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)} * that allows you to specify the diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index e49e771d4124..1a3d262f6b56 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -536,6 +536,13 @@ public class ContextWrapper extends Context { mBase.sendStickyBroadcastAsUser(intent, user); } + /** @hide */ + @Override + @Deprecated + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + mBase.sendStickyBroadcastAsUser(intent, user, options); + } + @Override @Deprecated public void sendStickyOrderedBroadcastAsUser(Intent intent, diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 381ca4c3a36e..74fd8cd10de3 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3323,6 +3323,7 @@ public final class Settings { PUBLIC_SETTINGS.add(SOUND_EFFECTS_ENABLED); PUBLIC_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED); PUBLIC_SETTINGS.add(SHOW_WEB_SUGGESTIONS); + PUBLIC_SETTINGS.add(VIBRATE_WHEN_RINGING); } /** @@ -3344,7 +3345,6 @@ public final class Settings { PRIVATE_SETTINGS.add(VIBRATE_IN_SILENT); PRIVATE_SETTINGS.add(MEDIA_BUTTON_RECEIVER); PRIVATE_SETTINGS.add(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY); - PRIVATE_SETTINGS.add(VIBRATE_WHEN_RINGING); PRIVATE_SETTINGS.add(DTMF_TONE_TYPE_WHEN_DIALING); PRIVATE_SETTINGS.add(HEARING_AID); PRIVATE_SETTINGS.add(TTY_MODE); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 2459cfa84636..57fe1315ab5d 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -20,7 +20,6 @@ import android.graphics.Rect; import android.text.Layout; import android.text.Selection; import android.text.Spannable; -import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -222,34 +221,26 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme return lineEnd(widget, buffer); } - private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) { - return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer); - } - @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { int initialScrollX = -1; int initialScrollY = -1; final int action = event.getAction(); - final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); if (action == MotionEvent.ACTION_UP) { initialScrollX = Touch.getInitialScrollX(widget, buffer); initialScrollY = Touch.getInitialScrollY(widget, buffer); } - boolean wasTouchSelecting = isTouchSelecting(isMouse, buffer); + boolean wasTouchSelecting = isSelecting(buffer); boolean handled = Touch.onTouchEvent(widget, buffer, event); - if (widget.didTouchFocusSelect() && !isMouse) { + if (widget.didTouchFocusSelect()) { return handled; } if (action == MotionEvent.ACTION_DOWN) { - // Capture the mouse pointer down location to ensure selection starts - // right under the mouse (and is not influenced by cursor location). - // The code below needs to run for mouse events. // For touch events, the code should run only when selection is active. - if (isMouse || isTouchSelecting(isMouse, buffer)) { + if (isSelecting(buffer)) { if (!widget.isFocused()) { if (!widget.requestFocus()) { return handled; @@ -265,15 +256,8 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme } } else if (widget.isFocused()) { if (action == MotionEvent.ACTION_MOVE) { - // Cursor can be active at any location in the text while mouse pointer can start - // selection from a totally different location. Use LAST_TAP_DOWN span to ensure - // text selection will start from mouse pointer location. - final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN); - if (isMouse && Touch.isSelectionStarted(buffer)) { - Selection.setSelection(buffer, startOffset); - } - - if (isTouchSelecting(isMouse, buffer) && handled) { + if (isSelecting(buffer) && handled) { + final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN); // Before selecting, make sure we've moved out of the "slop". // handled will be true, if we're in select mode AND we're // OUT of the slop diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index fee737732b96..d9068dc74a2c 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -119,18 +119,12 @@ public class Touch { ds = buffer.getSpans(0, buffer.length(), DragState.class); if (ds.length > 0) { - ds[0].mIsSelectionStarted = false; - if (ds[0].mFarEnough == false) { int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop(); if (Math.abs(event.getX() - ds[0].mX) >= slop || Math.abs(event.getY() - ds[0].mY) >= slop) { ds[0].mFarEnough = true; - if (event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { - ds[0].mIsActivelySelecting = true; - ds[0].mIsSelectionStarted = true; - } } } @@ -142,13 +136,9 @@ public class Touch { || MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0; - if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { - ds[0].mIsActivelySelecting = false; - } - float dx; float dy; - if (cap && event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { + if (cap) { // if we're selecting, we want the scroll to go in // the direction of the drag dx = event.getX() - ds[0].mX; @@ -160,7 +150,6 @@ public class Touch { ds[0].mX = event.getX(); ds[0].mY = event.getY(); - int nx = widget.getScrollX() + (int) dx; int ny = widget.getScrollY() + (int) dy; int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); @@ -172,10 +161,6 @@ public class Touch { int oldX = widget.getScrollX(); int oldY = widget.getScrollY(); - if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { - scrollTo(widget, layout, nx, ny); - } - // If we actually scrolled, then cancel the up action. if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) { widget.cancelLongPress(); @@ -207,37 +192,6 @@ public class Touch { return ds.length > 0 ? ds[0].mScrollY : -1; } - /** - * Checks if selection is still active. - * This is useful for extending Selection span on buffer. - * @param buffer The text buffer. - * @return true if buffer has been marked for selection. - * - * @hide - */ - static boolean isActivelySelecting(Spannable buffer) { - DragState[] ds; - ds = buffer.getSpans(0, buffer.length(), DragState.class); - - return ds.length > 0 && ds[0].mIsActivelySelecting; - } - - /** - * Checks if selection has begun (are we out of slop?). - * Note: DragState.mIsSelectionStarted goes back to false with the very next event. - * This is useful for starting Selection span on buffer. - * @param buffer The text buffer. - * @return true if selection has started on the buffer. - * - * @hide - */ - static boolean isSelectionStarted(Spannable buffer) { - DragState[] ds; - ds = buffer.getSpans(0, buffer.length(), DragState.class); - - return ds.length > 0 && ds[0].mIsSelectionStarted; - } - private static class DragState implements NoCopySpan { public float mX; public float mY; @@ -245,8 +199,6 @@ public class Touch { public int mScrollY; public boolean mFarEnough; public boolean mUsed; - public boolean mIsActivelySelecting; - public boolean mIsSelectionStarted; public DragState(float x, float y, int scrollX, int scrollY) { mX = x; diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 7a359e79d4c5..53490b4e15e3 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -29,6 +29,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.media.session.MediaController; import android.net.Uri; @@ -247,12 +248,30 @@ public abstract class Window { private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw"; + /** + * Flag for letting the theme drive the color of the window caption controls. Use with + * {@link #setDecorCaptionShade(int)}. This is the default value. + */ + public static final int DECOR_CAPTION_SHADE_AUTO = 0; + /** + * Flag for setting light-color controls on the window caption. Use with + * {@link #setDecorCaptionShade(int)}. + */ + public static final int DECOR_CAPTION_SHADE_LIGHT = 1; + /** + * Flag for setting dark-color controls on the window caption. Use with + * {@link #setDecorCaptionShade(int)}. + */ + public static final int DECOR_CAPTION_SHADE_DARK = 2; + private final Context mContext; private TypedArray mWindowStyle; private Callback mCallback; private OnWindowDismissedCallback mOnWindowDismissedCallback; private WindowControllerCallback mWindowControllerCallback; + private RestrictedCaptionAreaListener mRestrictedCaptionAreaListener; + private Rect mRestrictedCaptionAreaRect; private WindowManager mWindowManager; private IBinder mAppToken; private String mAppName; @@ -565,6 +584,18 @@ public abstract class Window { int getWindowStackId() throws RemoteException; } + /** + * Callback for clients that want to be aware of where caption draws content. + */ + public interface RestrictedCaptionAreaListener { + /** + * Called when the area where caption draws content changes. + * + * @param rect The area where caption content is positioned, relative to the top view. + */ + void onRestrictedCaptionAreaChanged(Rect rect); + } + public Window(Context context) { mContext = context; mFeatures = mLocalFeatures = getDefaultFeatures(context); @@ -778,6 +809,16 @@ public abstract class Window { } /** + * Set a callback for changes of area where caption will draw its content. + * + * @param listener Callback that will be called when the area changes. + */ + public final void setRestrictedCaptionAreaListener(RestrictedCaptionAreaListener listener) { + mRestrictedCaptionAreaListener = listener; + mRestrictedCaptionAreaRect = listener != null ? new Rect() : null; + } + + /** * Take ownership of this window's surface. The window's view hierarchy * will no longer draw into the surface, though it will otherwise continue * to operate (such as for receiving input events). The given SurfaceHolder @@ -2040,5 +2081,29 @@ public abstract class Window { return mOverlayWithDecorCaption; } + /** @hide */ + public void notifyRestrictedCaptionAreaCallback(int left, int top, int right, int bottom) { + if (mRestrictedCaptionAreaListener != null) { + mRestrictedCaptionAreaRect.set(left, top, right, bottom); + mRestrictedCaptionAreaListener.onRestrictedCaptionAreaChanged( + mRestrictedCaptionAreaRect); + } + } + + /** + * Set what color should the caption controls be. By default the system will try to determine + * the color from the theme. You can overwrite this by using {@link #DECOR_CAPTION_SHADE_DARK} + * or {@link #DECOR_CAPTION_SHADE_DARK}. + */ + public abstract void setDecorCaptionShade(int decorCaptionShade); + /** + * Set the drawable that is drawn underneath the caption during the resizing. + * + * During the resizing the caption might not be drawn fast enough to match the new dimensions. + * There is a second caption drawn underneath it that will be fast enough. By default the + * caption is constructed from the theme. You can provide a drawable, that will be drawn instead + * to better match your application. + */ + public abstract void setResizingCaptionDrawable(Drawable drawable); } diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index fbaf51c27b61..a42f4d95c1ed 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -16,6 +16,7 @@ package android.view.inputmethod; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -50,6 +51,7 @@ import java.util.Locale; * * @attr ref android.R.styleable#InputMethod_Subtype_label * @attr ref android.R.styleable#InputMethod_Subtype_icon + * @attr ref android.R.styleable#InputMethod_Subtype_languageTag * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue @@ -60,6 +62,7 @@ import java.util.Locale; */ public final class InputMethodSubtype implements Parcelable { private static final String TAG = InputMethodSubtype.class.getSimpleName(); + private static final String LANGUAGE_TAG_NONE = ""; private static final String EXTRA_VALUE_PAIR_SEPARATOR = ","; private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "="; // TODO: remove this @@ -74,6 +77,7 @@ public final class InputMethodSubtype implements Parcelable { private final int mSubtypeNameResId; private final int mSubtypeId; private final String mSubtypeLocale; + private final String mSubtypeLanguageTag; private final String mSubtypeMode; private final String mSubtypeExtraValue; private volatile HashMap<String, String> mExtraValueHashMapCache; @@ -171,6 +175,15 @@ public final class InputMethodSubtype implements Parcelable { private String mSubtypeLocale = ""; /** + * @param languageTag is the BCP-47 Language Tag supported by this subtype. + */ + public InputMethodSubtypeBuilder setLanguageTag(String languageTag) { + mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag; + return this; + } + private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE; + + /** * @param subtypeMode is the mode supported by this subtype. */ public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) { @@ -271,6 +284,7 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameResId = builder.mSubtypeNameResId; mSubtypeIconResId = builder.mSubtypeIconResId; mSubtypeLocale = builder.mSubtypeLocale; + mSubtypeLanguageTag = builder.mSubtypeLanguageTag; mSubtypeMode = builder.mSubtypeMode; mSubtypeExtraValue = builder.mSubtypeExtraValue; mIsAuxiliary = builder.mIsAuxiliary; @@ -291,6 +305,8 @@ public final class InputMethodSubtype implements Parcelable { s = source.readString(); mSubtypeLocale = s != null ? s : ""; s = source.readString(); + mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE; + s = source.readString(); mSubtypeMode = s != null ? s : ""; s = source.readString(); mSubtypeExtraValue = s != null ? s : ""; @@ -318,21 +334,38 @@ public final class InputMethodSubtype implements Parcelable { /** * @return The locale of the subtype. This method returns the "locale" string parameter passed * to the constructor. + * + * @deprecated Use {@link #getLanguageTag()} instead. */ + @Deprecated + @NonNull public String getLocale() { return mSubtypeLocale; } /** - * @return The normalized {@link Locale} object of the subtype. The returned locale may or may - * not equal to "locale" string parameter passed to the constructor. + * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag + * is specified. * - * <p>TODO: Consider to make this a public API.</p> + * @see Locale#forLanguageTag(String) + */ + @NonNull + public String getLanguageTag() { + return mSubtypeLanguageTag; + } + + /** + * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not + * specified, then try to construct from {@link #getLocale()} + * + * <p>TODO: Consider to make this a public API, or move this to support lib.</p> * @hide */ @Nullable public Locale getLocaleObject() { - // TODO: Move the following method from InputMethodUtils to InputMethodSubtype. + if (!TextUtils.isEmpty(mSubtypeLanguageTag)) { + return Locale.forLanguageTag(mSubtypeLanguageTag); + } return InputMethodUtils.constructLocaleFromString(mSubtypeLocale); } @@ -476,13 +509,14 @@ public final class InputMethodSubtype implements Parcelable { return (subtype.hashCode() == hashCode()); } return (subtype.hashCode() == hashCode()) - && (subtype.getLocale().equals(getLocale())) - && (subtype.getMode().equals(getMode())) - && (subtype.getExtraValue().equals(getExtraValue())) - && (subtype.isAuxiliary() == isAuxiliary()) - && (subtype.overridesImplicitlyEnabledSubtype() - == overridesImplicitlyEnabledSubtype()) - && (subtype.isAsciiCapable() == isAsciiCapable()); + && (subtype.getLocale().equals(getLocale())) + && (subtype.getLanguageTag().equals(getLanguageTag())) + && (subtype.getMode().equals(getMode())) + && (subtype.getExtraValue().equals(getExtraValue())) + && (subtype.isAuxiliary() == isAuxiliary()) + && (subtype.overridesImplicitlyEnabledSubtype() + == overridesImplicitlyEnabledSubtype()) + && (subtype.isAsciiCapable() == isAsciiCapable()); } return false; } @@ -497,6 +531,7 @@ public final class InputMethodSubtype implements Parcelable { dest.writeInt(mSubtypeNameResId); dest.writeInt(mSubtypeIconResId); dest.writeString(mSubtypeLocale); + dest.writeString(mSubtypeLanguageTag); dest.writeString(mSubtypeMode); dest.writeString(mSubtypeExtraValue); dest.writeInt(mIsAuxiliary ? 1 : 0); diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java index 491de781a20d..471b6d4322f7 100644 --- a/core/java/android/view/textservice/SpellCheckerInfo.java +++ b/core/java/android/view/textservice/SpellCheckerInfo.java @@ -117,6 +117,8 @@ public final class SpellCheckerInfo implements Parcelable { a.getString(com.android.internal.R.styleable .SpellChecker_Subtype_subtypeLocale), a.getString(com.android.internal.R.styleable + .SpellChecker_Subtype_languageTag), + a.getString(com.android.internal.R.styleable .SpellChecker_Subtype_subtypeExtraValue), a.getInt(com.android.internal.R.styleable .SpellChecker_Subtype_subtypeId, 0)); diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java index f2b03ccaab8c..df336988cf8f 100644 --- a/core/java/android/view/textservice/SpellCheckerSubtype.java +++ b/core/java/android/view/textservice/SpellCheckerSubtype.java @@ -18,6 +18,7 @@ package android.view.textservice; import com.android.internal.inputmethod.InputMethodUtils; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -40,6 +41,7 @@ import java.util.Locale; * @see SpellCheckerInfo * * @attr ref android.R.styleable#SpellChecker_Subtype_label + * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId @@ -49,11 +51,13 @@ public final class SpellCheckerSubtype implements Parcelable { private static final String EXTRA_VALUE_PAIR_SEPARATOR = ","; private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "="; private static final int SUBTYPE_ID_NONE = 0; + private static final String SUBTYPE_LANGUAGE_TAG_NONE = ""; private final int mSubtypeId; private final int mSubtypeHashCode; private final int mSubtypeNameResId; private final String mSubtypeLocale; + private final String mSubtypeLanguageTag; private final String mSubtypeExtraValue; private HashMap<String, String> mExtraValueHashMapCache; @@ -66,14 +70,17 @@ public final class SpellCheckerSubtype implements Parcelable { * * @param nameId The name of the subtype * @param locale The locale supported by the subtype + * @param languageTag The BCP-47 Language Tag associated with this subtype. * @param extraValue The extra value of the subtype * @param subtypeId The subtype ID that is supposed to be stable during package update. * * @hide */ - public SpellCheckerSubtype(int nameId, String locale, String extraValue, int subtypeId) { + public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue, + int subtypeId) { mSubtypeNameResId = nameId; mSubtypeLocale = locale != null ? locale : ""; + mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE; mSubtypeExtraValue = extraValue != null ? extraValue : ""; mSubtypeId = subtypeId; mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ? @@ -91,7 +98,7 @@ public final class SpellCheckerSubtype implements Parcelable { * to instantiate {@link SpellCheckerSubtype} object. */ public SpellCheckerSubtype(int nameId, String locale, String extraValue) { - this(nameId, locale, extraValue, SUBTYPE_ID_NONE); + this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE); } SpellCheckerSubtype(Parcel source) { @@ -100,6 +107,8 @@ public final class SpellCheckerSubtype implements Parcelable { s = source.readString(); mSubtypeLocale = s != null ? s : ""; s = source.readString(); + mSubtypeLanguageTag = s != null ? s : ""; + s = source.readString(); mSubtypeExtraValue = s != null ? s : ""; mSubtypeId = source.readInt(); mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ? @@ -115,12 +124,27 @@ public final class SpellCheckerSubtype implements Parcelable { /** * @return the locale of the subtype + * + * @deprecated Use {@link #getLanguageTag()} instead. */ + @Deprecated + @NonNull public String getLocale() { return mSubtypeLocale; } /** + * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag + * is specified. + * + * @see Locale#forLanguageTag(String) + */ + @NonNull + public String getLanguageTag() { + return mSubtypeLanguageTag; + } + + /** * @return the extra value of the subtype */ public String getExtraValue() { @@ -182,20 +206,24 @@ public final class SpellCheckerSubtype implements Parcelable { return (subtype.hashCode() == hashCode()) && (subtype.getNameResId() == getNameResId()) && (subtype.getLocale().equals(getLocale())) + && (subtype.getLanguageTag().equals(getLanguageTag())) && (subtype.getExtraValue().equals(getExtraValue())); } return false; } /** - * @return The normalized {@link Locale} object of the subtype. The returned locale may or may - * not equal to "locale" string parameter passed to the constructor. + * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not + * specified, then try to construct from {@link #getLocale()} * - * <p>TODO: Consider to make this a public API.</p> + * <p>TODO: Consider to make this a public API, or move this to support lib.</p> * @hide */ @Nullable public Locale getLocaleObject() { + if (!TextUtils.isEmpty(mSubtypeLanguageTag)) { + return Locale.forLanguageTag(mSubtypeLanguageTag); + } return InputMethodUtils.constructLocaleFromString(mSubtypeLocale); } @@ -234,6 +262,7 @@ public final class SpellCheckerSubtype implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(mSubtypeNameResId); dest.writeString(mSubtypeLocale); + dest.writeString(mSubtypeLanguageTag); dest.writeString(mSubtypeExtraValue); dest.writeInt(mSubtypeId); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 5146bc67c060..2d1f85508968 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -75,6 +75,7 @@ import android.view.ActionMode.Callback; import android.view.DisplayListCanvas; import android.view.DragEvent; import android.view.Gravity; +import android.view.InputDevice; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -229,7 +230,14 @@ public class Editor { // Set when this TextView gained focus with some text selected. Will start selection mode. boolean mCreatedWithASelection; - boolean mDoubleTap = false; + // Indicates the current tap state (first tap, double tap, or triple click). + private int mTapState = TAP_STATE_INITIAL; + private long mLastTouchUpTime = 0; + private static final int TAP_STATE_INITIAL = 0; + private static final int TAP_STATE_FIRST_TAP = 1; + private static final int TAP_STATE_DOUBLE_TAP = 2; + // Only for mouse input. + private static final int TAP_STATE_TRIPLE_CLICK = 3; private Runnable mInsertionActionModeRunnable; @@ -769,20 +777,12 @@ public class Editor { return retOffset; } - /** - * Adjusts selection to the word under last touch offset. Return true if the operation was - * successfully performed. - */ - private boolean selectCurrentWord() { - if (!mTextView.canSelectText()) { - return false; - } - + private boolean needsToSelectAllToSelectWordOrParagraph() { if (mTextView.hasPasswordTransformationMethod()) { // Always select all on a password field. // Cut/copy menu entries are not available for passwords, but being able to select all // is however useful to delete or paste to replace the entire content. - return mTextView.selectAllText(); + return true; } int inputType = mTextView.getInputType(); @@ -797,6 +797,21 @@ public class Editor { variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_FILTER) { + return true; + } + return false; + } + + /** + * Adjusts selection to the word under last touch offset. Return true if the operation was + * successfully performed. + */ + private boolean selectCurrentWord() { + if (!mTextView.canSelectText()) { + return false; + } + + if (needsToSelectAllToSelectWordOrParagraph()) { return mTextView.selectAllText(); } @@ -805,8 +820,8 @@ public class Editor { final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets); // Safety check in case standard touch event handling has been bypassed - if (minOffset < 0 || minOffset >= mTextView.getText().length()) return false; - if (maxOffset < 0 || maxOffset >= mTextView.getText().length()) return false; + if (minOffset < 0 || minOffset > mTextView.getText().length()) return false; + if (maxOffset < 0 || maxOffset > mTextView.getText().length()) return false; int selectionStart, selectionEnd; @@ -839,6 +854,63 @@ public class Editor { return selectionEnd > selectionStart; } + /** + * Adjusts selection to the paragraph under last touch offset. Return true if the operation was + * successfully performed. + */ + private boolean selectCurrentParagraph() { + if (!mTextView.canSelectText()) { + return false; + } + + if (needsToSelectAllToSelectWordOrParagraph()) { + return mTextView.selectAllText(); + } + + long lastTouchOffsets = getLastTouchOffsets(); + final int minLastTouchOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets); + final int maxLastTouchOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets); + + final long paragraphsRange = getParagraphsRange(minLastTouchOffset, maxLastTouchOffset); + final int start = TextUtils.unpackRangeStartFromLong(paragraphsRange); + final int end = TextUtils.unpackRangeEndFromLong(paragraphsRange); + if (start < end) { + Selection.setSelection((Spannable) mTextView.getText(), start, end); + return true; + } + return false; + } + + /** + * Get the minimum range of paragraphs that contains startOffset and endOffset. + */ + private long getParagraphsRange(int startOffset, int endOffset) { + final Layout layout = mTextView.getLayout(); + if (layout == null) { + return TextUtils.packRangeInLong(-1, -1); + } + final CharSequence text = mTextView.getText(); + int minLine = layout.getLineForOffset(startOffset); + // Search paragraph start. + while (minLine > 0) { + final int prevLineEndOffset = layout.getLineEnd(minLine - 1); + if (text.charAt(prevLineEndOffset - 1) == '\n') { + break; + } + minLine--; + } + int maxLine = layout.getLineForOffset(endOffset); + // Search paragraph end. + while (maxLine < layout.getLineCount() - 1) { + final int lineEndOffset = layout.getLineEnd(maxLine); + if (text.charAt(lineEndOffset - 1) == '\n') { + break; + } + maxLine++; + } + return TextUtils.packRangeInLong(layout.getLineStart(minLine), layout.getLineEnd(maxLine)); + } + void onLocaleChanged() { // Will be re-created on demand in getWordIterator with the proper new locale mWordIterator = null; @@ -1219,7 +1291,31 @@ public class Editor { } } + private void updateTapState(MotionEvent event) { + final int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); + // Detect double tap and triple click. + if (((mTapState == TAP_STATE_FIRST_TAP) + || ((mTapState == TAP_STATE_DOUBLE_TAP) && isMouse)) + && (SystemClock.uptimeMillis() - mLastTouchUpTime) <= + ViewConfiguration.getDoubleTapTimeout()) { + if (mTapState == TAP_STATE_FIRST_TAP) { + mTapState = TAP_STATE_DOUBLE_TAP; + } else { + mTapState = TAP_STATE_TRIPLE_CLICK; + } + } else { + mTapState = TAP_STATE_FIRST_TAP; + } + } + if (action == MotionEvent.ACTION_UP) { + mLastTouchUpTime = SystemClock.uptimeMillis(); + } + } + void onTouchEvent(MotionEvent event) { + updateTapState(event); updateFloatingToolbarVisibility(event); if (hasSelectionController()) { @@ -1773,7 +1869,8 @@ public class Editor { stopTextActionMode(); mPreserveDetachedSelection = false; - getSelectionController().enterDrag(); + getSelectionController().enterDrag( + SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_WORD); return true; } @@ -3777,30 +3874,6 @@ public class Editor { } } - public void showAtLocation(int offset) { - // TODO - investigate if there's a better way to show the handles - // after the drag accelerator has occured. - int[] tmpCords = new int[2]; - mTextView.getLocationInWindow(tmpCords); - - Layout layout = mTextView.getLayout(); - int posX = tmpCords[0]; - int posY = tmpCords[1]; - - final int line = layout.getLineForOffset(offset); - - int startX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - - mHotspotX - getHorizontalOffset() + getCursorOffset()); - int startY = layout.getLineBottom(line); - - // Take TextView's padding and scroll into account. - startX += mTextView.viewportToContentHorizontalOffset(); - startY += mTextView.viewportToContentVerticalOffset(); - - mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY, - startX + posX, startY + posY); - } - @Override protected void onDraw(Canvas c) { final int drawWidth = mDrawable.getIntrinsicWidth(); @@ -3933,13 +4006,16 @@ public class Editor { // Cancel the single tap delayed runnable. if (mInsertionActionModeRunnable != null - && (mDoubleTap || isCursorInsideEasyCorrectionSpan())) { + && ((mTapState == TAP_STATE_DOUBLE_TAP) + || (mTapState == TAP_STATE_TRIPLE_CLICK) + || isCursorInsideEasyCorrectionSpan())) { mTextView.removeCallbacks(mInsertionActionModeRunnable); } // Prepare and schedule the single tap runnable to run exactly after the double tap // timeout has passed. - if (!mDoubleTap && !isCursorInsideEasyCorrectionSpan() + if ((mTapState != TAP_STATE_DOUBLE_TAP) && (mTapState != TAP_STATE_TRIPLE_CLICK) + && !isCursorInsideEasyCorrectionSpan() && (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)) { if (mTextActionMode == null) { if (mInsertionActionModeRunnable == null) { @@ -4223,10 +4299,15 @@ public class Editor { boolean isExpanding; final float xDiff = x - mPrevX; + if (isStartHandle()) { + isExpanding = currLine < mPreviousLineTouched; + } else { + isExpanding = currLine > mPreviousLineTouched; + } if (atRtl == isStartHandle()) { - isExpanding = xDiff > 0 || currLine > mPreviousLineTouched; + isExpanding |= xDiff > 0; } else { - isExpanding = xDiff < 0 || currLine < mPreviousLineTouched; + isExpanding |= xDiff < 0; } if (mTextView.getHorizontallyScrolling()) { @@ -4492,14 +4573,24 @@ public class Editor { // Where the user first starts the drag motion. private int mStartOffset = -1; - // Indicates whether the user is selecting text and using the drag accelerator. - private boolean mDragAcceleratorActive; + private boolean mHaventMovedEnoughToStartDrag; // The line that a selection happened most recently with the drag accelerator. private int mLineSelectionIsOn = -1; // Whether the drag accelerator has selected past the initial line. private boolean mSwitchedLines = false; + // Indicates the drag accelerator mode that the user is currently using. + private int mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE; + // Drag accelerator is inactive. + private static final int DRAG_ACCELERATOR_MODE_INACTIVE = 0; + // Character based selection by dragging. Only for mouse. + private static final int DRAG_ACCELERATOR_MODE_CHARACTER = 1; + // Word based selection by dragging. Enabled after long pressing or double tapping. + private static final int DRAG_ACCELERATOR_MODE_WORD = 2; + // Paragraph based selection by dragging. Enabled after mouse triple click. + private static final int DRAG_ACCELERATOR_MODE_PARAGRAPH = 3; + SelectionModifierCursorController() { resetTouchOffsets(); } @@ -4510,7 +4601,6 @@ public class Editor { } initDrawables(); initHandles(); - hideInsertionPointCursorController(); } private void initDrawables() { @@ -4548,10 +4638,10 @@ public class Editor { if (mEndHandle != null) mEndHandle.hide(); } - public void enterDrag() { + public void enterDrag(int dragAcceleratorMode) { // Just need to init the handles / hide insertion cursor. show(); - mDragAcceleratorActive = true; + mDragAcceleratorMode = dragAcceleratorMode; // Start location of selection. mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX, mLastDownPositionY); @@ -4563,6 +4653,7 @@ public class Editor { // the user to continue dragging across the screen to select text; TextView will // scroll as necessary. mTextView.getParent().requestDisallowInterceptTouchEvent(true); + mTextView.cancelLongPress(); } public void onTouchEvent(MotionEvent event) { @@ -4570,6 +4661,7 @@ public class Editor { // selection and tap can move cursor from this tap position. final float eventX = event.getX(); final float eventY = event.getY(); + final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: if (extractedTextModeWillBeStarted()) { @@ -4582,7 +4674,8 @@ public class Editor { // Double tap detection if (mGestureStayedInTapRegion) { - if (mDoubleTap) { + if (mTapState == TAP_STATE_DOUBLE_TAP + || mTapState == TAP_STATE_TRIPLE_CLICK) { final float deltaX = eventX - mDownPositionX; final float deltaY = eventY - mDownPositionY; final float distanceSquared = deltaX * deltaX + deltaY * deltaY; @@ -4593,8 +4686,12 @@ public class Editor { boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop; - if (stayedInArea && isPositionOnText(eventX, eventY)) { - selectCurrentWordAndStartDrag(); + if (stayedInArea && (isMouse || isPositionOnText(eventX, eventY))) { + if (mTapState == TAP_STATE_DOUBLE_TAP) { + selectCurrentWordAndStartDrag(); + } else if (mTapState == TAP_STATE_TRIPLE_CLICK) { + selectCurrentParagraphAndStartDrag(); + } mDiscardNextActionUp = true; } } @@ -4639,94 +4736,168 @@ public class Editor { } } + if (isMouse && !isDragAcceleratorActive()) { + final int offset = mTextView.getOffsetForPosition(eventX, eventY); + if (mStartOffset != offset) { + // Start character based drag accelerator. + if (mTextActionMode != null) { + mTextActionMode.finish(); + } + enterDrag(DRAG_ACCELERATOR_MODE_CHARACTER); + mDiscardNextActionUp = true; + mHaventMovedEnoughToStartDrag = false; + } + } + if (mStartHandle != null && mStartHandle.isShowing()) { // Don't do the drag if the handles are showing already. break; } - if (mStartOffset != -1 && mTextView.getLayout() != null) { - if (!mHaventMovedEnoughToStartDrag) { - - float y = eventY; - if (mSwitchedLines) { - // Offset the finger by the same vertical offset as the handles. - // This improves visibility of the content being selected by - // shifting the finger below the content, this is applied once - // the user has switched lines. - final float fingerOffset = (mStartHandle != null) - ? mStartHandle.getIdealVerticalOffset() - : touchSlop; - y = eventY - fingerOffset; - } - - final int currLine = getCurrentLineAdjustedForSlop( - mTextView.getLayout(), - mLineSelectionIsOn, y); - if (!mSwitchedLines && currLine != mLineSelectionIsOn) { - // Break early here, we want to offset the finger position from - // the selection highlight, once the user moved their finger - // to a different line we should apply the offset and *not* switch - // lines until recomputing the position with the finger offset. - mSwitchedLines = true; - break; - } - - int startOffset; - int offset = mTextView.getOffsetAtCoordinate(currLine, eventX); - // Snap to word boundaries. - if (mStartOffset < offset) { - // Expanding with end handle. - offset = getWordEnd(offset); - startOffset = getWordStart(mStartOffset); - } else { - // Expanding with start handle. - offset = getWordStart(offset); - startOffset = getWordEnd(mStartOffset); - } - mLineSelectionIsOn = currLine; - Selection.setSelection((Spannable) mTextView.getText(), - startOffset, offset); - } - } + updateSelection(event); break; case MotionEvent.ACTION_UP: - if (mDragAcceleratorActive) { - // No longer dragging to select text, let the parent intercept events. - mTextView.getParent().requestDisallowInterceptTouchEvent(false); - - show(); - int startOffset = mTextView.getSelectionStart(); - int endOffset = mTextView.getSelectionEnd(); - - // Since we don't let drag handles pass once they're visible, we need to - // make sure the start / end locations are correct because the user *can* - // switch directions during the initial drag. - if (endOffset < startOffset) { - int tmp = endOffset; - endOffset = startOffset; - startOffset = tmp; - - // Also update the selection with the right offsets in this case. - Selection.setSelection((Spannable) mTextView.getText(), - startOffset, endOffset); - } + if (!isDragAcceleratorActive()) { + break; + } + updateSelection(event); - // Need to do this to display the handles. - mStartHandle.showAtLocation(startOffset); - mEndHandle.showAtLocation(endOffset); + // No longer dragging to select text, let the parent intercept events. + mTextView.getParent().requestDisallowInterceptTouchEvent(false); - // No longer the first dragging motion, reset. - startSelectionActionMode(); + int startOffset = mTextView.getSelectionStart(); + int endOffset = mTextView.getSelectionEnd(); - mDragAcceleratorActive = false; - mStartOffset = -1; - mSwitchedLines = false; + // Since we don't let drag handles pass once they're visible, we need to + // make sure the start / end locations are correct because the user *can* + // switch directions during the initial drag. + if (endOffset < startOffset) { + int tmp = endOffset; + endOffset = startOffset; + startOffset = tmp; + + // Also update the selection with the right offsets in this case. + Selection.setSelection((Spannable) mTextView.getText(), + startOffset, endOffset); + } + if (startOffset != endOffset) { + startSelectionActionMode(); } + + // No longer the first dragging motion, reset. + resetDragAcceleratorState(); break; } } + private void updateSelection(MotionEvent event) { + if (mTextView.getLayout() != null) { + switch (mDragAcceleratorMode) { + case DRAG_ACCELERATOR_MODE_CHARACTER: + updateCharacterBasedSelection(event); + break; + case DRAG_ACCELERATOR_MODE_WORD: + updateWordBasedSelection(event); + break; + case DRAG_ACCELERATOR_MODE_PARAGRAPH: + updateParagraphBasedSelection(event); + break; + } + } + } + + /** + * If the TextView allows text selection, selects the current paragraph and starts a drag. + * + * @return true if the drag was started. + */ + private boolean selectCurrentParagraphAndStartDrag() { + if (mInsertionActionModeRunnable != null) { + mTextView.removeCallbacks(mInsertionActionModeRunnable); + } + if (mTextActionMode != null) { + mTextActionMode.finish(); + } + if (!selectCurrentParagraph()) { + return false; + } + enterDrag(SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_PARAGRAPH); + return true; + } + + private void updateCharacterBasedSelection(MotionEvent event) { + final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY()); + Selection.setSelection((Spannable) mTextView.getText(), mStartOffset, offset); + } + + private void updateWordBasedSelection(MotionEvent event) { + if (mHaventMovedEnoughToStartDrag) { + return; + } + final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); + final ViewConfiguration viewConfig = ViewConfiguration.get( + mTextView.getContext()); + final float eventX = event.getX(); + final float eventY = event.getY(); + final int currLine; + if (isMouse) { + // No need to offset the y coordinate for mouse input. + currLine = mTextView.getLineAtCoordinate(eventY); + } else { + float y = eventY; + if (mSwitchedLines) { + // Offset the finger by the same vertical offset as the handles. + // This improves visibility of the content being selected by + // shifting the finger below the content, this is applied once + // the user has switched lines. + final int touchSlop = viewConfig.getScaledTouchSlop(); + final float fingerOffset = (mStartHandle != null) + ? mStartHandle.getIdealVerticalOffset() + : touchSlop; + y = eventY - fingerOffset; + } + + currLine = getCurrentLineAdjustedForSlop(mTextView.getLayout(), mLineSelectionIsOn, + y); + if (!mSwitchedLines && currLine != mLineSelectionIsOn) { + // Break early here, we want to offset the finger position from + // the selection highlight, once the user moved their finger + // to a different line we should apply the offset and *not* switch + // lines until recomputing the position with the finger offset. + mSwitchedLines = true; + return; + } + } + + int startOffset; + int offset = mTextView.getOffsetAtCoordinate(currLine, eventX); + // Snap to word boundaries. + if (mStartOffset < offset) { + // Expanding with end handle. + offset = getWordEnd(offset); + startOffset = getWordStart(mStartOffset); + } else { + // Expanding with start handle. + offset = getWordStart(offset); + startOffset = getWordEnd(mStartOffset); + } + mLineSelectionIsOn = currLine; + Selection.setSelection((Spannable) mTextView.getText(), + startOffset, offset); + } + + private void updateParagraphBasedSelection(MotionEvent event) { + final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY()); + + final int start = Math.min(offset, mStartOffset); + final int end = Math.max(offset, mStartOffset); + final long paragraphsRange = getParagraphsRange(start, end); + final int selectionStart = TextUtils.unpackRangeStartFromLong(paragraphsRange); + final int selectionEnd = TextUtils.unpackRangeEndFromLong(paragraphsRange); + Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd); + } + /** * @param event */ @@ -4749,8 +4920,12 @@ public class Editor { public void resetTouchOffsets() { mMinTouchOffset = mMaxTouchOffset = -1; + resetDragAcceleratorState(); + } + + private void resetDragAcceleratorState() { mStartOffset = -1; - mDragAcceleratorActive = false; + mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE; mSwitchedLines = false; } @@ -4765,7 +4940,7 @@ public class Editor { * @return true if the user is selecting text using the drag accelerator. */ public boolean isDragAcceleratorActive() { - return mDragAcceleratorActive; + return mDragAcceleratorMode != DRAG_ACCELERATOR_MODE_INACTIVE; } public void onTouchModeChanged(boolean isInTouchMode) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 94b75b73bb7d..5574f86795fb 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -17,7 +17,6 @@ package android.widget; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; - import android.R; import android.annotation.ColorInt; import android.annotation.DrawableRes; @@ -115,6 +114,7 @@ import android.view.Choreographer; import android.view.DragEvent; import android.view.Gravity; import android.view.HapticFeedbackConstants; +import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -618,9 +618,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final Paint mHighlightPaint; private boolean mHighlightPathBogus = true; - private boolean mFirstTouch = false; - private long mLastTouchUpTime = 0; - // Although these fields are specific to editable text, they are not added to Editor because // they are defined by the TextView's style and are theme-dependent. int mCursorDrawableRes; @@ -8406,23 +8403,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); - - if (mEditor != null && action == MotionEvent.ACTION_DOWN) { - // Detect double tap and inform the Editor. - if (mFirstTouch && (SystemClock.uptimeMillis() - mLastTouchUpTime) <= - ViewConfiguration.getDoubleTapTimeout()) { - mEditor.mDoubleTap = true; - mFirstTouch = false; - } else { - mEditor.mDoubleTap = false; - mFirstTouch = true; - } - } - - if (action == MotionEvent.ACTION_UP) { - mLastTouchUpTime = SystemClock.uptimeMillis(); - } - if (mEditor != null) { mEditor.onTouchEvent(event); diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java index 75ca639a2317..1b44ff3ec3fb 100644 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -63,17 +63,18 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame private boolean mReportNextDraw; private Drawable mCaptionBackgroundDrawable; + private Drawable mUserCaptionBackgroundDrawable; private Drawable mResizingBackgroundDrawable; private ColorDrawable mStatusBarColor; public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, - int statusBarColor) { + Drawable userCaptionBackgroundDrawable, int statusBarColor) { setName("ResizeFrame"); mRenderer = renderer; onResourcesLoaded(decorView, resizingBackgroundDrawable, captionBackgroundDrawable, - statusBarColor); + userCaptionBackgroundDrawable, statusBarColor); // Create a render node for the content and frame backdrop // which can be resized independently from the content. @@ -92,10 +93,12 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame } void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, - Drawable captionBackgroundDrawableDrawable, int statusBarColor) { + Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, + int statusBarColor) { mDecorView = decorView; mResizingBackgroundDrawable = resizingBackgroundDrawable; mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable; + mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; if (statusBarColor != 0) { mStatusBarColor = new ColorDrawable(statusBarColor); addSystemBarNodeIfNeeded(); @@ -281,8 +284,10 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame // Draw the caption and content backdrops in to our render node. DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height); - mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight); - mCaptionBackgroundDrawable.draw(canvas); + final Drawable drawable = mUserCaptionBackgroundDrawable != null + ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable; + drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight); + drawable.draw(canvas); // The backdrop: clear everything with the background. Clipping is done elsewhere. mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height); @@ -324,4 +329,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mChoreographer.postFrameCallback(this); } } + + void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) { + mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; + } } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 531ba2f97671..e40556446a39 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -74,6 +74,8 @@ import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getMode; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.Window.DECOR_CAPTION_SHADE_DARK; +import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; @@ -184,9 +186,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private boolean mWindowResizeCallbacksAdded = false; - BackdropFrameRenderer mBackdropFrameRenderer = null; + private BackdropFrameRenderer mBackdropFrameRenderer = null; private Drawable mResizingBackgroundDrawable; private Drawable mCaptionBackgroundDrawable; + private Drawable mUserCaptionBackgroundDrawable; DecorView(Context context, int featureId, PhoneWindow window) { super(context); @@ -1580,18 +1583,20 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind initializeElevation(); } - View onResourcesLoaded(LayoutInflater inflater, int layoutResource) { + void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { mStackId = getStackId(); mResizingBackgroundDrawable = getResizingBackgroundDrawable( mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource); - mCaptionBackgroundDrawable = - getContext().getDrawable(R.drawable.decor_caption_title_focused); + if (mCaptionBackgroundDrawable == null) { + mCaptionBackgroundDrawable = getContext().getDrawable( + R.drawable.decor_caption_title_focused); + } if (mBackdropFrameRenderer != null) { mBackdropFrameRenderer.onResourcesLoaded( this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, - getCurrentColor(mStatusColorViewState)); + mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState)); } mDecorCaptionView = createDecorCaptionView(inflater); @@ -1608,17 +1613,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } mContentRoot = (ViewGroup) root; initializeElevation(); - return root; } // Free floating overlapping windows require a caption. private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) { - DecorCaptionView DecorCaptionView = null; - for (int i = getChildCount() - 1; i >= 0 && DecorCaptionView == null; i--) { + DecorCaptionView decorCaptionView = null; + for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) { View view = getChildAt(i); if (view instanceof DecorCaptionView) { // The decor was most likely saved from a relaunch - so reuse it. - DecorCaptionView = (DecorCaptionView) view; + decorCaptionView = (DecorCaptionView) view; removeViewAt(i); } } @@ -1630,27 +1634,72 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind && ActivityManager.StackId.hasWindowDecor(mStackId)) { // Dependent on the brightness of the used title we either use the // dark or the light button frame. - if (DecorCaptionView == null) { - Context context = getContext(); + if (decorCaptionView == null) { + decorCaptionView = inflateDecorCaptionView(inflater); + } + decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/); + } else { + decorCaptionView = null; + } + + // Tell the decor if it has a visible caption. + enableCaption(decorCaptionView != null); + return decorCaptionView; + } + + private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) { + final Context context = getContext(); + // We make a copy of the inflater, so it has the right context associated with it. + inflater = inflater.from(context); + final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption, + null); + setDecorCaptionShade(context, view); + return view; + } + + private void setDecorCaptionShade(Context context, DecorCaptionView view) { + final int shade = mWindow.getDecorCaptionShade(); + switch (shade) { + case DECOR_CAPTION_SHADE_LIGHT: + setLightDecorCaptionShade(view); + break; + case DECOR_CAPTION_SHADE_DARK: + setDarkDecorCaptionShade(view); + break; + default: { TypedValue value = new TypedValue(); context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true); - inflater = inflater.from(context); + // We invert the shade depending on brightness of the theme. Dark shade for light + // theme and vice versa. Thanks to this the buttons should be visible on the + // background. if (Color.luminance(value.data) < 0.5) { - DecorCaptionView = (DecorCaptionView) inflater.inflate( - R.layout.decor_caption_dark, null); + setLightDecorCaptionShade(view); } else { - DecorCaptionView = (DecorCaptionView) inflater.inflate( - R.layout.decor_caption_light, null); + setDarkDecorCaptionShade(view); } + break; } - DecorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/); - } else { - DecorCaptionView = null; } + } - // Tell the decor if it has a visible caption. - enableCaption(DecorCaptionView != null); - return DecorCaptionView; + void updateDecorCaptionShade() { + if (mDecorCaptionView != null) { + setDecorCaptionShade(getContext(), mDecorCaptionView); + } + } + + private void setLightDecorCaptionShade(DecorCaptionView view) { + view.findViewById(R.id.maximize_window).setBackgroundResource( + R.drawable.decor_maximize_button_light); + view.findViewById(R.id.close_window).setBackgroundResource( + R.drawable.decor_close_button_light); + } + + private void setDarkDecorCaptionShade(DecorCaptionView view) { + view.findViewById(R.id.maximize_window).setBackgroundResource( + R.drawable.decor_maximize_button_dark); + view.findViewById(R.id.close_window).setBackgroundResource( + R.drawable.decor_close_button_dark); } /** @@ -1735,11 +1784,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (mBackdropFrameRenderer != null) { return; } - final ThreadedRenderer renderer = (ThreadedRenderer) getHardwareRenderer(); + final ThreadedRenderer renderer = getHardwareRenderer(); if (renderer != null) { mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, - getCurrentColor(mStatusColorViewState)); + mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState)); // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. // If we want to get the shadow shown while resizing, we would need to elevate a new @@ -1849,6 +1898,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind getResources().getDisplayMetrics()); } + /** + * Provide an override of the caption background drawable. + */ + void setUserCaptionBackgroundDrawable(Drawable drawable) { + mUserCaptionBackgroundDrawable = drawable; + if (mBackdropFrameRenderer != null) { + mBackdropFrameRenderer.setUserCaptionBackgroundDrawable(drawable); + } + } + private static class ColorViewState { View view = null; int targetVisibility = View.INVISIBLE; diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 337bb693561e..86bd7824fc4d 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -94,7 +94,6 @@ import android.view.accessibility.AccessibilityNodeProvider; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; -import android.widget.LinearLayout; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; @@ -273,6 +272,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private boolean mIsStartingWindow; private int mTheme = -1; + private int mDecorCaptionShade = DECOR_CAPTION_SHADE_AUTO; + static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); @@ -2527,7 +2528,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } mDecor.startChanging(); - final View in = mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); + mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { @@ -3720,4 +3721,21 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } } + + @Override + public void setResizingCaptionDrawable(Drawable drawable) { + mDecor.setUserCaptionBackgroundDrawable(drawable); + } + + @Override + public void setDecorCaptionShade(int decorCaptionShade) { + mDecorCaptionShade = decorCaptionShade; + if (mDecor != null) { + mDecor.updateDecorCaptionShade(); + } + } + + int getDecorCaptionShade() { + return mDecorCaptionShade; + } } diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java index d747686ca762..c3fe9e77e877 100644 --- a/core/java/com/android/internal/widget/DecorCaptionView.java +++ b/core/java/com/android/internal/widget/DecorCaptionView.java @@ -319,6 +319,10 @@ public class DecorCaptionView extends ViewGroup implements View.OnTouchListener, captionHeight + mContent.getMeasuredHeight()); } } + + // This assumes that the caption bar is at the top. + mOwner.notifyRestrictedCaptionAreaCallback(mMaximize.getLeft(), mMaximize.getTop(), + mClose.getRight(), mClose.getBottom()); } /** * Determine if the workspace is entirely covered by the window. diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 71f881e6d6e1..2488111247a2 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -595,14 +595,14 @@ enum { MEMINFO_COUNT }; -static long get_zram_mem_used() +static long long get_zram_mem_used() { #define ZRAM_SYSFS "/sys/block/zram0/" FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r"); if (f) { - long mem_used_total = 0; + long long mem_used_total = 0; - int matched = fscanf(f, "%*d %*d %ld %*d %*d %*d %*d", &mem_used_total); + int matched = fscanf(f, "%*d %*d %lld %*d %*d %*d %*d", &mem_used_total); if (matched != 1) ALOGW("failed to parse " ZRAM_SYSFS "mm_stat"); @@ -612,9 +612,9 @@ static long get_zram_mem_used() f = fopen(ZRAM_SYSFS "mem_used_total", "r"); if (f) { - long mem_used_total = 0; + long long mem_used_total = 0; - int matched = fscanf(f, "%ld", &mem_used_total); + int matched = fscanf(f, "%lld", &mem_used_total); if (matched != 1) ALOGW("failed to parse " ZRAM_SYSFS "mem_used_total"); diff --git a/core/res/Android.mk b/core/res/Android.mk index cfc791df43eb..a1bef8326fb7 100644 --- a/core/res/Android.mk +++ b/core/res/Android.mk @@ -23,6 +23,7 @@ LOCAL_CERTIFICATE := platform # Tell aapt to create "extending (non-application)" resource IDs, # since these resources will be used by many apps. LOCAL_AAPT_FLAGS := -x +LOCAL_AAPT_FLAGS += --private-symbols com.android.internal LOCAL_MODULE_TAGS := optional diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5251b20811c3..11271979f091 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -277,6 +277,7 @@ <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" /> <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" /> <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" /> + <protected-broadcast android:name="android.intent.action.BUGREPORT_STARTED" /> <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" /> <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" /> diff --git a/core/res/res/drawable/ic_decor_close_button_dark_focused.xml b/core/res/res/drawable/ic_decor_close_button_dark_focused.xml index d7b167ddab6e..0794ed376b12 100644 --- a/core/res/res/drawable/ic_decor_close_button_dark_focused.xml +++ b/core/res/res/drawable/ic_decor_close_button_dark_focused.xml @@ -23,7 +23,7 @@ Copyright (C) 2015 The Android Open Source Project android:translateX="8.0" android:translateY="8.0" > <path - android:fillColor="#FFFFFFFF" + android:fillColor="#ff000000" android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/> </group> </vector> diff --git a/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml b/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml index e2e81b91abf3..bd1db5164c3f 100644 --- a/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml +++ b/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml @@ -23,7 +23,7 @@ Copyright (C) 2015 The Android Open Source Project android:translateX="8.0" android:translateY="8.0" > <path - android:fillColor="#33FFFFFF" + android:fillColor="#33000000" android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/> </group> </vector> diff --git a/core/res/res/drawable/ic_decor_close_button_light_focused.xml b/core/res/res/drawable/ic_decor_close_button_light_focused.xml index 0794ed376b12..d7b167ddab6e 100644 --- a/core/res/res/drawable/ic_decor_close_button_light_focused.xml +++ b/core/res/res/drawable/ic_decor_close_button_light_focused.xml @@ -23,7 +23,7 @@ Copyright (C) 2015 The Android Open Source Project android:translateX="8.0" android:translateY="8.0" > <path - android:fillColor="#ff000000" + android:fillColor="#FFFFFFFF" android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/> </group> </vector> diff --git a/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml b/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml index bd1db5164c3f..e2e81b91abf3 100644 --- a/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml +++ b/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml @@ -23,7 +23,7 @@ Copyright (C) 2015 The Android Open Source Project android:translateX="8.0" android:translateY="8.0" > <path - android:fillColor="#33000000" + android:fillColor="#33FFFFFF" android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/> </group> </vector> diff --git a/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml b/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml index 73d808be9821..c23390e4bde9 100644 --- a/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml +++ b/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml @@ -23,10 +23,10 @@ Copyright (C) 2015 The Android Open Source Project android:translateX="8.0" android:translateY="8.0" > <path - android:fillColor="#FFFFFFFF" + android:fillColor="#FF000000" android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> <path - android:fillColor="#B2FFFFFF" + android:fillColor="#B2000000" android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/> </group> </vector> diff --git a/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml b/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml index dc79e10015f8..a194a3912b21 100644 --- a/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml +++ b/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml @@ -23,10 +23,10 @@ Copyright (C) 2015 The Android Open Source Project android:translateX="8.0" android:translateY="8.0" > <path - android:fillColor="#33FFFFFF" + android:fillColor="#33000000" android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> <path - android:fillColor="#33FFFFFF" + android:fillColor="#33000000" android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/> </group> </vector> diff --git a/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml b/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml index c23390e4bde9..73d808be9821 100644 --- a/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml +++ b/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml @@ -23,10 +23,10 @@ Copyright (C) 2015 The Android Open Source Project android:translateX="8.0" android:translateY="8.0" > <path - android:fillColor="#FF000000" + android:fillColor="#FFFFFFFF" android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> <path - android:fillColor="#B2000000" + android:fillColor="#B2FFFFFF" android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/> </group> </vector> diff --git a/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml b/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml index a194a3912b21..dc79e10015f8 100644 --- a/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml +++ b/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml @@ -23,10 +23,10 @@ Copyright (C) 2015 The Android Open Source Project android:translateX="8.0" android:translateY="8.0" > <path - android:fillColor="#33000000" + android:fillColor="#33FFFFFF" android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> <path - android:fillColor="#33000000" + android:fillColor="#33FFFFFF" android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/> </group> </vector> diff --git a/core/res/res/layout/decor_caption.xml b/core/res/res/layout/decor_caption.xml new file mode 100644 index 000000000000..02467369825d --- /dev/null +++ b/core/res/res/layout/decor_caption.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2015, 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. +*/ +--> + +<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:descendantFocusability="beforeDescendants" > + <LinearLayout + android:id="@+id/caption" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="end" + android:background="@drawable/decor_caption_title" + android:focusable="false" + android:descendantFocusability="blocksDescendants" > + <Button + android:id="@+id/maximize_window" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_margin="5dp" + android:padding="4dp" + android:layout_gravity="center_vertical|end" + android:contentDescription="@string/maximize_button_text" + android:background="@drawable/decor_maximize_button_dark" /> + <Button + android:id="@+id/close_window" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_margin="5dp" + android:padding="4dp" + android:layout_gravity="center_vertical|end" + android:contentDescription="@string/close_button_text" + android:background="@drawable/decor_close_button_dark" /> + </LinearLayout> +</com.android.internal.widget.DecorCaptionView> diff --git a/core/res/res/layout/decor_caption_dark.xml b/core/res/res/layout/decor_caption_dark.xml deleted file mode 100644 index 95d228971a06..000000000000 --- a/core/res/res/layout/decor_caption_dark.xml +++ /dev/null @@ -1,52 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** Copyright 2015, 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. -*/ ---> - -<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:descendantFocusability="beforeDescendants" > - <LinearLayout - android:id="@+id/caption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="end" - android:background="@drawable/decor_caption_title" - android:focusable="false" - android:descendantFocusability="blocksDescendants" > - <Button - android:id="@+id/maximize_window" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/maximize_button_text" - android:background="@drawable/decor_maximize_button_dark" /> - <Button - android:id="@+id/close_window" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/close_button_text" - android:background="@drawable/decor_close_button_dark" /> - </LinearLayout> -</com.android.internal.widget.DecorCaptionView> diff --git a/core/res/res/layout/decor_caption_light.xml b/core/res/res/layout/decor_caption_light.xml deleted file mode 100644 index f0f661e0fef0..000000000000 --- a/core/res/res/layout/decor_caption_light.xml +++ /dev/null @@ -1,52 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** Copyright 2015, 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. -*/ ---> - -<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:descendantFocusability="beforeDescendants" > - <LinearLayout - android:id="@+id/caption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="end" - android:background="@drawable/decor_caption_title" - android:focusable="false" - android:descendantFocusability="blocksDescendants" > - <Button - android:id="@+id/maximize_window" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/maximize_button_text" - android:background="@drawable/decor_maximize_button_light" /> - <Button - android:id="@+id/close_window" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/close_button_text" - android:background="@drawable/decor_close_button_light" /> - </LinearLayout> -</com.android.internal.widget.DecorCaptionView> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 9bca3d691506..786554cc41ba 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3042,7 +3042,8 @@ i <attr name="imeSubtypeLocale" format="string" /> <!-- The mode of the subtype. This string can be a mode (e.g. voice, keyboard...) and this string will be passed to the IME when the framework calls the IME with the - subtype. --> + subtype. {@link android.view.inputmethod.InputMethodSubtype#getLocale()} returns the + value specified in this attribute. --> <attr name="imeSubtypeMode" format="string" /> <!-- Set true if the subtype is auxiliary. An auxiliary subtype won't be shown in the input method selection list in the settings app. @@ -3067,6 +3068,9 @@ i this subtype. This is important because many password fields only allow ASCII-characters. --> <attr name="isAsciiCapable" format="boolean" /> + <!-- The BCP-47 Language Tag of the subtype. This replaces + {@link android.R.styleable#InputMethod_Subtype_imeSubtypeLocale}. --> + <attr name="languageTag" format="string" /> </declare-styleable> <!-- Use <code>spell-checker</code> as the root tag of the XML resource that @@ -3090,7 +3094,8 @@ i <attr name="label" /> <!-- The locale of the subtype. This string should be a locale (e.g. en_US, fr_FR...) This is also used by the framework to know the supported locales - of the spell checker. --> + of the spell checker. {@link android.view.textservice.SpellCheckerSubtype#getLocale()} + returns the value specified in this attribute. --> <attr name="subtypeLocale" format="string" /> <!-- The extra value of the subtype. This string can be any string and will be passed to the SpellChecker. --> @@ -3102,6 +3107,9 @@ i {@code Arrays.hashCode(new Object[] {subtypeLocale, extraValue}) will be used instead. --> <attr name="subtypeId" /> + <!-- The BCP-47 Language Tag of the subtype. This replaces + {@link android.R.styleable#SpellChecker_Subtype_subtypeLocale}. --> + <attr name="languageTag" /> </declare-styleable> <!-- Use <code>accessibility-service</code> as the root tag of the XML resource that diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index b6b2e204b9cf..addeb05f86f2 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2683,6 +2683,7 @@ <public type="attr" name="encryptionAware" /> <public type="attr" name="preferenceFragmentStyle" /> <public type="attr" name="canControlMagnification" /> + <public type="attr" name="languageTag" /> <public type="style" name="Theme.Material.DayNight" /> <public type="style" name="Theme.Material.DayNight.DarkActionBar" /> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index eadcae0fb322..530b08de8009 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -16,10 +16,6 @@ */ --> <resources> - <!-- We don't want to publish private symbols in android.R as part of the - SDK. Instead, put them here. --> - <private-symbols package="com.android.internal" /> - <!-- Private symbols that we need to reference from framework code. See frameworks/base/core/res/MakeJavaSymbols.sed for how to easily generate this. @@ -1958,9 +1954,12 @@ <java-symbol type="bool" name="config_built_in_sip_phone" /> <java-symbol type="id" name="maximize_window" /> <java-symbol type="id" name="close_window" /> - <java-symbol type="layout" name="decor_caption_light" /> - <java-symbol type="layout" name="decor_caption_dark" /> + <java-symbol type="layout" name="decor_caption" /> <java-symbol type="drawable" name="decor_caption_title_focused" /> + <java-symbol type="drawable" name="decor_close_button_dark" /> + <java-symbol type="drawable" name="decor_close_button_light" /> + <java-symbol type="drawable" name="decor_maximize_button_dark" /> + <java-symbol type="drawable" name="decor_maximize_button_light" /> <!-- From TelephonyProvider --> <java-symbol type="xml" name="apns" /> @@ -2267,6 +2266,7 @@ <!-- Floating toolbar --> <java-symbol type="id" name="floating_toolbar_menu_item_image_button" /> + <java-symbol type="id" name="overflow" /> <java-symbol type="layout" name="floating_popup_container" /> <java-symbol type="layout" name="floating_popup_menu_button" /> <java-symbol type="layout" name="floating_popup_open_overflow_button" /> diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml index 7ab0b130c293..e795c10003ee 100644 --- a/core/tests/coretests/res/layout/activity_text_view.xml +++ b/core/tests/coretests/res/layout/activity_text_view.xml @@ -23,6 +23,6 @@ <EditText android:id="@+id/textview" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="wrap_content" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java index 73fdb103fce1..4a1c414234bd 100644 --- a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java +++ b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java @@ -30,11 +30,16 @@ import static android.test.MoreAsserts.assertNotEqual; */ public class SpellCheckerSubtypeTest extends InstrumentationTestCase { private static final int SUBTYPE_SUBTYPE_ID_NONE = 0; + private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_NONE = ""; + private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE = ""; + private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_A = "en_GB"; + private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_A = "en-GB"; private static final int SUBTYPE_NAME_RES_ID_A = 0x12345; private static final String SUBTYPE_EXTRA_VALUE_A = "Key1=Value1,Key2=Value2"; private static final int SUBTYPE_SUBTYPE_ID_A = 42; private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_B = "en_IN"; + private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_B = "en-IN"; private static final int SUBTYPE_NAME_RES_ID_B = 0x54321; private static final String SUBTYPE_EXTRA_VALUE_B = "Key3=Value3,Key4=Value4"; private static final int SUBTYPE_SUBTYPE_ID_B = -42; @@ -60,9 +65,11 @@ public class SpellCheckerSubtypeTest extends InstrumentationTestCase { @SmallTest public void testSubtypeWithNoSubtypeId() throws Exception { final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, - SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE); + SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, + SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE); assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId()); assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale()); + assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag()); assertEquals("Value1", subtype.getExtraValueOf("Key1")); assertEquals("Value2", subtype.getExtraValueOf("Key2")); // Historically we have used SpellCheckerSubtype#hashCode() to track which subtype is @@ -75,6 +82,7 @@ public class SpellCheckerSubtypeTest extends InstrumentationTestCase { final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype); assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId()); assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale()); + assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag()); assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1")); assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2")); assertEquals( @@ -84,10 +92,12 @@ public class SpellCheckerSubtypeTest extends InstrumentationTestCase { public void testSubtypeWithSubtypeId() throws Exception { final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, - SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A); + SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, + SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A); assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId()); assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale()); + assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag()); assertEquals("Value1", subtype.getExtraValueOf("Key1")); assertEquals("Value2", subtype.getExtraValueOf("Key2")); // Similar to "SubtypeId" in InputMethodSubtype, "SubtypeId" in SpellCheckerSubtype enables @@ -97,6 +107,7 @@ public class SpellCheckerSubtypeTest extends InstrumentationTestCase { final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype); assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId()); assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale()); + assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag()); assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1")); assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2")); assertEquals(SUBTYPE_SUBTYPE_ID_A, clonedSubtype.hashCode()); @@ -104,30 +115,42 @@ public class SpellCheckerSubtypeTest extends InstrumentationTestCase { @SmallTest public void testGetLocaleObject() throws Exception { - assertEquals(new Locale("en"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "en", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); - assertEquals(new Locale("en", "US"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "en_US", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); - assertEquals(new Locale("en", "US", "POSIX"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "en_US_POSIX", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); - - // Special rewrite rule for "tl" for versions of Android earlier than Lollipop that did not - // support three letter language codes, and used "tl" (Tagalog) as the language string for - // "fil" (Filipino). - assertEquals(new Locale("fil"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "tl", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); - assertEquals(new Locale("fil", "PH"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "tl_PH", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); - assertEquals(new Locale("fil", "PH", "POSIX"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "tl_PH_POSIX", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); - - // So far rejecting invalid/unexpected locale strings is out of the scope. - assertEquals(new Locale("a"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "a", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); - assertEquals(new Locale("a b c"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "a b c", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); - assertEquals(new Locale("en-US"), new SpellCheckerSubtype( - SUBTYPE_NAME_RES_ID_A, "en-US", SUBTYPE_EXTRA_VALUE_A).getLocaleObject()); + assertEquals(new Locale("en", "GB"), + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_GB", + SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); + assertEquals(new Locale("en", "GB"), + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE, + "en-GB", SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); + + // If neither locale string nor language tag is specified, + // {@link SpellCheckerSubtype#getLocaleObject} returns null. + assertNull( + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE, + SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); + + // If both locale string and language tag are specified, + // {@link SpellCheckerSubtype#getLocaleObject} uses language tag. + assertEquals(new Locale("en", "GB"), + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_US", "en-GB", + SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); + + // Make sure that "tl_PH" is rewritten to "fil_PH" for spell checkers that need to support + // Android KitKat and prior, which do not support 3-letter language codes. + assertEquals(new Locale("fil", "PH"), + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "tl_PH", + SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); + + // "languageTag" attribute is available in Android N and later, where 3-letter country codes + // are guaranteed to be available. It's developers' responsibility for specifying a valid + // country subtags here and we do not rewrite "tl" to "fil" for simplicity. + assertEquals("tl", + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE, + "tl-PH", SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE) + .getLocaleObject().getLanguage()); } @SmallTest @@ -156,50 +179,83 @@ public class SpellCheckerSubtypeTest extends InstrumentationTestCase { // If subtype ID is 0 (== SUBTYPE_SUBTYPE_ID_NONE), we keep the same behavior. assertEquals( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE)); assertNotEqual( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE)); assertNotEqual( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE)); + assertNotEqual( + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE), + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, + SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE)); assertNotEqual( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_NONE), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_NONE)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B, + SUBTYPE_SUBTYPE_ID_NONE)); // If subtype ID is not 0, we test the equality based only on the subtype ID. assertEquals( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A)); assertEquals( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A)); assertEquals( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A)); + assertEquals( + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A), + new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, + SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A)); assertEquals( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_A)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B, + SUBTYPE_SUBTYPE_ID_A)); assertNotEqual( new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A), + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_A), new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, - SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_B)); + SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, + SUBTYPE_SUBTYPE_ID_B)); } + } diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java index ddbdc87a4972..83a9e01761d9 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java @@ -16,15 +16,23 @@ package android.widget; +import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles; +import static android.widget.espresso.DragHandleUtils.onHandleView; +import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex; import static android.widget.espresso.TextViewActions.mouseDoubleClickOnTextAtIndex; import static android.widget.espresso.TextViewActions.mouseLongClickOnTextAtIndex; import static android.widget.espresso.TextViewActions.mouseDoubleClickAndDragOnText; import static android.widget.espresso.TextViewActions.mouseDragOnText; import static android.widget.espresso.TextViewActions.mouseLongClickAndDragOnText; +import static android.widget.espresso.TextViewActions.mouseTripleClickAndDragOnText; +import static android.widget.espresso.TextViewActions.mouseTripleClickOnTextAtIndex; import static android.widget.espresso.TextViewAssertions.hasSelection; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import com.android.frameworks.coretests.R; @@ -48,10 +56,23 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< final String helloWorld = "Hello world!"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); + + assertNoSelectionHandles(); + onView(withId(R.id.textview)).perform( mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!"))); onView(withId(R.id.textview)).check(hasSelection("llo wor")); + + onHandleView(com.android.internal.R.id.selection_start_handle) + .check(matches(isDisplayed())); + onHandleView(com.android.internal.R.id.selection_end_handle) + .check(matches(isDisplayed())); + + onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w"))); + onView(withId(R.id.textview)).check(hasSelection("")); + + assertNoSelectionHandles(); } @SmallTest @@ -89,6 +110,9 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex( helloWorld.indexOf("rld"))); onView(withId(R.id.textview)).check(hasSelection("world")); + + onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(helloWorld.length())); + onView(withId(R.id.textview)).check(hasSelection("!")); } @SmallTest @@ -113,6 +137,9 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex( helloWorld.indexOf("rld"))); onView(withId(R.id.textview)).check(hasSelection("world")); + + onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(helloWorld.length())); + onView(withId(R.id.textview)).check(hasSelection("!")); } @SmallTest @@ -166,4 +193,102 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< mouseLongClickAndDragOnText(text.indexOf("j"), text.indexOf("f"))); onView(withId(R.id.textview)).check(hasSelection("efg hijk")); } + + @SmallTest + public void testSelectTextByTripleClick() throws Exception { + getActivity(); + + final StringBuilder builder = new StringBuilder(); + builder.append("First paragraph.\n"); + builder.append("Second paragraph."); + for (int i = 0; i < 10; i++) { + builder.append(" This paragraph is very long."); + } + builder.append('\n'); + builder.append("Third paragraph."); + final String text = builder.toString(); + + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(replaceText(text)); + + onView(withId(R.id.textview)).perform( + mouseTripleClickOnTextAtIndex(text.indexOf("rst"))); + onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n")); + + onView(withId(R.id.textview)).perform( + mouseTripleClickOnTextAtIndex(text.indexOf("cond"))); + onView(withId(R.id.textview)).check(hasSelection( + text.substring(text.indexOf("Second"), text.indexOf("Third")))); + + onView(withId(R.id.textview)).perform( + mouseTripleClickOnTextAtIndex(text.indexOf("ird"))); + onView(withId(R.id.textview)).check(hasSelection("Third paragraph.")); + + onView(withId(R.id.textview)).perform( + mouseTripleClickOnTextAtIndex(text.indexOf("very long"))); + onView(withId(R.id.textview)).check(hasSelection( + text.substring(text.indexOf("Second"), text.indexOf("Third")))); + } + + @SmallTest + public void testSelectTextByTripleClickAndDrag() throws Exception { + getActivity(); + + final StringBuilder builder = new StringBuilder(); + builder.append("First paragraph.\n"); + builder.append("Second paragraph."); + for (int i = 0; i < 10; i++) { + builder.append(" This paragraph is very long."); + } + builder.append('\n'); + builder.append("Third paragraph."); + final String text = builder.toString(); + + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(replaceText(text)); + + onView(withId(R.id.textview)).perform( + mouseTripleClickAndDragOnText(text.indexOf("irst"), text.indexOf("st"))); + onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n")); + + onView(withId(R.id.textview)).perform( + mouseTripleClickAndDragOnText(text.indexOf("cond"), text.indexOf("Third") - 2)); + onView(withId(R.id.textview)).check(hasSelection( + text.substring(text.indexOf("Second"), text.indexOf("Third")))); + + onView(withId(R.id.textview)).perform( + mouseTripleClickAndDragOnText(text.indexOf("First"), text.indexOf("ird"))); + onView(withId(R.id.textview)).check(hasSelection(text)); + } + + @SmallTest + public void testSelectTextByTripleClickAndDrag_reverse() throws Exception { + getActivity(); + + final StringBuilder builder = new StringBuilder(); + builder.append("First paragraph.\n"); + builder.append("Second paragraph."); + for (int i = 0; i < 10; i++) { + builder.append(" This paragraph is very long."); + } + builder.append('\n'); + builder.append("Third paragraph."); + final String text = builder.toString(); + + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(replaceText(text)); + + onView(withId(R.id.textview)).perform( + mouseTripleClickAndDragOnText(text.indexOf("st"), text.indexOf("irst"))); + onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n")); + + onView(withId(R.id.textview)).perform( + mouseTripleClickAndDragOnText(text.indexOf("Third") - 2, text.indexOf("cond"))); + onView(withId(R.id.textview)).check(hasSelection( + text.substring(text.indexOf("Second"), text.indexOf("Third")))); + + onView(withId(R.id.textview)).perform( + mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First"))); + onView(withId(R.id.textview)).check(hasSelection(text)); + } } diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index 461450595b77..e54723ea1b13 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -16,6 +16,8 @@ package android.widget; +import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles; +import static android.widget.espresso.DragHandleUtils.onHandleView; import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText; import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex; @@ -26,24 +28,22 @@ import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex; import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; import static android.widget.espresso.TextViewAssertions.hasSelection; import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed; +import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed; +import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup; +import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem; +import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.pressKey; +import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView; import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.RootMatchers.withDecorView; -import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; -import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static org.hamcrest.Matchers.allOf; import com.android.frameworks.coretests.R; -import android.support.test.espresso.NoMatchingRootException; -import android.support.test.espresso.NoMatchingViewException; -import android.support.test.espresso.ViewInteraction; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; import android.view.KeyEvent; @@ -153,19 +153,115 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV @SmallTest public void testToolbarAppearsAfterSelection() throws Exception { - // It'll be nice to check that the toolbar is not visible (or does not exist) here - // I can't currently find a way to do this. I'll get to it later. - final String text = "Toolbar appears after selection."; onView(withId(R.id.textview)).perform(click()); + assertFloatingToolbarIsNotDisplayed(); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); onView(withId(R.id.textview)).perform( longPressOnTextAtIndex(text.indexOf("appears"))); - // It takes the toolbar less than 100ms to start to animate into screen. - // Ideally, we'll wait using the UiController, but I guess this works for now. - Thread.sleep(100); - assertFloatingToolbarIsDisplayed(getActivity()); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + + final String text2 = "Toolbar disappears after typing text."; + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text2)); + assertFloatingToolbarIsNotDisplayed(); + } + + @SmallTest + public void testToolbarAndInsertionHandle() throws Exception { + final String text = "text"; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); + assertFloatingToolbarIsNotDisplayed(); + + onHandleView(com.android.internal.R.id.insertion_handle).perform(click()); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + + assertFloatingToolbarContainsItem( + getActivity().getString(com.android.internal.R.string.selectAll)); + assertFloatingToolbarDoesNotContainItem( + getActivity().getString(com.android.internal.R.string.copy)); + assertFloatingToolbarDoesNotContainItem( + getActivity().getString(com.android.internal.R.string.cut)); + } + + @SmallTest + public void testToolbarAndSelectionHandle() throws Exception { + final String text = "abcd efg hijk"; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); + + onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f"))); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + + assertFloatingToolbarContainsItem( + getActivity().getString(com.android.internal.R.string.selectAll)); + assertFloatingToolbarContainsItem( + getActivity().getString(com.android.internal.R.string.copy)); + assertFloatingToolbarContainsItem( + getActivity().getString(com.android.internal.R.string.cut)); + + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); + onHandleView(com.android.internal.R.id.selection_start_handle) + .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a'))); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + + onHandleView(com.android.internal.R.id.selection_end_handle) + .perform(dragHandle(textView, Handle.SELECTION_END, text.length())); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + + assertFloatingToolbarDoesNotContainItem( + getActivity().getString(com.android.internal.R.string.selectAll)); + assertFloatingToolbarContainsItem( + getActivity().getString(com.android.internal.R.string.copy)); + assertFloatingToolbarContainsItem( + getActivity().getString(com.android.internal.R.string.cut)); + } + + @SmallTest + public void testInsertionHandle() throws Exception { + final String text = "abcd efg hijk "; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); + + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); + + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); + + onHandleView(com.android.internal.R.id.insertion_handle) + .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a'))); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a"))); + + onHandleView(com.android.internal.R.id.insertion_handle) + .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f'))); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f"))); + } + + @SmallTest + public void testInsertionHandle_multiLine() throws Exception { + final String text = "abcd\n" + "efg\n" + "hijk\n"; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); + + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length())); + + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); + + onHandleView(com.android.internal.R.id.insertion_handle) + .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a'))); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a"))); + + onHandleView(com.android.internal.R.id.insertion_handle) + .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f'))); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f"))); } @SmallTest @@ -183,7 +279,7 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV onHandleView(com.android.internal.R.id.selection_end_handle) .check(matches(isDisplayed())); - final TextView textView = (TextView)getActivity().findViewById(R.id.textview); + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a'))); onView(withId(R.id.textview)).check(hasSelection("abcd efg")); @@ -200,7 +296,7 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i'))); - final TextView textView = (TextView)getActivity().findViewById(R.id.textview); + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('e'))); onView(withId(R.id.textview)).check(hasSelection("efg\nhijk")); @@ -219,13 +315,46 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV } @SmallTest + public void testSelectionHandles_multiLine_rtl() throws Exception { + // Arabic text. + final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n" + + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n" + + "\u0639\u063A\u063B"; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(replaceText(text)); + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length())); + onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('\u0634'))); + + final TextView textView = (TextView)getActivity().findViewById(R.id.textview); + onHandleView(com.android.internal.R.id.selection_start_handle) + .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062E'))); + onView(withId(R.id.textview)).check(hasSelection( + text.substring(text.indexOf('\u062D'), text.indexOf('\u0635') + 1))); + + onHandleView(com.android.internal.R.id.selection_start_handle) + .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062A'))); + onView(withId(R.id.textview)).check(hasSelection( + text.substring(text.indexOf('\u062A'), text.indexOf('\u0635') + 1))); + + onHandleView(com.android.internal.R.id.selection_end_handle) + .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u0638'))); + onView(withId(R.id.textview)).check(hasSelection( + text.substring(text.indexOf('\u062A'), text.indexOf('\u0638') + 1))); + + onHandleView(com.android.internal.R.id.selection_end_handle) + .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u063B'))); + onView(withId(R.id.textview)).check(hasSelection(text)); + } + + + @SmallTest public void testSelectionHandles_doesNotPassAnotherHandle() throws Exception { final String text = "abcd efg hijk lmn"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('f'))); - final TextView textView = (TextView)getActivity().findViewById(R.id.textview); + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('l'))); onView(withId(R.id.textview)).check(hasSelection("g")); @@ -243,7 +372,7 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i'))); - final TextView textView = (TextView)getActivity().findViewById(R.id.textview); + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('r') + 1)); onView(withId(R.id.textview)).check(hasSelection("k")); @@ -261,7 +390,7 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i'))); - final TextView textView = (TextView)getActivity().findViewById(R.id.textview); + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f'))); @@ -314,7 +443,7 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('m'))); - final TextView textView = (TextView)getActivity().findViewById(R.id.textview); + final TextView textView = (TextView) getActivity().findViewById(R.id.textview); onHandleView(com.android.internal.R.id.selection_start_handle) .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c'))); @@ -342,25 +471,4 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i'))); onView(withId(R.id.textview)).check(hasSelection("hijk")); } - - private static void assertNoSelectionHandles() { - try { - onHandleView(com.android.internal.R.id.selection_start_handle) - .check(matches(isDisplayed())); - } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) { - try { - onHandleView(com.android.internal.R.id.selection_end_handle) - .check(matches(isDisplayed())); - } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) { - return; - } - } - throw new AssertionError("Selection handle found"); - } - - private static ViewInteraction onHandleView(int id) - throws NoMatchingRootException, NoMatchingViewException, AssertionError { - return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class))) - .inRoot(withDecorView(hasDescendant(withId(id)))); - } } diff --git a/core/tests/coretests/src/android/widget/espresso/DragAction.java b/core/tests/coretests/src/android/widget/espresso/DragAction.java index ce975688a894..b2c8e38141b8 100644 --- a/core/tests/coretests/src/android/widget/espresso/DragAction.java +++ b/core/tests/coretests/src/android/widget/espresso/DragAction.java @@ -157,6 +157,59 @@ public final class DragAction implements ViewAction { }, /** + * Starts a drag with a mouse triple click. + */ + MOUSE_TRIPLE_CLICK { + private DownMotionPerformer downMotion = new DownMotionPerformer() { + @Override + @Nullable + public MotionEvent perform( + UiController uiController, float[] coordinates, float[] precision) { + MotionEvent downEvent = MotionEvents.sendDown( + uiController, coordinates, precision) + .down; + for (int i = 0; i < 2; ++i) { + try { + if (!MotionEvents.sendUp(uiController, downEvent)) { + String logMessage = "Injection of up event as part of the triple " + + "click failed. Sending cancel event."; + Log.d(TAG, logMessage); + MotionEvents.sendCancel(uiController, downEvent); + return null; + } + + long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime(); + uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout); + } finally { + downEvent.recycle(); + } + downEvent = MotionEvents.sendDown( + uiController, coordinates, precision).down; + } + return downEvent; + } + }; + + @Override + public Status sendSwipe( + UiController uiController, + float[] startCoordinates, float[] endCoordinates, float[] precision) { + return sendLinearDrag( + uiController, downMotion, startCoordinates, endCoordinates, precision); + } + + @Override + public String toString() { + return "mouse triple click and drag to select"; + } + + @Override + public UiController wrapUiController(UiController uiController) { + return new MouseUiController(uiController); + } + }, + + /** * Starts a drag with a tap. */ TAP { diff --git a/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java new file mode 100644 index 000000000000..f744cae226b6 --- /dev/null +++ b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.widget.espresso; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.RootMatchers.withDecorView; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.Matchers.allOf; + +import android.support.test.espresso.NoMatchingRootException; +import android.support.test.espresso.NoMatchingViewException; +import android.support.test.espresso.ViewInteraction; +import android.widget.Editor; + +public class DragHandleUtils { + private DragHandleUtils() { + + } + + public static void assertNoSelectionHandles() { + try { + onHandleView(com.android.internal.R.id.selection_start_handle) + .check(matches(isDisplayed())); + } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) { + try { + onHandleView(com.android.internal.R.id.selection_end_handle) + .check(matches(isDisplayed())); + } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) { + return; + } + } + throw new AssertionError("Selection handle found"); + } + + public static ViewInteraction onHandleView(int id) + throws NoMatchingRootException, NoMatchingViewException, AssertionError { + return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class))) + .inRoot(withDecorView(hasDescendant(withId(id)))); + } +} diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java index fc01d8416df3..f02fe004e5b8 100644 --- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java +++ b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java @@ -19,31 +19,133 @@ package android.widget.espresso; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.RootMatchers.withDecorView; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.isRoot; import static android.support.test.espresso.matcher.ViewMatchers.withTagValue; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import android.app.Activity; +import org.hamcrest.Matcher; + +import android.support.test.espresso.NoMatchingRootException; +import android.support.test.espresso.NoMatchingViewException; +import android.support.test.espresso.UiController; +import android.support.test.espresso.ViewAction; +import android.support.test.espresso.ViewInteraction; +import android.support.test.espresso.action.ViewActions; +import android.support.test.espresso.matcher.ViewMatchers; +import android.view.View; + import com.android.internal.widget.FloatingToolbar; /** * Espresso utility methods for the floating toolbar. */ public class FloatingToolbarEspressoUtils { - + private final static Object TAG = FloatingToolbar.FLOATING_TOOLBAR_TAG; private FloatingToolbarEspressoUtils() {} + private static ViewInteraction onFloatingToolBar() { + return onView(withTagValue(is(TAG))) + .inRoot(withDecorView(hasDescendant(withTagValue(is(TAG))))); + } + /** * Asserts that the floating toolbar is displayed on screen. * * @throws AssertionError if the assertion fails */ - public static void assertFloatingToolbarIsDisplayed(Activity activity) { - onView(withTagValue(is((Object) FloatingToolbar.FLOATING_TOOLBAR_TAG))) - .inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))) - .check(matches(isDisplayed())); + public static void assertFloatingToolbarIsDisplayed() { + onFloatingToolBar().check(matches(isDisplayed())); + } + + /** + * Asserts that the floating toolbar is not displayed on screen. + * + * @throws AssertionError if the assertion fails + */ + public static void assertFloatingToolbarIsNotDisplayed() { + try { + onFloatingToolBar().check(matches(isDisplayed())); + } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) { + return; + } + throw new AssertionError("Floating toolbar is displayed"); + } + + private static void toggleOverflow() { + final int id = com.android.internal.R.id.overflow; + onView(allOf(withId(id), isDisplayed())) + .inRoot(withDecorView(hasDescendant(withId(id)))) + .perform(ViewActions.click()); + onView(isRoot()).perform(SLEEP); + } + + public static void sleepForFloatingToolbarPopup() { + onView(isRoot()).perform(SLEEP); + } + + /** + * Asserts that the floating toolbar contains the specified item. + * + * @param itemLabel label of the item. + * @throws AssertionError if the assertion fails + */ + public static void assertFloatingToolbarContainsItem(String itemLabel) { + try{ + onFloatingToolBar().check(matches(hasDescendant(ViewMatchers.withText(itemLabel)))); + } catch (AssertionError e) { + try{ + toggleOverflow(); + } catch (NoMatchingViewException | NoMatchingRootException e2) { + // No overflow items. + throw e; + } + try{ + onFloatingToolBar().check(matches(hasDescendant(ViewMatchers.withText(itemLabel)))); + } finally { + toggleOverflow(); + } + } + } + + /** + * Asserts that the floating toolbar doesn't contain the specified item. + * + * @param itemLabel label of the item. + * @throws AssertionError if the assertion fails + */ + public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) { + try{ + assertFloatingToolbarContainsItem(itemLabel); + } catch (AssertionError e) { + return; + } + throw new AssertionError("Floating toolbar contains " + itemLabel); } + /** + * ViewAction to sleep to wait floating toolbar's animation. + */ + private static final ViewAction SLEEP = new ViewAction() { + private static final long SLEEP_DURATION = 400; + + @Override + public Matcher<View> getConstraints() { + return isDisplayed(); + } + + @Override + public String getDescription() { + return "Sleep " + SLEEP_DURATION + " ms."; + } + + @Override + public void perform(UiController uiController, View view) { + uiController.loopMainThreadForAtLeast(SLEEP_DURATION); + } + }; } diff --git a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java index de640ca1053d..e51f27857a4b 100644 --- a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java +++ b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java @@ -22,9 +22,13 @@ import android.support.test.espresso.UiController; import android.support.test.espresso.ViewAction; import android.support.test.espresso.action.CoordinatesProvider; import android.support.test.espresso.action.GeneralClickAction; +import android.support.test.espresso.action.MotionEvents; +import android.support.test.espresso.action.MotionEvents.DownResultHolder; import android.support.test.espresso.action.PrecisionDescriber; +import android.support.test.espresso.action.Press; import android.support.test.espresso.action.Tapper; import android.view.View; +import android.view.ViewConfiguration; /** * ViewAction for performing an click on View by a mouse. @@ -32,10 +36,58 @@ import android.view.View; public final class MouseClickAction implements ViewAction { private final GeneralClickAction mGeneralClickAction; - public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider, - PrecisionDescriber precisionDescriber) { + public enum CLICK implements Tapper { + TRIPLE { + @Override + public Tapper.Status sendTap(UiController uiController, float[] coordinates, + float[] precision) { + Tapper.Status stat = sendSingleTap(uiController, coordinates, precision); + boolean warning = false; + if (stat == Tapper.Status.FAILURE) { + return Tapper.Status.FAILURE; + } else if (stat == Tapper.Status.WARNING) { + warning = true; + } + + long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime(); + for (int i = 0; i < 2; i++) { + if (0 < doubleTapMinimumTimeout) { + uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout); + } + stat = sendSingleTap(uiController, coordinates, precision); + if (stat == Tapper.Status.FAILURE) { + return Tapper.Status.FAILURE; + } else if (stat == Tapper.Status.WARNING) { + warning = true; + } + } + + if (warning) { + return Tapper.Status.WARNING; + } else { + return Tapper.Status.SUCCESS; + } + } + }; + + private static Tapper.Status sendSingleTap(UiController uiController, + float[] coordinates, float[] precision) { + DownResultHolder res = MotionEvents.sendDown(uiController, coordinates, precision); + try { + if (!MotionEvents.sendUp(uiController, res.down)) { + MotionEvents.sendCancel(uiController, res.down); + return Tapper.Status.FAILURE; + } + } finally { + res.down.recycle(); + } + return res.longPress ? Tapper.Status.WARNING : Tapper.Status.SUCCESS; + } + }; + + public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider) { mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider, - precisionDescriber); + Press.PINPOINT); } @Override @@ -51,5 +103,13 @@ public final class MouseClickAction implements ViewAction { @Override public void perform(UiController uiController, View view) { mGeneralClickAction.perform(new MouseUiController(uiController), view); + long doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout(); + if (0 < doubleTapTimeout) { + // Wait to avoid false gesture detection. Without this wait, consecutive clicks can be + // detected as a triple click. e.g. 2 double clicks are detected as a triple click and + // a single click because espresso isn't aware of triple click detection logic, which + // is TextView specific gesture. + uiController.loopMainThreadForAtLeast(doubleTapTimeout); + } } } diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java index 32cc6d648556..54d5823c7aca 100644 --- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java +++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java @@ -64,7 +64,7 @@ public final class TextViewActions { */ public static ViewAction mouseClickOnTextAtIndex(int index) { return actionWithAssertions( - new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), Press.PINPOINT)); + new MouseClickAction(Tap.SINGLE, new TextCoordinates(index))); } /** @@ -94,7 +94,7 @@ public final class TextViewActions { */ public static ViewAction mouseDoubleClickOnTextAtIndex(int index) { return actionWithAssertions( - new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.PINPOINT)); + new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index))); } /** @@ -124,7 +124,22 @@ public final class TextViewActions { */ public static ViewAction mouseLongClickOnTextAtIndex(int index) { return actionWithAssertions( - new MouseClickAction(Tap.LONG, new TextCoordinates(index), Press.PINPOINT)); + new MouseClickAction(Tap.LONG, new TextCoordinates(index))); + } + + /** + * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br> + * <br> + * View constraints: + * <ul> + * <li>must be a TextView displayed on screen + * <ul> + * + * @param index The index of the TextView's text to triple-click on. + */ + public static ViewAction mouseTripleClickOnTextAtIndex(int index) { + return actionWithAssertions( + new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index))); } /** @@ -237,6 +252,28 @@ public final class TextViewActions { TextView.class)); } + /** + * Returns an action that triple click then drags by mouse on text from startIndex to endIndex + * on the TextView.<br> + * <br> + * View constraints: + * <ul> + * <li>must be a TextView displayed on screen + * <ul> + * + * @param startIndex The index of the TextView's text to start a drag from + * @param endIndex The index of the TextView's text to end the drag at + */ + public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) { + return actionWithAssertions( + new DragAction( + DragAction.Drag.MOUSE_TRIPLE_CLICK, + new TextCoordinates(startIndex), + new TextCoordinates(endIndex), + Press.PINPOINT, + TextView.class)); + } + public enum Handle { SELECTION_START, SELECTION_END, diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index a046512ef1cb..bcc2b40643be 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -18,6 +18,7 @@ package android.media; import android.Manifest; import android.annotation.DrawableRes; +import android.annotation.IntDef; import android.annotation.NonNull; import android.app.ActivityThread; import android.content.BroadcastReceiver; @@ -41,6 +42,8 @@ import android.text.TextUtils; import android.util.Log; import android.view.Display; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -201,6 +204,7 @@ public class MediaRouter { info.mDescription = sStatic.mResources.getText( com.android.internal.R.string.bluetooth_a2dp_audio_route_name); info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; + info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH; sStatic.mBluetoothA2dpRoute = info; addRouteStatic(sStatic.mBluetoothA2dpRoute); } else { @@ -480,6 +484,7 @@ public class MediaRouter { route.mName = globalRoute.name; route.mDescription = globalRoute.description; route.mSupportedTypes = globalRoute.supportedTypes; + route.mDeviceType = globalRoute.deviceType; route.mEnabled = globalRoute.enabled; route.setRealStatusCode(globalRoute.statusCode); route.mPlaybackType = globalRoute.playbackType; @@ -1411,6 +1416,7 @@ public class MediaRouter { newRoute.mDescription = sStatic.mResources.getText( com.android.internal.R.string.wireless_display_route_description); newRoute.updatePresentationDisplay(); + newRoute.mDeviceType = RouteInfo.DEVICE_TYPE_TV; return newRoute; } @@ -1470,6 +1476,7 @@ public class MediaRouter { CharSequence mDescription; private CharSequence mStatus; int mSupportedTypes; + int mDeviceType; RouteGroup mGroup; final RouteCategory mCategory; Drawable mIcon; @@ -1502,6 +1509,42 @@ public class MediaRouter { /** @hide */ public static final int STATUS_IN_USE = 5; /** @hide */ public static final int STATUS_CONNECTED = 6; + /** @hide */ + @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH}) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceType {} + + /** + * The default receiver device type of the route indicating the type is unknown. + * + * @see #getDeviceType + */ + public static final int DEVICE_TYPE_UNKNOWN = 0; + + /** + * A receiver device type of the route indicating the presentation of the media is happening + * on a TV. + * + * @see #getDeviceType + */ + public static final int DEVICE_TYPE_TV = 1; + + /** + * A receiver device type of the route indicating the presentation of the media is happening + * on a speaker. + * + * @see #getDeviceType + */ + public static final int DEVICE_TYPE_SPEAKER = 2; + + /** + * A receiver device type of the route indicating the presentation of the media is happening + * on a bluetooth device such as a bluetooth speaker. + * + * @see #getDeviceType + */ + public static final int DEVICE_TYPE_BLUETOOTH = 3; + private Object mTag; /** @@ -1533,6 +1576,7 @@ public class MediaRouter { RouteInfo(RouteCategory category) { mCategory = category; + mDeviceType = DEVICE_TYPE_UNKNOWN; } /** @@ -1670,6 +1714,18 @@ public class MediaRouter { return mSupportedTypes; } + /** + * Gets the type of the receiver device associated with this route. + * + * @return The type of the receiver device associated with this route: + * {@link #DEVICE_TYPE_BLUETOOTH}, {@link #DEVICE_TYPE_TV}, {@link #DEVICE_TYPE_SPEAKER}, + * or {@link #DEVICE_TYPE_UNKNOWN}. + */ + @DeviceType + public int getDeviceType() { + return mDeviceType; + } + /** @hide */ public boolean matchesTypes(int types) { return (mSupportedTypes & types) != 0; diff --git a/media/java/android/media/MediaRouterClientState.java b/media/java/android/media/MediaRouterClientState.java index 54b827631b5b..34e18f6c6094 100644 --- a/media/java/android/media/MediaRouterClientState.java +++ b/media/java/android/media/MediaRouterClientState.java @@ -104,6 +104,7 @@ public final class MediaRouterClientState implements Parcelable { public int volumeMax; public int volumeHandling; public int presentationDisplayId; + public @MediaRouter.RouteInfo.DeviceType int deviceType; public RouteInfo(String id) { this.id = id; @@ -113,6 +114,7 @@ public final class MediaRouterClientState implements Parcelable { playbackStream = -1; volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; presentationDisplayId = -1; + deviceType = MediaRouter.RouteInfo.DEVICE_TYPE_UNKNOWN; } public RouteInfo(RouteInfo other) { @@ -128,6 +130,7 @@ public final class MediaRouterClientState implements Parcelable { volumeMax = other.volumeMax; volumeHandling = other.volumeHandling; presentationDisplayId = other.presentationDisplayId; + deviceType = other.deviceType; } RouteInfo(Parcel in) { @@ -143,6 +146,7 @@ public final class MediaRouterClientState implements Parcelable { volumeMax = in.readInt(); volumeHandling = in.readInt(); presentationDisplayId = in.readInt(); + deviceType = in.readInt(); } @Override @@ -164,6 +168,7 @@ public final class MediaRouterClientState implements Parcelable { dest.writeInt(volumeMax); dest.writeInt(volumeHandling); dest.writeInt(presentationDisplayId); + dest.writeInt(deviceType); } @Override @@ -180,6 +185,7 @@ public final class MediaRouterClientState implements Parcelable { + ", volumeMax=" + volumeMax + ", volumeHandling=" + volumeHandling + ", presentationDisplayId=" + presentationDisplayId + + ", deviceType=" + deviceType + " }"; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 2d77c6a3044a..770d01121d55 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -316,12 +316,8 @@ public class DirectoryFragment extends Fragment { mAdapter = new DocumentsAdapter(context); mRecView.setAdapter(mAdapter); - mDefaultItemColor = context.getResources().getColor(android.R.color.transparent); - // Get the accent color. - TypedValue selColor = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true); - // Set the opacity to 10%. - mSelectedItemColor = (selColor.data & 0x00ffffff) | 0x16000000; + mDefaultItemColor = context.getResources().getColor(R.color.item_doc_background); + mSelectedItemColor = context.getResources().getColor(R.color.item_doc_background_selected); GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener() { @@ -943,6 +939,7 @@ public class DirectoryFragment extends Fragment { public void setSelected(boolean selected) { itemView.setActivated(selected); + itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor); } @Override diff --git a/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java b/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java new file mode 100644 index 000000000000..4b87da483b11 --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java @@ -0,0 +1,186 @@ +package com.android.providers.settings; + +import android.net.NetworkPolicy; +import android.net.NetworkTemplate; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Backup/Restore Serializer Class for android.net.NetworkPolicy + */ +public class NetworkPolicySerializer { + private static final boolean DEBUG = false; + private static final String TAG = "NetworkPolicySerializer"; + + private static final int NULL = 0; + private static final int NOT_NULL = 1; + /** + * Current Version of the Serializer. + */ + private static int STATE_VERSION = 1; + + /** + * Marshals an array of NetworkPolicy objects into a byte-array. + * + * @param policies - NetworkPolicies to be Marshaled + * @return byte array + */ + + public static byte[] marshalNetworkPolicies(NetworkPolicy policies[]) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (policies != null && policies.length != 0) { + DataOutputStream out = new DataOutputStream(baos); + try { + out.writeInt(STATE_VERSION); + out.writeInt(policies.length); + for (NetworkPolicy policy : policies) { + byte[] marshaledPolicy = marshalNetworkPolicy(policy); + if (marshaledPolicy != null) { + out.writeByte(NOT_NULL); + out.writeInt(marshaledPolicy.length); + out.write(marshaledPolicy); + } else { + out.writeByte(NULL); + } + } + } catch (IOException ioe) { + Log.e(TAG, "Failed to Convert NetworkPolicies to byte array", ioe); + baos.reset(); + } + } + return baos.toByteArray(); + } + + /** + * Unmarshals a byte array into an array of NetworkPolicy Objects + * + * @param data - marshaled NetworkPolicies Array + * @return NetworkPolicy[] array + */ + public static NetworkPolicy[] unmarshalNetworkPolicies(byte[] data) { + if (data == null || data.length == 0) { + return new NetworkPolicy[0]; + } + DataInputStream in = new DataInputStream(new ByteArrayInputStream(data)); + try { + int version = in.readInt(); + int length = in.readInt(); + NetworkPolicy[] policies = new NetworkPolicy[length]; + for (int i = 0; i < length; i++) { + byte isNull = in.readByte(); + if (isNull == NULL) continue; + int byteLength = in.readInt(); + byte[] policyData = new byte[byteLength]; + in.read(policyData, 0, byteLength); + policies[i] = unmarshalNetworkPolicy(policyData); + } + return policies; + } catch (IOException ioe) { + Log.e(TAG, "Failed to Convert byte array to NetworkPolicies", ioe); + return new NetworkPolicy[0]; + } + } + + /** + * Marshals a NetworkPolicy object into a byte-array. + * + * @param networkPolicy - NetworkPolicy to be Marshaled + * @return byte array + */ + public static byte[] marshalNetworkPolicy(NetworkPolicy networkPolicy) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (networkPolicy != null) { + DataOutputStream out = new DataOutputStream(baos); + try { + out.writeInt(STATE_VERSION); + writeNetworkTemplate(out, networkPolicy.template); + out.writeInt(networkPolicy.cycleDay); + writeString(out, networkPolicy.cycleTimezone); + out.writeLong(networkPolicy.warningBytes); + out.writeLong(networkPolicy.limitBytes); + out.writeLong(networkPolicy.lastWarningSnooze); + out.writeLong(networkPolicy.lastLimitSnooze); + out.writeInt(networkPolicy.metered ? 1 : 0); + out.writeInt(networkPolicy.inferred ? 1 : 0); + } catch (IOException ioe) { + Log.e(TAG, "Failed to Convert NetworkPolicy to byte array", ioe); + baos.reset(); + } + } + return baos.toByteArray(); + } + + /** + * Unmarshals a byte array into a NetworkPolicy Object + * + * @param data - marshaled NetworkPolicy Object + * @return NetworkPolicy Object + */ + public static NetworkPolicy unmarshalNetworkPolicy(byte[] data) { + if (data == null || data.length == 0) { + return null; + } + DataInputStream in = new DataInputStream(new ByteArrayInputStream(data)); + try { + int version = in.readInt(); + NetworkTemplate template = readNetworkTemplate(in, version); + int cycleDay = in.readInt(); + String cycleTimeZone = readString(in, version); + long warningBytes = in.readLong(); + long limitBytes = in.readLong(); + long lastWarningSnooze = in.readLong(); + long lastLimitSnooze = in.readLong(); + boolean metered = in.readInt() == 1; + boolean inferred = in.readInt() == 1; + return new NetworkPolicy(template, cycleDay, cycleTimeZone, warningBytes, limitBytes, + lastWarningSnooze, lastLimitSnooze, metered, inferred); + } catch (IOException ioe) { + Log.e(TAG, "Failed to Convert byte array to NetworkPolicy", ioe); + return null; + } + } + + private static NetworkTemplate readNetworkTemplate(DataInputStream in, int version) + throws IOException { + byte isNull = in.readByte(); + if (isNull == NULL) return null; + int matchRule = in.readInt(); + String subscriberId = readString(in, version); + String networkId = readString(in, version); + return new NetworkTemplate(matchRule, subscriberId, networkId); + } + + private static void writeNetworkTemplate(DataOutputStream out, NetworkTemplate template) + throws IOException { + if (template != null) { + out.writeByte(NOT_NULL); + out.writeInt(template.getMatchRule()); + writeString(out, template.getSubscriberId()); + writeString(out, template.getNetworkId()); + } else { + out.writeByte(NULL); + } + } + + private static String readString(DataInputStream in, int version) throws IOException { + byte isNull = in.readByte(); + if (isNull == NOT_NULL) { + return in.readUTF(); + } + return null; + } + + private static void writeString(DataOutputStream out, String val) throws IOException { + if (val != null) { + out.writeByte(NOT_NULL); + out.writeUTF(val); + } else { + out.writeByte(NULL); + } + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 2e96f18bedb2..185a03fc2e66 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -24,6 +24,7 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.net.NetworkPolicyManager; import android.net.Uri; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; @@ -81,10 +82,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final String KEY_GLOBAL = "global"; private static final String KEY_LOCALE = "locale"; private static final String KEY_LOCK_SETTINGS = "lock_settings"; + private static final String KEY_SOFTAP_CONFIG = "softap_config"; + private static final String KEY_NET_POLICIES = "network_policies"; + // Versioning of the state file. Increment this version // number any time the set of state items is altered. - private static final int STATE_VERSION = 4; + private static final int STATE_VERSION = 6; // Slots in the checksum array. Never insert new items in the middle // of this array; new slots must be appended. @@ -95,22 +99,28 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final int STATE_WIFI_CONFIG = 4; private static final int STATE_GLOBAL = 5; private static final int STATE_LOCK_SETTINGS = 6; + private static final int STATE_SOFTAP_CONFIG = 7; + private static final int STATE_NET_POLICIES = 8; - private static final int STATE_SIZE = 7; // The current number of state items + private static final int STATE_SIZE = 9; // The current number of state items // Number of entries in the checksum array at various version numbers private static final int STATE_SIZES[] = { - 0, - 4, // version 1 - 5, // version 2 added STATE_WIFI_CONFIG - 6, // version 3 added STATE_GLOBAL - STATE_SIZE // version 4 added STATE_LOCK_SETTINGS + 0, + 4, // version 1 + 5, // version 2 added STATE_WIFI_CONFIG + 6, // version 3 added STATE_GLOBAL + 7, // version 4 added STATE_LOCK_SETTINGS + 8, // version 5 added STATE_SOFTAP_CONFIG + STATE_SIZE // version 6 added STATE_NET_POLICIES }; // Versioning of the 'full backup' format private static final int FULL_BACKUP_VERSION = 3; private static final int FULL_BACKUP_ADDED_GLOBAL = 2; // added the "global" entry private static final int FULL_BACKUP_ADDED_LOCK_SETTINGS = 3; // added the "lock_settings" entry + private static final int FULL_BACKUP_ADDED_SOFTAP_CONF = 4; //added the "softap_config" entry + private static final int FULL_BACKUP_ADDED_NET_POLICIES = 5; //added the "network_policies" entry private static final int INTEGER_BYTE_COUNT = Integer.SIZE / Byte.SIZE; @@ -119,8 +129,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final String TAG = "SettingsBackupAgent"; private static final String[] PROJECTION = { - Settings.NameValueTable.NAME, - Settings.NameValueTable.VALUE + Settings.NameValueTable.NAME, + Settings.NameValueTable.VALUE }; private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf"; @@ -396,7 +406,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, - ParcelFileDescriptor newState) throws IOException { + ParcelFileDescriptor newState) throws IOException { byte[] systemSettingsData = getSystemSettings(); byte[] secureSettingsData = getSecureSettings(); @@ -405,26 +415,34 @@ public class SettingsBackupAgent extends BackupAgentHelper { byte[] locale = mSettingsHelper.getLocaleData(); byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT); byte[] wifiConfigData = getFileData(mWifiConfigFile); + byte[] softApConfigData = getSoftAPConfiguration(); + byte[] netPoliciesData = getNetworkPolicies(); long[] stateChecksums = readOldChecksums(oldState); stateChecksums[STATE_SYSTEM] = - writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data); + writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data); stateChecksums[STATE_SECURE] = - writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data); + writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data); stateChecksums[STATE_GLOBAL] = - writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, globalSettingsData, data); + writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, globalSettingsData, data); stateChecksums[STATE_LOCALE] = - writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data); + writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data); stateChecksums[STATE_WIFI_SUPPLICANT] = - writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT, - wifiSupplicantData, data); + writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT, + wifiSupplicantData, data); stateChecksums[STATE_WIFI_CONFIG] = - writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData, - data); + writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData, + data); stateChecksums[STATE_LOCK_SETTINGS] = - writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS, - lockSettingsData, data); + writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS, + lockSettingsData, data); + stateChecksums[STATE_SOFTAP_CONFIG] = + writeIfChanged(stateChecksums[STATE_SOFTAP_CONFIG], KEY_SOFTAP_CONFIG, + softApConfigData, data); + stateChecksums[STATE_NET_POLICIES] = + writeIfChanged(stateChecksums[STATE_NET_POLICIES], KEY_NET_POLICIES, + netPoliciesData, data); writeNewChecksums(stateChecksums, newState); } @@ -504,7 +522,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoredSupplicantData, restoredSupplicantData.length); FileUtils.setPermissions(FILE_WIFI_SUPPLICANT, FileUtils.S_IRUSR | FileUtils.S_IWUSR | - FileUtils.S_IRGRP | FileUtils.S_IWGRP, + FileUtils.S_IRGRP | FileUtils.S_IWGRP, Process.myUid(), Process.WIFI_UID); } if (restoredWifiConfigFile != null) { @@ -533,7 +551,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { @Override public void onRestore(BackupDataInput data, int appVersionCode, - ParcelFileDescriptor newState) throws IOException { + ParcelFileDescriptor newState) throws IOException { HashSet<String> movedToGlobal = new HashSet<String>(); Settings.System.getMovedToGlobalSettings(movedToGlobal); @@ -561,7 +579,15 @@ public class SettingsBackupAgent extends BackupAgentHelper { mWifiRestore.incorporateWifiConfigFile(data); } else if (KEY_LOCK_SETTINGS.equals(key)) { restoreLockSettings(data); - } else { + } else if (KEY_SOFTAP_CONFIG.equals(key)){ + byte[] softapData = new byte[size]; + data.readEntityData(softapData, 0, size); + restoreSoftApConfiguration(softapData); + } else if (KEY_NET_POLICIES.equals(key)) { + byte[] netPoliciesData = new byte[size]; + data.readEntityData(netPoliciesData, 0, size); + restoreNetworkPolicies(netPoliciesData); + } else { data.skipEntityData(); } } @@ -589,6 +615,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { byte[] locale = mSettingsHelper.getLocaleData(); byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT); byte[] wifiConfigData = getFileData(mWifiConfigFile); + byte[] softApConfigData = getSoftAPConfiguration(); + byte[] netPoliciesData = getNetworkPolicies(); // Write the data to the staging file, then emit that as our tarfile // representation of the backed-up settings. @@ -623,6 +651,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (DEBUG_BACKUP) Log.d(TAG, lockSettingsData.length + " bytes of lock settings data"); out.writeInt(lockSettingsData.length); out.write(lockSettingsData); + if (DEBUG_BACKUP) Log.d(TAG, softApConfigData.length + " bytes of softap config data"); + out.writeInt(softApConfigData.length); + out.write(softApConfigData); + if (DEBUG_BACKUP) Log.d(TAG, netPoliciesData.length + " bytes of network policies data"); + out.writeInt(netPoliciesData.length); + out.write(netPoliciesData); out.flush(); // also flushes downstream @@ -635,7 +669,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { @Override public void onRestoreFile(ParcelFileDescriptor data, long size, - int type, String domain, String relpath, long mode, long mtime) + int type, String domain, String relpath, long mode, long mtime) throws IOException { if (DEBUG_BACKUP) Log.d(TAG, "onRestoreFile() invoked"); // Our data is actually a blob of flattened settings data identical to that @@ -692,7 +726,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes); FileUtils.setPermissions(FILE_WIFI_SUPPLICANT, FileUtils.S_IRUSR | FileUtils.S_IWUSR | - FileUtils.S_IRGRP | FileUtils.S_IWGRP, + FileUtils.S_IRGRP | FileUtils.S_IWGRP, Process.myUid(), Process.WIFI_UID); // retain the previous WIFI state. enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED || @@ -715,6 +749,26 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + if (version >= FULL_BACKUP_ADDED_SOFTAP_CONF){ + nBytes = in.readInt(); + if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of softap config data"); + if (nBytes > buffer.length) buffer = new byte[nBytes]; + if (nBytes > 0) { + in.readFully(buffer, 0, nBytes); + restoreSoftApConfiguration(buffer); + } + } + + if (version >= FULL_BACKUP_ADDED_NET_POLICIES){ + nBytes = in.readInt(); + if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of network policies data"); + if (nBytes > buffer.length) buffer = new byte[nBytes]; + if (nBytes > 0) { + in.readFully(buffer, 0, nBytes); + restoreNetworkPolicies(buffer); + } + } + if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete."); } else { data.close(); @@ -754,7 +808,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { } private long writeIfChanged(long oldChecksum, String key, byte[] data, - BackupDataOutput output) { + BackupDataOutput output) { CRC32 checkSummer = new CRC32(); checkSummer.update(data); long newChecksum = checkSummer.getValue(); @@ -829,7 +883,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { } private void restoreSettings(BackupDataInput data, Uri contentUri, - HashSet<String> movedToGlobal) { + HashSet<String> movedToGlobal) { byte[] settings = new byte[data.getDataSize()]; try { data.readEntityData(settings, 0, settings.length); @@ -841,7 +895,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { } private void restoreSettings(byte[] settings, int bytes, Uri contentUri, - HashSet<String> movedToGlobal) { + HashSet<String> movedToGlobal) { if (DEBUG) { Log.i(TAG, "restoreSettings: " + contentUri); } @@ -1160,6 +1214,30 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + private byte[] getSoftAPConfiguration(){ + WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE); + return WiFiConfigurationSerializer.marshalWifiConfig(wifiManager.getWifiApConfiguration()); + } + + private void restoreSoftApConfiguration(byte[] data){ + WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE); + wifiManager.setWifiApConfiguration(WiFiConfigurationSerializer.unmarshalWifiConfig(data)); + } + + private byte[] getNetworkPolicies(){ + NetworkPolicyManager networkPolicyManager = + (NetworkPolicyManager)getSystemService(NETWORK_POLICY_SERVICE); + return NetworkPolicySerializer + .marshalNetworkPolicies(networkPolicyManager.getNetworkPolicies()); + } + + private void restoreNetworkPolicies(byte[] data){ + NetworkPolicyManager networkPolicyManager = + (NetworkPolicyManager)getSystemService(NETWORK_POLICY_SERVICE); + networkPolicyManager + .setNetworkPolicies(NetworkPolicySerializer.unmarshalNetworkPolicies(data)); + } + /** * Write an int in BigEndian into the byte array. * @param out byte array @@ -1181,8 +1259,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { } private int readInt(byte[] in, int pos) { - int result = - ((in[pos ] & 0xFF) << 24) | + int result = ((in[pos ] & 0xFF) << 24) | ((in[pos + 1] & 0xFF) << 16) | ((in[pos + 2] & 0xFF) << 8) | ((in[pos + 3] & 0xFF) << 0); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java b/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java new file mode 100644 index 000000000000..f9f1d3f2fb33 --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.providers.settings; + +import android.net.IpConfiguration; +import android.net.LinkAddress; +import android.net.ProxyInfo; +import android.net.StaticIpConfiguration; +import android.net.wifi.WifiConfiguration; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.BitSet; + + +/** + * Backup/Restore Serializer Class for com.android.net.wifi.WifiConfiguration + */ +public class WiFiConfigurationSerializer { + private static final boolean DEBUG = false; + private static final String TAG = "WiFiConfigSerializer"; + + private static final int NULL = 0; + private static final int NOT_NULL = 1; + /** + * Current Version of the Serializer. + */ + private static int STATE_VERSION = 1; + + + /** + * Marshals a WifiConfig object into a byte-array. + * + * @param wifiConfig - WifiConfiguration to be Marshalled + * @return byte array + */ + + public static byte[] marshalWifiConfig(WifiConfiguration wifiConfig) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if(wifiConfig != null) { + DataOutputStream out = new DataOutputStream(baos); + try { + out.writeInt(STATE_VERSION); + out.writeInt(wifiConfig.networkId); + out.writeInt(wifiConfig.status); + out.writeInt(wifiConfig.disableReason); + writeString(out, wifiConfig.SSID); + writeString(out, wifiConfig.BSSID); + out.writeInt(wifiConfig.apBand); + out.writeInt(wifiConfig.apChannel); + writeString(out, wifiConfig.autoJoinBSSID); + writeString(out, wifiConfig.FQDN); + writeString(out, wifiConfig.providerFriendlyName); + out.writeInt(wifiConfig.roamingConsortiumIds.length); + for (long id : wifiConfig.roamingConsortiumIds) { + out.writeLong(id); + } + writeString(out, wifiConfig.preSharedKey); + for (String wepKey : wifiConfig.wepKeys) { + writeString(out, wepKey); + } + out.writeInt(wifiConfig.wepTxKeyIndex); + out.writeInt(wifiConfig.priority); + out.writeInt(wifiConfig.hiddenSSID ? 1 : 0); + out.writeInt(wifiConfig.requirePMF ? 1 : 0); + writeString(out, wifiConfig.updateIdentifier); + + writeBitSet(out, wifiConfig.allowedKeyManagement); + writeBitSet(out, wifiConfig.allowedProtocols); + writeBitSet(out, wifiConfig.allowedAuthAlgorithms); + writeBitSet(out, wifiConfig.allowedPairwiseCiphers); + writeBitSet(out, wifiConfig.allowedGroupCiphers); + + + //IpConfiguration + writeIpConfiguration(out, wifiConfig.getIpConfiguration()); + + writeString(out, wifiConfig.dhcpServer); + writeString(out, wifiConfig.defaultGwMacAddress); + out.writeInt(wifiConfig.autoJoinStatus); + out.writeInt(wifiConfig.selfAdded ? 1 : 0); + out.writeInt(wifiConfig.didSelfAdd ? 1 : 0); + out.writeInt(wifiConfig.validatedInternetAccess ? 1 : 0); + out.writeInt(wifiConfig.ephemeral ? 1 : 0); + out.writeInt(wifiConfig.creatorUid); + out.writeInt(wifiConfig.lastConnectUid); + out.writeInt(wifiConfig.lastUpdateUid); + writeString(out, wifiConfig.creatorName); + writeString(out, wifiConfig.lastUpdateName); + out.writeLong(wifiConfig.blackListTimestamp); + out.writeLong(wifiConfig.lastConnectionFailure); + out.writeLong(wifiConfig.lastRoamingFailure); + out.writeInt(wifiConfig.lastRoamingFailureReason); + out.writeLong(wifiConfig.roamingFailureBlackListTimeMilli); + out.writeLong(wifiConfig.numConnectionFailures); + out.writeLong(wifiConfig.numIpConfigFailures); + out.writeInt(wifiConfig.numAuthFailures); + out.writeInt(wifiConfig.numScorerOverride); + out.writeInt(wifiConfig.numScorerOverrideAndSwitchedNetwork); + out.writeInt(wifiConfig.numAssociation); + out.writeInt(wifiConfig.numUserTriggeredWifiDisableLowRSSI); + out.writeInt(wifiConfig.numUserTriggeredWifiDisableBadRSSI); + out.writeInt(wifiConfig.numUserTriggeredWifiDisableNotHighRSSI); + out.writeInt(wifiConfig.numTicksAtLowRSSI); + out.writeInt(wifiConfig.numTicksAtBadRSSI); + out.writeInt(wifiConfig.numTicksAtNotHighRSSI); + out.writeInt(wifiConfig.numUserTriggeredJoinAttempts); + out.writeInt(wifiConfig.autoJoinUseAggressiveJoinAttemptThreshold); + out.writeInt(wifiConfig.autoJoinBailedDueToLowRssi ? 1 : 0); + out.writeInt(wifiConfig.userApproved); + out.writeInt(wifiConfig.numNoInternetAccessReports); + out.writeInt(wifiConfig.noInternetAccessExpected ? 1 : 0); + } catch (IOException ioe) { + Log.e(TAG, "Failed to Convert WifiConfiguration to byte array", ioe); + baos.reset(); + } + } + return baos.toByteArray(); + } + + /** + * Unmarshals a byte array into a WifiConfig Object + * + * @param data - marshalled WifiConfig Object + * @return WifiConfiguration Object + */ + + public static WifiConfiguration unmarshalWifiConfig(byte[] data) { + if (data == null || data.length == 0) { + return null; + } + DataInputStream in = new DataInputStream(new ByteArrayInputStream(data)); + WifiConfiguration config = new WifiConfiguration(); + try { + int version = in.readInt(); + + config.networkId = in.readInt(); + config.status = in.readInt(); + config.disableReason = in.readInt(); + config.SSID = readString(in, version); + config.BSSID = readString(in, version); + config.apBand = in.readInt(); + config.apChannel = in.readInt(); + config.autoJoinBSSID = readString(in, version); + config.FQDN = readString(in, version); + config.providerFriendlyName = readString(in, version); + int numRoamingConsortiumIds = in.readInt(); + config.roamingConsortiumIds = new long[numRoamingConsortiumIds]; + for (int i = 0; i < numRoamingConsortiumIds; i++) { + config.roamingConsortiumIds[i] = in.readLong(); + } + config.preSharedKey = readString(in, version); + for (int i = 0; i < config.wepKeys.length; i++) { + config.wepKeys[i] = readString(in, version); + } + config.wepTxKeyIndex = in.readInt(); + config.priority = in.readInt(); + config.hiddenSSID = in.readInt() != 0; + config.requirePMF = in.readInt() != 0; + config.updateIdentifier = readString(in, version); + + config.allowedKeyManagement = readBitSet(in, version); + config.allowedProtocols = readBitSet(in, version); + config.allowedAuthAlgorithms = readBitSet(in, version); + config.allowedPairwiseCiphers = readBitSet(in, version); + config.allowedGroupCiphers = readBitSet(in, version); + + //Not backed-up because EnterpriseConfig involves + //Certificates which are device specific. + //config.enterpriseConfig = new WifiEnterpriseConfig(); + + config.setIpConfiguration(readIpConfiguration(in, version)); + + + config.dhcpServer = readString(in, version); + config.defaultGwMacAddress = readString(in, version); + config.autoJoinStatus = in.readInt(); + config.selfAdded = in.readInt() != 0; + config.didSelfAdd = in.readInt() != 0; + config.validatedInternetAccess = in.readInt() != 0; + config.ephemeral = in.readInt() != 0; + config.creatorUid = in.readInt(); + config.lastConnectUid = in.readInt(); + config.lastUpdateUid = in.readInt(); + config.creatorName = readString(in, version); + config.lastUpdateName = readString(in, version); + config.blackListTimestamp = in.readLong(); + config.lastConnectionFailure = in.readLong(); + config.lastRoamingFailure = in.readLong(); + config.lastRoamingFailureReason = in.readInt(); + config.roamingFailureBlackListTimeMilli = in.readLong(); + config.numConnectionFailures = in.readInt(); + config.numIpConfigFailures = in.readInt(); + config.numAuthFailures = in.readInt(); + 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(); + config.autoJoinUseAggressiveJoinAttemptThreshold = in.readInt(); + config.autoJoinBailedDueToLowRssi = in.readInt() != 0; + config.userApproved = in.readInt(); + config.numNoInternetAccessReports = in.readInt(); + config.noInternetAccessExpected = in.readInt() != 0; + } catch (IOException ioe) { + Log.e(TAG, "Failed to convert byte array to WifiConfiguration object", ioe); + return null; + } + return config; + } + + private static ProxyInfo readProxyInfo(DataInputStream in, int version) throws IOException { + int isNull = in.readByte(); + if (isNull == NULL) return null; + String host = readString(in, version); + int port = in.readInt(); + String exclusionList = readString(in, version); + return new ProxyInfo(host, port, exclusionList); + } + + private static void writeProxyInfo(DataOutputStream out, ProxyInfo proxyInfo) throws IOException { + if (proxyInfo != null) { + out.writeByte(NOT_NULL); + writeString(out, proxyInfo.getHost()); + out.writeInt(proxyInfo.getPort()); + writeString(out, proxyInfo.getExclusionListAsString()); + } else { + out.writeByte(NULL); + } + } + + private static InetAddress readInetAddress(DataInputStream in, int version) throws IOException { + int isNull = in.readByte(); + if (isNull == NULL) return null; + InetAddress address = null; + int addressLength = in.readInt(); + if (addressLength < 1) return address; + byte[] addressBytes = new byte[addressLength]; + in.read(addressBytes, 0, addressLength); + try { + address = InetAddress.getByAddress(addressBytes); + } catch (UnknownHostException unknownHostException) { + return null; + } + return address; + } + + private static void writeInetAddress(DataOutputStream out, InetAddress address) throws IOException { + if (address.getAddress() != null) { + out.writeByte(NOT_NULL); + out.writeInt(address.getAddress().length); + out.write(address.getAddress(), 0, address.getAddress().length); + } else { + out.writeByte(NULL); + } + } + + private static LinkAddress readLinkAddress(DataInputStream in, int version) throws IOException { + int isNull = in.readByte(); + if (isNull == NULL) return null; + InetAddress address = readInetAddress(in, version); + int prefixLength = in.readInt(); + int flags = in.readInt(); + int scope = in.readInt(); + return new LinkAddress(address, prefixLength, flags, scope); + } + + private static void writeLinkAddress(DataOutputStream out, LinkAddress address) throws IOException { + if (address != null) { + out.writeByte(NOT_NULL); + writeInetAddress(out, address.getAddress()); + out.writeInt(address.getPrefixLength()); + out.writeInt(address.getFlags()); + out.writeInt(address.getScope()); + } else { + out.writeByte(NULL); + } + } + + private static StaticIpConfiguration readStaticIpConfiguration(DataInputStream in, int version) throws IOException { + int isNull = in.readByte(); + if (isNull == NULL) return null; + StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration(); + staticIpConfiguration.ipAddress = readLinkAddress(in, version); + staticIpConfiguration.gateway = readInetAddress(in, version); + int dnsServersLength = in.readInt(); + for (int i = 0; i < dnsServersLength; i++) { + staticIpConfiguration.dnsServers.add(readInetAddress(in, version)); + } + staticIpConfiguration.domains = readString(in, version); + return staticIpConfiguration; + } + + private static void writeStaticIpConfiguration(DataOutputStream out, StaticIpConfiguration staticIpConfiguration) throws IOException { + if (staticIpConfiguration != null) { + out.writeByte(NOT_NULL); + writeLinkAddress(out, staticIpConfiguration.ipAddress); + writeInetAddress(out, staticIpConfiguration.gateway); + out.writeInt(staticIpConfiguration.dnsServers.size()); + for (InetAddress inetAddress : staticIpConfiguration.dnsServers) { + writeInetAddress(out, inetAddress); + } + writeString(out, staticIpConfiguration.domains); + } else { + out.writeByte(NULL); + } + } + + private static IpConfiguration readIpConfiguration(DataInputStream in, int version) throws IOException { + int isNull = in.readByte(); + if (isNull == NULL) return null; + IpConfiguration ipConfiguration = new IpConfiguration(); + String tmp = readString(in, version); + ipConfiguration.ipAssignment = tmp == null ? null : IpConfiguration.IpAssignment.valueOf(tmp); + tmp = readString(in, version); + ipConfiguration.proxySettings = tmp == null ? null : IpConfiguration.ProxySettings.valueOf(tmp); + ipConfiguration.staticIpConfiguration = readStaticIpConfiguration(in, version); + ipConfiguration.httpProxy = readProxyInfo(in, version); + return ipConfiguration; + } + + + private static void writeIpConfiguration(DataOutputStream out, IpConfiguration ipConfiguration) throws IOException { + if (ipConfiguration != null) { + out.writeByte(NOT_NULL); + writeString(out, ipConfiguration.ipAssignment != null ? ipConfiguration.ipAssignment.name() : null); + writeString(out, ipConfiguration.proxySettings != null ? ipConfiguration.proxySettings.name() : null); + writeStaticIpConfiguration(out, ipConfiguration.staticIpConfiguration); + writeProxyInfo(out, ipConfiguration.httpProxy); + } else { + out.writeByte(NULL); + } + + } + + private static String readString(DataInputStream in, int version) throws IOException { + byte isNull = in.readByte(); + if (isNull == NOT_NULL) { + return in.readUTF(); + } + return null; + } + + private static void writeString(DataOutputStream out, String val) throws IOException { + if (val != null) { + out.writeByte(NOT_NULL); + out.writeUTF(val); + } else { + out.writeByte(NULL); + } + } + + private static BitSet readBitSet(DataInputStream in, int version) throws IOException { + byte isNull = in.readByte(); + if (isNull == NOT_NULL) { + int length = in.readInt(); + byte[] bytes = new byte[length]; + in.read(bytes, 0, length); + return BitSet.valueOf(bytes); + } + return new BitSet(); + } + + private static void writeBitSet(DataOutputStream out, BitSet val) throws IOException { + if (val != null) { + out.writeByte(NOT_NULL); + byte[] byteArray = val.toByteArray(); + out.writeInt(byteArray.length); + out.write(byteArray); + } else { + out.writeByte(NULL); + } + } +} diff --git a/packages/SettingsProvider/test/Android.mk b/packages/SettingsProvider/test/Android.mk index ef863e7df0c2..f2789678debe 100644 --- a/packages/SettingsProvider/test/Android.mk +++ b/packages/SettingsProvider/test/Android.mk @@ -5,7 +5,10 @@ include $(CLEAR_VARS) # Note we statically link SettingsState to do some unit tests. It's not accessible otherwise # because this test is not an instrumentation test. (because the target runs in the system process.) LOCAL_SRC_FILES := $(call all-subdir-java-files) \ - ../src/com/android/providers/settings/SettingsState.java + ../src/com/android/providers/settings/SettingsState.java \ + ../src/com/android/providers/settings/WiFiConfigurationSerializer.java \ + ../src/com/android/providers/settings/NetworkPolicySerializer.java + LOCAL_PACKAGE_NAME := SettingsProviderTest @@ -13,4 +16,4 @@ LOCAL_MODULE_TAGS := tests LOCAL_CERTIFICATE := platform -include $(BUILD_PACKAGE)
\ No newline at end of file +include $(BUILD_PACKAGE) diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java new file mode 100644 index 000000000000..1986596f4ef1 --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java @@ -0,0 +1,117 @@ +package com.android.providers.settings; + +import android.net.NetworkPolicy; +import android.net.NetworkTemplate; +import android.test.AndroidTestCase; + +import java.util.Random; + +/** + * Tests for NetworkPolicySerializer + */ +public class NetworkPolicySerializerTest extends AndroidTestCase { + static Random sRandom = new Random(); + + public void testMarshallAndUnmarshalNetworkPolicy() { + NetworkPolicy policy = getDummyNetworkPolicy(); + byte[] data = NetworkPolicySerializer.marshalNetworkPolicy(policy); + assertNotNull("Got Null data from marshal", data); + assertFalse("Got back an empty byte[] from marshal", data.length == 0); + + NetworkPolicy unmarshaled = NetworkPolicySerializer.unmarshalNetworkPolicy(data); + assertNotNull("Got Null data from unmarshaled", unmarshaled); + assertTrue("NetworkPolicy Marshall and Unmarshal Failed!", policy.equals(unmarshaled)); + } + + public void testMarshallNetworkPolicyEdgeCases() { + byte[] data = NetworkPolicySerializer.marshalNetworkPolicy(null); + assertNotNull("NetworkPolicy marshal returned null. Expected: byte[0]", data); + assertEquals("NetworkPolicy marshal returned incomplete byte array. Expected: byte[0]", + data.length, 0); + } + + public void testUnmarshallNetworkPolicyEdgeCases() { + NetworkPolicy policy = NetworkPolicySerializer.unmarshalNetworkPolicy(null); + assertNull("Non null NetworkPolicy returned for null byte[] input", policy); + + policy = NetworkPolicySerializer.unmarshalNetworkPolicy(new byte[0]); + assertNull("Non null NetworkPolicy returned for empty byte[] input", policy); + + policy = NetworkPolicySerializer.unmarshalNetworkPolicy(new byte[]{10, 20, 30, 40, 50, 60}); + assertNull("Non null NetworkPolicy returned for incomplete byte[] input", policy); + } + + public void testMarshallAndUnmarshalNetworkPolicies() { + NetworkPolicy[] policies = getDummyNetworkPolicies(5); + byte[] data = NetworkPolicySerializer.marshalNetworkPolicies(policies); + assertNotNull("Got Null data from marshal", data); + assertFalse("Got back an empty byte[] from marshal", data.length == 0); + + NetworkPolicy[] unmarshaled = NetworkPolicySerializer.unmarshalNetworkPolicies(data); + assertNotNull("Got Null data from unmarshaled", unmarshaled); + try { + for (int i = 0; i < policies.length; i++) { + assertTrue("NetworkPolicies Marshall and Unmarshal Failed!", + policies[i].equals(unmarshaled[i])); + } + } catch (NullPointerException npe) { + assertTrue("Some policies were not marshaled/unmarshaled correctly", false); + } + } + + public void testMarshallNetworkPoliciesEdgeCases() { + byte[] data = NetworkPolicySerializer.marshalNetworkPolicies(null); + assertNotNull("NetworkPolicies marshal returned null!", data); + assertEquals("NetworkPolicies marshal returned incomplete byte array", data.length, 0); + + data = NetworkPolicySerializer.marshalNetworkPolicies(new NetworkPolicy[0]); + assertNotNull("NetworkPolicies marshal returned null for empty NetworkPolicy[]", data); + assertEquals("NetworkPolicies marshal returned incomplete byte array for empty NetworkPolicy[]" + , data.length, 0); + } + + public void testUnmarshalNetworkPoliciesEdgeCases() { + NetworkPolicy[] policies = NetworkPolicySerializer.unmarshalNetworkPolicies(null); + assertNotNull("NetworkPolicies unmarshal returned null for null input. Expected: byte[0] ", + policies); + assertEquals("Non Empty NetworkPolicy[] returned for null input Expected: byte[0]", + policies.length, 0); + + policies = NetworkPolicySerializer.unmarshalNetworkPolicies(new byte[0]); + assertNotNull("NetworkPolicies unmarshal returned null for empty byte[] input. Expected: byte[0]", + policies); + assertEquals("Non Empty NetworkPolicy[] returned for empty byte[] input. Expected: byte[0]", + policies.length, 0); + + policies = NetworkPolicySerializer.unmarshalNetworkPolicies(new byte[]{10, 20, 30, 40, 50, 60}); + assertNotNull("NetworkPolicies unmarshal returned null for incomplete byte[] input. " + + "Expected: byte[0] ", policies); + assertEquals("Non Empty NetworkPolicy[] returned for incomplete byte[] input Expected: byte[0]", + policies.length, 0); + + } + + private NetworkPolicy[] getDummyNetworkPolicies(int num) { + NetworkPolicy[] policies = new NetworkPolicy[num]; + for (int i = 0; i < num; i++) { + policies[i] = getDummyNetworkPolicy(); + } + return policies; + } + + private NetworkPolicy getDummyNetworkPolicy() { + NetworkTemplate template = new NetworkTemplate(NetworkTemplate.MATCH_MOBILE_ALL, "subId", + "GoogleGuest"); + int cycleDay = sRandom.nextInt(); + String cycleTimezone = "timezone"; + long warningBytes = sRandom.nextLong(); + long limitBytes = sRandom.nextLong(); + long lastWarningSnooze = sRandom.nextLong(); + long lastLimitSnooze = sRandom.nextLong(); + boolean metered = sRandom.nextInt() % 2 == 0; + boolean inferred = sRandom.nextInt() % 2 == 0; + return new NetworkPolicy(template, cycleDay, cycleTimezone, warningBytes, limitBytes, + lastWarningSnooze, lastLimitSnooze, metered, inferred); + } + +} diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index 0e31cdf4a0e2..44018a024432 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -40,6 +40,8 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import libcore.io.Streams; +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; import android.app.Instrumentation; import android.app.NotificationManager; import android.content.Context; @@ -130,7 +132,8 @@ public class BugreportReceiverTest extends InstrumentationTestCase { Bundle extras = sendBugreportFinishedIntent(42, PLAIN_TEXT_PATH, SCREENSHOT_PATH); assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT); - // TODO: assert service is down + String service = BugreportProgressService.class.getName(); + assertFalse("Service '" + service + "' is still running", isServiceRunning(service)); } public void testBugreportFinished_withWarning() throws Exception { @@ -306,6 +309,17 @@ public class BugreportReceiverTest extends InstrumentationTestCase { fail("Did not find entry '" + entryName + "' on file '" + uri + "'"); } + private boolean isServiceRunning(String name) { + ActivityManager manager = (ActivityManager) mContext + .getSystemService(Context.ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if (service.service.getClassName().equals(name)) { + return true; + } + } + return false; + } + private static void createTextFile(String path, String content) throws IOException { Log.v(TAG, "createFile(" + path + ")"); try (Writer writer = new BufferedWriter(new OutputStreamWriter( diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java index 5e8bab1d7a3c..7d3713724e76 100644 --- a/packages/Shell/tests/src/com/android/shell/UiBot.java +++ b/packages/Shell/tests/src/com/android/shell/UiBot.java @@ -118,7 +118,8 @@ final class UiBot { // TODO: UI Automator should provide such logic. public void chooseActivity(String name) { // First check if the activity is the default option. - String shareText = String.format("Share with %s", name); + String shareText = "Share with " + name; + Log.v(TAG, "Waiting for ActivityChooser text: '" + shareText + "'"); boolean gotIt = mDevice.wait(Until.hasObject(By.text(shareText)), mTimeout); if (gotIt) { diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2e65656546c3..51b84f52498d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -136,6 +136,9 @@ android:protectionLevel="signature" /> <uses-permission android:name="com.android.systemui.permission.SELF" /> + <!-- Adding Quick Settings tiles --> + <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" /> + <application android:name=".SystemUIApplication" android:persistent="true" diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index e949adc8a748..a995ec778972 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -78,47 +78,64 @@ android:tint="@android:color/white" /> </LinearLayout> - <include layout="@layout/split_clock_view" - android:layout_width="wrap_content" + <TextView + android:id="@+id/header_emergency_calls_only" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="2dp" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:id="@+id/clock" - /> - - <com.android.systemui.statusbar.policy.DateView - android:id="@+id/date" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="6dp" - android:layout_marginTop="8dp" - android:layout_toEndOf="@id/clock" + android:layout_alignParentStart="true" android:layout_alignParentTop="true" - android:drawableStart="@drawable/header_dot" - android:drawablePadding="6dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:paddingTop="8dp" + android:visibility="gone" + android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly" + android:text="@*android:string/emergency_calls_only" android:singleLine="true" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" - android:textSize="@dimen/qs_time_collapsed_size" - systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" - /> + android:gravity="center_vertical" /> - <com.android.systemui.statusbar.AlphaOptimizedButton - android:id="@+id/alarm_status" + <LinearLayout + android:id="@+id/date_time_group" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentStart="true" android:layout_alignParentTop="true" - android:layout_toEndOf="@id/date" - android:drawablePadding="6dp" - android:drawableStart="@drawable/ic_access_alarms_small" - android:textColor="#64ffffff" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date" - android:minHeight="36dp" - android:paddingStart="6dp" - android:background="?android:attr/selectableItemBackground" - android:visibility="gone" - /> + android:orientation="horizontal"> + + <include layout="@layout/split_clock_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="2dp" + android:id="@+id/clock" /> + + <com.android.systemui.statusbar.policy.DateView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="6dp" + android:layout_marginTop="8dp" + android:layout_alignParentTop="true" + android:drawableStart="@drawable/header_dot" + android:drawablePadding="6dp" + android:singleLine="true" + android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" + android:textSize="@dimen/qs_time_collapsed_size" + systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" /> + + <com.android.systemui.statusbar.AlphaOptimizedButton + android:id="@+id/alarm_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:drawablePadding="6dp" + android:drawableStart="@drawable/ic_access_alarms_small" + android:textColor="#64ffffff" + android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date" + android:minHeight="36dp" + android:paddingStart="6dp" + android:background="?android:attr/selectableItemBackground" + android:visibility="gone" /> + </LinearLayout> <com.android.systemui.qs.QuickQSPanel android:id="@+id/quick_qs_panel" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 388da17301e8..40e8b5055591 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -103,7 +103,7 @@ <!-- The default tiles to display in QuickSettings --> <string name="quick_settings_tiles_default" translatable="false"> - wifi,bt,inversion,dnd,cell,airplane,rotation,flashlight,location,cast,hotspot + wifi,bt,flashlight,dnd,cell,battery,rotation,airplane,location,cast </string> <!-- The tiles to display in QuickSettings --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4e812aa8d76c..4f070d65c2dc 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -45,8 +45,11 @@ <!-- Height of a large notification in the status bar --> <dimen name="notification_max_height">276dp</dimen> - <!-- Height of a medium notification in the status bar --> - <dimen name="notification_mid_height">128dp</dimen> + <!-- Height of a heads up notification in the status bar for legacy custom views --> + <dimen name="notification_max_heads_up_height_legacy">128dp</dimen> + + <!-- Height of a heads up notification in the status bar --> + <dimen name="notification_max_heads_up_height">140dp</dimen> <!-- Height of a the summary ("more card") notification on keyguard. --> <dimen name="notification_summary_height">44dp</dimen> @@ -133,6 +136,7 @@ <dimen name="qs_quick_actions_padding">25dp</dimen> <dimen name="qs_quick_tile_size">48dp</dimen> <dimen name="qs_quick_tile_padding">12dp</dimen> + <dimen name="qs_date_anim_translation">44.5dp</dimen> <dimen name="qs_page_indicator_size">12dp</dimen> <dimen name="qs_tile_icon_size">24dp</dimen> <dimen name="qs_tile_text_size">12sp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java index 5b8d3d66e332..c9ba8858bf37 100644 --- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java @@ -19,6 +19,7 @@ package com.android.systemui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.content.Context; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; @@ -35,13 +36,19 @@ public class ViewInvertHelper { private final Paint mDarkPaint = new Paint(); private final Interpolator mLinearOutSlowInInterpolator; - private final ArrayList<View> mTargets; private final ColorMatrix mMatrix = new ColorMatrix(); private final ColorMatrix mGrayscaleMatrix = new ColorMatrix(); private final long mFadeDuration; + private final ArrayList<View> mTargets = new ArrayList<>(); - public ViewInvertHelper(View target, long fadeDuration) { - this(constructArray(target), fadeDuration); + public ViewInvertHelper(View v, long fadeDuration) { + this(v.getContext(), fadeDuration); + addTarget(v); + } + public ViewInvertHelper(Context context, long fadeDuration) { + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + android.R.interpolator.linear_out_slow_in); + mFadeDuration = fadeDuration; } private static ArrayList<View> constructArray(View target) { @@ -50,11 +57,12 @@ public class ViewInvertHelper { return views; } - public ViewInvertHelper(ArrayList<View> targets, long fadeDuration) { - mTargets = targets; - mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTargets.get(0).getContext(), - android.R.interpolator.linear_out_slow_in); - mFadeDuration = fadeDuration; + public void clearTargets() { + mTargets.clear(); + } + + public void addTarget(View target) { + mTargets.add(target); } public void fade(final boolean invert, long delay) { @@ -112,4 +120,12 @@ public class ViewInvertHelper { mMatrix.preConcat(mGrayscaleMatrix); mDarkPaint.setColorFilter(new ColorMatrixColorFilter(mMatrix)); } + + public void setInverted(boolean invert, boolean fade, long delay) { + if (fade) { + fade(invert, delay); + } else { + update(invert); + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 879624e9803e..b1847e1c1ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -127,8 +127,8 @@ public abstract class BaseStatusBar extends SystemUI implements public static final boolean ENABLE_REMOTE_INPUT = SystemProperties.getBoolean("debug.enable_remote_input", true); - public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE - && SystemProperties.getBoolean("debug.child_notifs", false); + public static final boolean ENABLE_CHILD_NOTIFICATIONS + = SystemProperties.getBoolean("debug.child_notifs", true); protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 5c79c7d922d4..2b93554b14be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -51,6 +51,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private static final int COLORED_DIVIDER_ALPHA = 0x7B; private final LinearInterpolator mLinearInterpolator = new LinearInterpolator(); private final int mNotificationMinHeightLegacy; + private final int mMaxHeadsUpHeightLegacy; + private final int mMaxHeadsUpHeight; private final int mNotificationMinHeight; private final int mNotificationMaxHeight; private int mRowMinHeight; @@ -95,6 +97,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private boolean mIsHeadsUp; private boolean mLastChronometerRunning = true; private NotificationHeaderView mNotificationHeader; + private NotificationViewWrapper mNotificationHeaderWrapper; private ViewStub mChildrenContainerStub; private NotificationGroupManager mGroupManager; private boolean mChildrenExpanded; @@ -218,10 +221,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; int minHeight = customView && beforeN && !mIsSummaryWithChildren ? mNotificationMinHeightLegacy : mNotificationMinHeight; + boolean headsUpCustom = getPrivateLayout().getHeadsUpChild() != null && + getPrivateLayout().getHeadsUpChild().getId() + != com.android.internal.R.id.status_bar_latest_event_content; + int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy + : mMaxHeadsUpHeight; mRowMinHeight = minHeight; mMaxViewHeight = mNotificationMaxHeight; - mPrivateLayout.setSmallHeight(mRowMinHeight); - mPublicLayout.setSmallHeight(mRowMinHeight); + mPrivateLayout.setHeights(mRowMinHeight, headsUpheight); + mPublicLayout.setHeights(mRowMinHeight, headsUpheight); } public StatusBarNotification getStatusBarNotification() { @@ -385,6 +393,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } public int getHeadsUpHeight() { + if (mIsSummaryWithChildren) { + return mChildrenContainer.getIntrinsicHeight(); + } return mHeadsUpHeight; } @@ -462,6 +473,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { R.dimen.notification_min_height); mNotificationMaxHeight = getResources().getDimensionPixelSize( R.dimen.notification_max_height); + mMaxHeadsUpHeightLegacy = getResources().getDimensionPixelSize( + R.dimen.notification_max_heads_up_height_legacy); + mMaxHeadsUpHeight = getResources().getDimensionPixelSize( + R.dimen.notification_max_heads_up_height); } /** @@ -570,6 +585,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (showing != null) { showing.setDark(dark, fade, delay); } + if (mIsSummaryWithChildren) { + mChildrenContainer.setDark(dark, fade, delay); + mNotificationHeaderWrapper.setDark(dark, fade, delay); + } } public boolean isExpandable() { @@ -967,9 +986,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { com.android.internal.R.id.expand_button); expandButton.setVisibility(VISIBLE); mNotificationHeader.setOnClickListener(mExpandClickListener); + mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(), + mNotificationHeader); addView(mNotificationHeader); } else { header.reapply(getContext(), mNotificationHeader); + mNotificationHeaderWrapper.notifyContentUpdated(); } updateHeaderExpandButton(); updateChildrenHeaderAppearance(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index da01d54796b6..2944c4f46d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -52,7 +52,6 @@ public class NotificationContentView extends FrameLayout { private static final int VISIBLE_TYPE_SINGLELINE = 3; private final Rect mClipBounds = new Rect(); - private final int mHeadsUpHeight; private final int mRoundRectRadius; private final Interpolator mLinearInterpolator = new LinearInterpolator(); private final boolean mRoundRectClippingEnabled; @@ -77,6 +76,7 @@ public class NotificationContentView extends FrameLayout { private boolean mShowingLegacyBackground; private boolean mIsChildInGroup; private int mSmallHeight; + private int mHeadsUpHeight; private StatusBarNotification mStatusBarNotification; private NotificationGroupManager mGroupManager; @@ -103,7 +103,6 @@ public class NotificationContentView extends FrameLayout { super(context, attrs); mHybridViewManager = new HybridNotificationViewManager(getContext(), this); mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); - mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height); mRoundRectRadius = getResources().getDimensionPixelSize( R.dimen.notification_material_rounded_rect_radius); mRoundRectClippingEnabled = getResources().getBoolean( @@ -112,8 +111,9 @@ public class NotificationContentView extends FrameLayout { setOutlineProvider(mOutlineProvider); } - public void setSmallHeight(int smallHeight) { + public void setHeights(int smallHeight, int headsUpMaxHeight) { mSmallHeight = smallHeight; + mHeadsUpHeight = headsUpMaxHeight; } @Override @@ -150,7 +150,7 @@ public class NotificationContentView extends FrameLayout { ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams(); if (layoutParams.height >= 0) { // An actual height is set - size = Math.min(maxSize, layoutParams.height); + size = Math.min(size, layoutParams.height); } mHeadsUpChild.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST)); @@ -283,10 +283,10 @@ public class NotificationContentView extends FrameLayout { } public int getMaxHeight() { - if (mIsHeadsUp && mHeadsUpChild != null) { - return mHeadsUpChild.getHeight(); - } else if (mExpandedChild != null) { + if (mExpandedChild != null) { return mExpandedChild.getHeight(); + } else if (mIsHeadsUp && mHeadsUpChild != null) { + return mHeadsUpChild.getHeight(); } return mSmallHeight; } @@ -457,6 +457,9 @@ public class NotificationContentView extends FrameLayout { if (mDark == dark || mContractedChild == null) return; mDark = dark; mContractedWrapper.setDark(dark && !mShowingLegacyBackground, fade, delay); + if (mSingleLineView != null) { + mSingleLineView.setDark(dark, fade, delay); + } } public void setHeadsUp(boolean headsUp) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java new file mode 100644 index 000000000000..ddad2e018c12 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.view.NotificationHeaderView; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.ImageView; + +import com.android.systemui.R; +import com.android.systemui.ViewInvertHelper; +import com.android.systemui.statusbar.phone.NotificationPanelView; + +import java.util.ArrayList; + +/** + * Wraps a notification header view. + */ +public class NotificationHeaderViewWrapper extends NotificationViewWrapper { + + private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); + private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter( + 0, PorterDuff.Mode.SRC_ATOP); + private final int mIconDarkAlpha; + private final int mIconDarkColor = 0xffffffff; + protected final Interpolator mLinearOutSlowInInterpolator; + protected final ViewInvertHelper mInvertHelper; + + protected int mColor; + private ImageView mIcon; + + private ImageView mExpandButton; + private NotificationHeaderView mNotificationHeader; + + protected NotificationHeaderViewWrapper(Context ctx, View view) { + super(view); + mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx, + android.R.interpolator.linear_out_slow_in); + mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION); + resolveHeaderViews(); + } + + protected void resolveHeaderViews() { + mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon); + mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button); + mColor = resolveColor(mExpandButton); + mNotificationHeader = (NotificationHeaderView) mView.findViewById( + com.android.internal.R.id.notification_header); + for (int i = 0; i < mNotificationHeader.getChildCount(); i++) { + View child = mNotificationHeader.getChildAt(i); + if (child != mIcon) { + mInvertHelper.addTarget(child); + } + } + } + + private int resolveColor(ImageView icon) { + if (icon != null && icon.getDrawable() != null) { + ColorFilter filter = icon.getDrawable().getColorFilter(); + if (filter instanceof PorterDuffColorFilter) { + return ((PorterDuffColorFilter) filter).getColor(); + } + } + return 0; + } + + @Override + public void notifyContentUpdated() { + mInvertHelper.clearTargets(); + // Reinspect the notification. + resolveHeaderViews(); + } + + @Override + public void setDark(boolean dark, boolean fade, long delay) { + if (fade) { + mInvertHelper.fade(dark, delay); + } else { + mInvertHelper.update(dark); + } + if (mIcon != null) { + boolean hadColorFilter = mNotificationHeader.getOriginalIconColor() + != NotificationHeaderView.NO_COLOR; + if (fade) { + if (hadColorFilter) { + fadeIconColorFilter(mIcon, dark, delay); + fadeIconAlpha(mIcon, dark, delay); + } else { + fadeGrayscale(mIcon, dark, delay); + } + } else { + if (hadColorFilter) { + updateIconColorFilter(mIcon, dark); + updateIconAlpha(mIcon, dark); + } else { + updateGrayscale(mIcon, dark); + } + } + } + } + + protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, + boolean dark, long delay, Animator.AnimatorListener listener) { + float startIntensity = dark ? 0f : 1f; + float endIntensity = dark ? 1f : 0f; + ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); + animator.addUpdateListener(updateListener); + animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); + animator.setInterpolator(mLinearOutSlowInInterpolator); + animator.setStartDelay(delay); + if (listener != null) { + animator.addListener(listener); + } + animator.start(); + } + + private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateIconColorFilter(target, (Float) animation.getAnimatedValue()); + } + }, dark, delay, null /* listener */); + } + + private void fadeIconAlpha(final ImageView target, boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = (float) animation.getAnimatedValue(); + target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t)); + } + }, dark, delay, null /* listener */); + } + + protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateGrayscaleMatrix((float) animation.getAnimatedValue()); + target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + } + }, dark, delay, new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!dark) { + target.setColorFilter(null); + } + } + }); + } + + private void updateIconColorFilter(ImageView target, boolean dark) { + updateIconColorFilter(target, dark ? 1f : 0f); + } + + private void updateIconColorFilter(ImageView target, float intensity) { + int color = interpolateColor(mColor, mIconDarkColor, intensity); + mIconColorFilter.setColor(color); + Drawable iconDrawable = target.getDrawable(); + + // Also, the notification might have been modified during the animation, so background + // might be null here. + if (iconDrawable != null) { + iconDrawable.mutate().setColorFilter(mIconColorFilter); + } + } + + private void updateIconAlpha(ImageView target, boolean dark) { + target.setImageAlpha(dark ? mIconDarkAlpha : 255); + } + + protected void updateGrayscale(ImageView target, boolean dark) { + if (dark) { + updateGrayscaleMatrix(1f); + target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + } else { + target.setColorFilter(null); + } + } + + @Override + public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) { + mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); + mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); + } + + private void updateGrayscaleMatrix(float intensity) { + mGrayscaleColorMatrix.setSaturation(1 - intensity); + } + + private static int interpolateColor(int source, int target, float t) { + int aSource = Color.alpha(source); + int rSource = Color.red(source); + int gSource = Color.green(source); + int bSource = Color.blue(source); + int aTarget = Color.alpha(target); + int rTarget = Color.red(target); + int gTarget = Color.green(target); + int bTarget = Color.blue(target); + return Color.argb( + (int) (aSource * (1f - t) + aTarget * t), + (int) (rSource * (1f - t) + rTarget * t), + (int) (gSource * (1f - t) + gTarget * t), + (int) (bSource * (1f - t) + bTarget * t)); + } + + @Override + public NotificationHeaderView getNotificationHeader() { + return mNotificationHeader; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java index fb0a419ad37c..77e8c55a2d0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java @@ -16,74 +16,31 @@ package com.android.systemui.statusbar; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; -import android.content.res.ColorStateList; import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.view.MotionEvent; -import android.view.NotificationHeaderView; import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.ImageView; import android.widget.ProgressBar; -import android.widget.TextView; - -import com.android.systemui.R; -import com.android.systemui.ViewInvertHelper; -import com.android.systemui.statusbar.phone.NotificationPanelView; - -import java.util.ArrayList; /** * Wraps a notification view inflated from a template. */ -public class NotificationTemplateViewWrapper extends NotificationViewWrapper { +public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper { - private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); - private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter( - 0, PorterDuff.Mode.SRC_ATOP); - private final int mIconDarkAlpha; - private final int mIconDarkColor = 0xffffffff; - private final int mDarkProgressTint = 0xffffffff; - private final Interpolator mLinearOutSlowInInterpolator; + private static final int mDarkProgressTint = 0xffffffff; - private int mColor; - private ViewInvertHelper mInvertHelper; - private ImageView mIcon; protected ImageView mPicture; - - private ImageView mExpandButton; - private NotificationHeaderView mNotificationHeader; private ProgressBar mProgressBar; protected NotificationTemplateViewWrapper(Context ctx, View view) { - super(view); - mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); - mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx, - android.R.interpolator.linear_out_slow_in); - - resolveViews(); + super(ctx, view); + resolveTemplateViews(); } - private void resolveViews() { + private void resolveTemplateViews() { View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column); - mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon); mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon); - mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button); - mColor = resolveColor(mExpandButton); final View progress = mView.findViewById(com.android.internal.R.id.progress); if (progress instanceof ProgressBar) { mProgressBar = (ProgressBar) progress; @@ -91,30 +48,9 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper { // It's still a viewstub mProgressBar = null; } - mNotificationHeader = (NotificationHeaderView) mView.findViewById( - com.android.internal.R.id.notification_header); - ArrayList<View> viewsToInvert = new ArrayList<>(); if (mainColumn != null) { - viewsToInvert.add(mainColumn); - } - for (int i = 0; i < mNotificationHeader.getChildCount(); i++) { - View child = mNotificationHeader.getChildAt(i); - if (child != mIcon) { - viewsToInvert.add(child); - } - } - mInvertHelper = new ViewInvertHelper(viewsToInvert, - NotificationPanelView.DOZE_ANIMATION_DURATION); - } - - private int resolveColor(ImageView icon) { - if (icon != null && icon.getDrawable() != null) { - ColorFilter filter = icon.getDrawable().getColorFilter(); - if (filter instanceof PorterDuffColorFilter) { - return ((PorterDuffColorFilter) filter).getColor(); - } + mInvertHelper.addTarget(mainColumn); } - return 0; } @Override @@ -122,37 +58,12 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper { super.notifyContentUpdated(); // Reinspect the notification. - resolveViews(); + resolveTemplateViews(); } @Override public void setDark(boolean dark, boolean fade, long delay) { - if (mInvertHelper != null) { - if (fade) { - mInvertHelper.fade(dark, delay); - } else { - mInvertHelper.update(dark); - } - } - if (mIcon != null) { - boolean hadColorFilter = mNotificationHeader.getOriginalIconColor() - != NotificationHeaderView.NO_COLOR; - if (fade) { - if (hadColorFilter) { - fadeIconColorFilter(mIcon, dark, delay); - fadeIconAlpha(mIcon, dark, delay); - } else { - fadeGrayscale(mIcon, dark, delay); - } - } else { - if (hadColorFilter) { - updateIconColorFilter(mIcon, dark); - updateIconAlpha(mIcon, dark); - } else { - updateGrayscale(mIcon, dark); - } - } - } + super.setDark(dark, fade, delay); setPictureGrayscale(dark, fade, delay); setProgressBarDark(dark, fade, delay); } @@ -197,96 +108,6 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper { } } - private void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, - boolean dark, long delay, Animator.AnimatorListener listener) { - float startIntensity = dark ? 0f : 1f; - float endIntensity = dark ? 1f : 0f; - ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); - animator.addUpdateListener(updateListener); - animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); - animator.setInterpolator(mLinearOutSlowInInterpolator); - animator.setStartDelay(delay); - if (listener != null) { - animator.addListener(listener); - } - animator.start(); - } - - private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateIconColorFilter(target, (Float) animation.getAnimatedValue()); - } - }, dark, delay, null /* listener */); - } - - private void fadeIconAlpha(final ImageView target, boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (float) animation.getAnimatedValue(); - target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t)); - } - }, dark, delay, null /* listener */); - } - - protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) { - startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateGrayscaleMatrix((float) animation.getAnimatedValue()); - target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); - } - }, dark, delay, new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (!dark) { - target.setColorFilter(null); - } - } - }); - } - - private void updateIconColorFilter(ImageView target, boolean dark) { - updateIconColorFilter(target, dark ? 1f : 0f); - } - - private void updateIconColorFilter(ImageView target, float intensity) { - int color = interpolateColor(mColor, mIconDarkColor, intensity); - mIconColorFilter.setColor(color); - Drawable iconDrawable = target.getDrawable(); - - // Also, the notification might have been modified during the animation, so background - // might be null here. - if (iconDrawable != null) { - iconDrawable.mutate().setColorFilter(mIconColorFilter); - } - } - - private void updateIconAlpha(ImageView target, boolean dark) { - target.setImageAlpha(dark ? mIconDarkAlpha : 255); - } - - protected void updateGrayscale(ImageView target, boolean dark) { - if (dark) { - updateGrayscaleMatrix(1f); - target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); - } else { - target.setColorFilter(null); - } - } - - @Override - public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) { - mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE); - mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); - } - - private void updateGrayscaleMatrix(float intensity) { - mGrayscaleColorMatrix.setSaturation(1 - intensity); - } - private static int interpolateColor(int source, int target, float t) { int aSource = Color.alpha(source); int rSource = Color.red(source); @@ -302,9 +123,4 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper { (int) (gSource * (1f - t) + gTarget * t), (int) (bSource * (1f - t) + bTarget * t)); } - - @Override - public NotificationHeaderView getNotificationHeader() { - return mNotificationHeader; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java index 119d57ba7506..61499de40bbf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java @@ -26,16 +26,13 @@ import android.view.View; */ public abstract class NotificationViewWrapper { - private static final String TAG_BIG_MEDIA_NARROW = "bigMediaNarrow"; - private static final String TAG_MEDIA = "media"; - private static final String TAG_BIG_PICTURE = "bigPicture"; - protected final View mView; - private boolean mSubTextVisible = true; public static NotificationViewWrapper wrap(Context ctx, View v) { if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) { return new NotificationTemplateViewWrapper(ctx, v); + } else if (v instanceof NotificationHeaderView) { + return new NotificationHeaderViewWrapper(ctx, v); } else { return new NotificationCustomViewWrapper(v); } @@ -57,9 +54,7 @@ public abstract class NotificationViewWrapper { /** * Notifies this wrapper that the content of the view might have changed. */ - public void notifyContentUpdated() { - setSubTextVisible(mSubTextVisible); - } + public void notifyContentUpdated() {}; /** * @return true if this template might need to be clipped with a round rect to make it look @@ -70,14 +65,6 @@ public abstract class NotificationViewWrapper { } /** - * Change the subTextVisibility - * @param visible Should the subtext be visible - */ - public void setSubTextVisible(boolean visible) { - mSubTextVisible = visible; - } - - /** * Update the appearance of the expand button. * * @param expandable should this view be expandable diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java index fafea9856db2..5fb6fecc2357 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java @@ -23,6 +23,8 @@ import android.widget.TextView; import com.android.keyguard.AlphaOptimizedLinearLayout; import com.android.systemui.R; +import com.android.systemui.ViewInvertHelper; +import com.android.systemui.statusbar.phone.NotificationPanelView; /** * A hybrid view which may contain information about one ore more notifications. @@ -31,6 +33,7 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout { protected TextView mTitleView; protected TextView mTextView; + private ViewInvertHelper mInvertHelper; public HybridNotificationView(Context context) { this(context, null); @@ -54,6 +57,7 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout { super.onFinishInflate(); mTitleView = (TextView) findViewById(R.id.notification_title); mTextView = (TextView) findViewById(R.id.notification_text); + mInvertHelper = new ViewInvertHelper(this, NotificationPanelView.DOZE_ANIMATION_DURATION); } public void bind(CharSequence title) { @@ -65,4 +69,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout { mTextView.setText(text); requestLayout(); } + + public void setDark(boolean dark, boolean fade, long delay) { + mInvertHelper.setInverted(dark, fade, delay); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java index c4930a9d1750..1372ccaa824e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java @@ -62,11 +62,15 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements private boolean mDetailTransitioning; private ViewGroup mExpandedGroup; + private ViewGroup mDateTimeGroup; + private View mEmergencyOnly; private TextView mQsDetailHeaderTitle; private boolean mListening; private AlarmManager.AlarmClockInfo mNextAlarm; private QuickQSPanel mHeaderQsPanel; + private boolean mShowEmergencyCallsOnly; + private float mDateTimeTranslation; public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); @@ -76,6 +80,10 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements protected void onFinishInflate() { super.onFinishInflate(); + mEmergencyOnly = findViewById(R.id.header_emergency_calls_only); + mDateTimeTranslation = mContext.getResources().getDimension( + R.dimen.qs_date_anim_translation); + mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group); mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group); mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel); @@ -141,6 +149,9 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE); mHeaderQsPanel.setAlpha(1 - headerExpansionFraction); mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE); + + mDateTimeGroup.setTranslationY(headerExpansionFraction * mDateTimeTranslation); + mEmergencyOnly.setAlpha(headerExpansionFraction); } public void setListening(boolean listening) { @@ -160,6 +171,8 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements private void updateVisibilities() { mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE); mQsDetailHeader.setVisibility(mExpanded && mShowingDetail ? View.VISIBLE : View.INVISIBLE); + mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly + ? View.VISIBLE : View.INVISIBLE); mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE); } @@ -256,8 +269,14 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements } @Override - public void setEmergencyCallsOnly(boolean emergencyOnly) { - // Don't care. + public void setEmergencyCallsOnly(boolean show) { + boolean changed = show != mShowEmergencyCallsOnly; + if (changed) { + mShowEmergencyCallsOnly = show; + if (mExpanded) { + updateEverything(); + } + } } private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() { @@ -314,6 +333,7 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements private void handleShowingDetail(final QSTile.DetailAdapter detail) { final boolean showingDetail = detail != null; + transition(mDateTimeGroup, !showingDetail); transition(mExpandedGroup, !showingDetail); if (mAlarmShowing) { transition(mAlarmStatus, !showingDetail); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index 9015a0e786f3..2b71ce936ab5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -23,9 +23,11 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; +import com.android.systemui.ViewInvertHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.HybridNotificationViewManager; +import com.android.systemui.statusbar.phone.NotificationPanelView; import java.util.ArrayList; import java.util.List; @@ -49,6 +51,7 @@ public class NotificationChildrenContainer extends ViewGroup { private final int mNotificatonTopPadding; private final HybridNotificationViewManager mHybridViewManager; private final float mCollapsedBottompadding; + private ViewInvertHelper mOverflowInvertHelper; private boolean mChildrenExpanded; private ExpandableNotificationRow mNotificationParent; private HybridNotificationView mGroupOverflowContainer; @@ -172,12 +175,17 @@ public class NotificationChildrenContainer extends ViewGroup { if (hasOverflow) { mGroupOverflowContainer = mHybridViewManager.bindFromNotificationGroup( mGroupOverflowContainer, mChildren, lastVisibleIndex + 1); + if (mOverflowInvertHelper == null) { + mOverflowInvertHelper= new ViewInvertHelper(mGroupOverflowContainer, + NotificationPanelView.DOZE_ANIMATION_DURATION); + } if (mGroupOverFlowState == null) { mGroupOverFlowState = new ViewState(); } } else if (mGroupOverflowContainer != null) { removeView(mGroupOverflowContainer); mGroupOverflowContainer = null; + mOverflowInvertHelper = null; mGroupOverFlowState = null; } } @@ -324,7 +332,7 @@ public class NotificationChildrenContainer extends ViewGroup { if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) { return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; } - if (mNotificationParent.isExpanded()) { + if (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp()) { return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED; } return NUMBER_OF_CHILDREN_WHEN_COLLAPSED; @@ -387,16 +395,17 @@ public class NotificationChildrenContainer extends ViewGroup { boolean withDelays, long baseDelay, long duration) { int childCount = mChildren.size(); ViewState tmpState = new ViewState(); - int notGoneIndex = 0; - for (int i = 0; i < childCount; i++) { + int delayIndex = 0; + int maxAllowChildCount = getMaxAllowedVisibleChildren(true /* likeCollapsed */); + for (int i = childCount - 1; i >= 0; i--) { ExpandableNotificationRow child = mChildren.get(i); StackViewState viewState = state.getViewStateForView(child); int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN, - notGoneIndex + 1); + delayIndex); long delay = withDelays ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN : 0; - delay += baseDelay; + delay = (long) (delay * (mChildrenExpanded ? 1.0f : 0.5f) + baseDelay); stateAnimator.startStackAnimations(child, viewState, state, -1, delay); // layout the divider @@ -405,11 +414,13 @@ public class NotificationChildrenContainer extends ViewGroup { tmpState.yTranslation = viewState.yTranslation - mDividerHeight; tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0; stateAnimator.startViewAnimations(divider, tmpState, delay, duration); - - notGoneIndex++; + if (i < maxAllowChildCount) { + delayIndex++; + } } if (mGroupOverflowContainer != null) { - stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState, -1, 0); + stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState, + baseDelay, duration); } } @@ -443,4 +454,10 @@ public class NotificationChildrenContainer extends ViewGroup { public int getMinHeight() { return getIntrinsicHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */)); } + + public void setDark(boolean dark, boolean fade, long delay) { + if (mGroupOverflowContainer != null) { + mOverflowInvertHelper.setInverted(dark, fade, delay); + } + } } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 960fb4bcbd07..5f57a7632db9 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -1233,14 +1233,6 @@ class AlarmManagerService extends SystemService { } } - // direct-callback alarms must be wakeup alarms (otherwise they should just be - // posting work to a Handler) - if (directReceiver != null) { - if (type != RTC_WAKEUP && type != ELAPSED_REALTIME_WAKEUP) { - throw new IllegalArgumentException("Only wakeup alarms can use AlarmReceivers"); - } - } - if (workSource != null) { getContext().enforcePermission( android.Manifest.permission.UPDATE_DEVICE_STATS, diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index ede92fb76f30..7fcedc6fa14f 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -788,6 +788,9 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) { + if (callback == null) { + return; + } synchronized (this) { op = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op; Callback cb = mModeWatchers.get(callback.asBinder()); @@ -816,6 +819,9 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void stopWatchingMode(IAppOpsCallback callback) { + if (callback == null) { + return; + } synchronized (this) { Callback cb = mModeWatchers.remove(callback.asBinder()); if (cb != null) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c712a568c480..ed64c2b37a4a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -34,6 +34,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import android.annotation.Nullable; import android.app.AlarmManager; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -72,6 +73,7 @@ import android.net.RouteInfo; import android.net.UidRange; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.FileUtils; import android.os.Handler; @@ -1529,6 +1531,7 @@ public class ConnectivityService extends IConnectivityManager.Stub log("sendStickyBroadcast: action=" + intent.getAction()); } + Bundle options = null; final long ident = Binder.clearCallingIdentity(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { final NetworkInfo ni = intent.getParcelableExtra( @@ -1536,6 +1539,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (ni.getType() == ConnectivityManager.TYPE_MOBILE_SUPL) { intent.setAction(ConnectivityManager.CONNECTIVITY_ACTION_SUPL); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } else { + BroadcastOptions opts = BroadcastOptions.makeBasic(); + opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M); + options = opts.toBundle(); } final IBatteryStats bs = BatteryStatsService.getService(); try { @@ -1546,7 +1553,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } try { - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL, options); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 45c1ed224d49..0282a72784c8 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -3536,6 +3536,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final String ATTR_LABEL = "label"; private static final String ATTR_ICON = "icon"; private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale"; + private static final String ATTR_IME_SUBTYPE_LANGUAGE_TAG = "languageTag"; private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode"; private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue"; private static final String ATTR_IS_AUXILIARY = "isAuxiliary"; @@ -3629,6 +3630,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId())); out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId())); out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale()); + out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG, + subtype.getLanguageTag()); out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode()); out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue()); out.attribute(null, ATTR_IS_AUXILIARY, @@ -3690,6 +3693,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub parser.getAttributeValue(null, ATTR_LABEL)); final String imeSubtypeLocale = parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE); + final String languageTag = + parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG); final String imeSubtypeMode = parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE); final String imeSubtypeExtraValue = @@ -3700,6 +3705,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub .setSubtypeNameResId(label) .setSubtypeIconResId(icon) .setSubtypeLocale(imeSubtypeLocale) + .setLanguageTag(languageTag) .setSubtypeMode(imeSubtypeMode) .setSubtypeExtraValue(imeSubtypeExtraValue) .setIsAuxiliary(isAuxiliary) diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 1bab7b99ec62..622aa16cb852 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -468,7 +468,7 @@ public final class BroadcastQueue { } private void deliverToRegisteredReceiverLocked(BroadcastRecord r, - BroadcastFilter filter, boolean ordered) { + BroadcastFilter filter, boolean ordered, int index) { boolean skip = false; if (filter.requiredPermission != null) { int perm = mService.checkComponentPermission(filter.requiredPermission, @@ -576,64 +576,70 @@ public final class BroadcastQueue { if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, r.callingPid, r.resolvedType, filter.receiverList.uid)) { - return; + skip = true; } - if (filter.receiverList.app == null || filter.receiverList.app.crashing) { + if (!skip && (filter.receiverList.app == null || filter.receiverList.app.crashing)) { Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r + " to " + filter.receiverList + ": process crashing"); skip = true; } - if (!skip) { - // If permissions need a review before any of the app components can run, we drop - // the broadcast and if the calling app is in the foreground and the broadcast is - // explicit we launch the review UI passing it a pending intent to send the skipped - // broadcast. - if (Build.PERMISSIONS_REVIEW_REQUIRED) { - if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName, - filter.owningUserId)) { - return; - } + if (skip) { + r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED; + return; + } + + // If permissions need a review before any of the app components can run, we drop + // the broadcast and if the calling app is in the foreground and the broadcast is + // explicit we launch the review UI passing it a pending intent to send the skipped + // broadcast. + if (Build.PERMISSIONS_REVIEW_REQUIRED) { + if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName, + filter.owningUserId)) { + r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED; + return; } + } - // If this is not being sent as an ordered broadcast, then we - // don't want to touch the fields that keep track of the current - // state of ordered broadcasts. + r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED; + + // If this is not being sent as an ordered broadcast, then we + // don't want to touch the fields that keep track of the current + // state of ordered broadcasts. + if (ordered) { + r.receiver = filter.receiverList.receiver.asBinder(); + r.curFilter = filter; + filter.receiverList.curBroadcast = r; + r.state = BroadcastRecord.CALL_IN_RECEIVE; + if (filter.receiverList.app != null) { + // Bump hosting application to no longer be in background + // scheduling class. Note that we can't do that if there + // isn't an app... but we can only be in that case for + // things that directly call the IActivityManager API, which + // are already core system stuff so don't matter for this. + r.curApp = filter.receiverList.app; + filter.receiverList.app.curReceiver = r; + mService.updateOomAdjLocked(r.curApp); + } + } + try { + if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST, + "Delivering to " + filter + " : " + r); + performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, + new Intent(r.intent), r.resultCode, r.resultData, + r.resultExtras, r.ordered, r.initialSticky, r.userId); if (ordered) { - r.receiver = filter.receiverList.receiver.asBinder(); - r.curFilter = filter; - filter.receiverList.curBroadcast = r; - r.state = BroadcastRecord.CALL_IN_RECEIVE; - if (filter.receiverList.app != null) { - // Bump hosting application to no longer be in background - // scheduling class. Note that we can't do that if there - // isn't an app... but we can only be in that case for - // things that directly call the IActivityManager API, which - // are already core system stuff so don't matter for this. - r.curApp = filter.receiverList.app; - filter.receiverList.app.curReceiver = r; - mService.updateOomAdjLocked(r.curApp); - } + r.state = BroadcastRecord.CALL_DONE_RECEIVE; } - try { - if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST, - "Delivering to " + filter + " : " + r); - performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, - new Intent(r.intent), r.resultCode, r.resultData, - r.resultExtras, r.ordered, r.initialSticky, r.userId); - if (ordered) { - r.state = BroadcastRecord.CALL_DONE_RECEIVE; - } - } catch (RemoteException e) { - Slog.w(TAG, "Failure sending broadcast " + r.intent, e); - if (ordered) { - r.receiver = null; - r.curFilter = null; - filter.receiverList.curBroadcast = null; - if (filter.receiverList.app != null) { - filter.receiverList.app.curReceiver = null; - } + } catch (RemoteException e) { + Slog.w(TAG, "Failure sending broadcast " + r.intent, e); + if (ordered) { + r.receiver = null; + r.curFilter = null; + filter.receiverList.curBroadcast = null; + if (filter.receiverList.app != null) { + filter.receiverList.app.curReceiver = null; } } } @@ -740,7 +746,7 @@ public final class BroadcastQueue { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Delivering non-ordered on [" + mQueueName + "] to registered " + target + ": " + r); - deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); + deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i); } addBroadcastToHistoryLocked(r); if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast [" @@ -897,7 +903,7 @@ public final class BroadcastQueue { "Delivering ordered [" + mQueueName + "] to registered " + filter + ": " + r); - deliverToRegisteredReceiverLocked(r, filter, r.ordered); + deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx); if (r.receiver == null || !r.ordered) { // The receiver has already finished, so schedule to // process the next one. @@ -925,10 +931,17 @@ public final class BroadcastQueue { info.activityInfo.name); boolean skip = false; + if (brOptions != null && + (info.activityInfo.applicationInfo.targetSdkVersion + < brOptions.getMinManifestReceiverApiLevel() || + info.activityInfo.applicationInfo.targetSdkVersion + > brOptions.getMaxManifestReceiverApiLevel())) { + skip = true; + } int perm = mService.checkComponentPermission(info.activityInfo.permission, r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, info.activityInfo.exported); - if (perm != PackageManager.PERMISSION_GRANTED) { + if (!skip && perm != PackageManager.PERMISSION_GRANTED) { if (!info.activityInfo.exported) { Slog.w(TAG, "Permission Denial: broadcasting " + r.intent.toString() @@ -945,7 +958,7 @@ public final class BroadcastQueue { + " due to receiver " + component.flattenToShortString()); } skip = true; - } else if (info.activityInfo.permission != null) { + } else if (!skip && info.activityInfo.permission != null) { final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission); if (opCode != AppOpsManager.OP_NONE && mService.mAppOpsService.noteOperation(opCode, r.callingUid, @@ -1118,6 +1131,7 @@ public final class BroadcastQueue { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Skipping delivery of ordered [" + mQueueName + "] " + r + " for whatever reason"); + r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED; r.receiver = null; r.curFilter = null; r.state = BroadcastRecord.IDLE; @@ -1125,6 +1139,7 @@ public final class BroadcastQueue { return; } + r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED; r.state = BroadcastRecord.APP_RECEIVE; r.curComponent = component; r.curReceiver = info.activityInfo; @@ -1294,6 +1309,7 @@ public final class BroadcastQueue { String anrMessage = null; Object curReceiver = r.receivers.get(r.nextReceiver-1); + r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT; Slog.w(TAG, "Receiver during timeout: " + curReceiver); logBroadcastReceiverDiscardLocked(r); if (curReceiver instanceof BroadcastFilter) { diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 1a269cf840f7..e99cbf9e563f 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -57,6 +57,7 @@ final class BroadcastRecord extends Binder { final int appOp; // an app op that is associated with this broadcast final BroadcastOptions options; // BroadcastOptions supplied by caller final List receivers; // contains BroadcastFilter and ResolveInfo + final int[] delivery; // delivery state of each receiver IIntentReceiver resultTo; // who receives final result if non-null long enqueueClockTime; // the clock time the broadcast was enqueued long dispatchTime; // when dispatch started on this set of receivers @@ -79,6 +80,11 @@ final class BroadcastRecord extends Binder { static final int CALL_DONE_RECEIVE = 3; static final int WAITING_SERVICES = 4; + static final int DELIVERY_PENDING = 0; + static final int DELIVERY_DELIVERED = 1; + static final int DELIVERY_SKIPPED = 2; + static final int DELIVERY_TIMEOUT = 3; + // The following are set when we are calling a receiver (one that // was found in our list of registered receivers). BroadcastFilter curFilter; @@ -183,12 +189,24 @@ final class BroadcastRecord extends Binder { PrintWriterPrinter printer = new PrintWriterPrinter(pw); for (int i = 0; i < N; i++) { Object o = receivers.get(i); - pw.print(prefix); pw.print("Receiver #"); pw.print(i); - pw.print(": "); pw.println(o); - if (o instanceof BroadcastFilter) - ((BroadcastFilter)o).dumpBrief(pw, p2); - else if (o instanceof ResolveInfo) - ((ResolveInfo)o).dump(printer, p2, 0); + pw.print(prefix); + switch (delivery[i]) { + case DELIVERY_PENDING: pw.print("Pending"); break; + case DELIVERY_DELIVERED: pw.print("Deliver"); break; + case DELIVERY_SKIPPED: pw.print("Skipped"); break; + case DELIVERY_TIMEOUT: pw.print("Timeout"); break; + default: pw.print("???????"); break; + } + pw.print(" #"); pw.print(i); pw.print(": "); + if (o instanceof BroadcastFilter) { + pw.println(o); + ((BroadcastFilter) o).dumpBrief(pw, p2); + } else if (o instanceof ResolveInfo) { + pw.println("(manifest)"); + ((ResolveInfo) o).dump(printer, p2, 0); + } else { + pw.println(o); + } } } @@ -211,6 +229,7 @@ final class BroadcastRecord extends Binder { appOp = _appOp; options = _options; receivers = _receivers; + delivery = new int[_receivers != null ? _receivers.size() : 0]; resultTo = _resultTo; resultCode = _resultCode; resultData = _resultData; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 33f39bc8adc0..c83012c0d2d6 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -159,8 +159,8 @@ import java.util.Objects; public class NotificationManagerService extends SystemService { static final String TAG = "NotificationService"; static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); - public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE - && SystemProperties.getBoolean("debug.child_notifs", false); + public static final boolean ENABLE_CHILD_NOTIFICATIONS + = SystemProperties.getBoolean("debug.child_notifs", true); static final int MAX_PACKAGE_NOTIFICATIONS = 50; diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 812d9b625a8f..2329b4251fc7 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -389,7 +389,6 @@ public class DhcpClient extends BaseDhcpStateMachine { } private void scheduleRenew() { - mRenewAlarm.cancel(); if (mDhcpLeaseExpiry != 0) { long now = SystemClock.elapsedRealtime(); long alarmTime = (now + mDhcpLeaseExpiry) / 2; @@ -822,6 +821,11 @@ public class DhcpClient extends BaseDhcpStateMachine { return NOT_HANDLED; } } + + @Override + public void exit() { + mRenewAlarm.cancel(); + } } class DhcpRenewingState extends PacketRetransmittingState { diff --git a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java index 757f1c6ea39d..13657ab7f02d 100644 --- a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java +++ b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; +import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; @@ -165,6 +166,11 @@ public class BroadcastInterceptingContext extends ContextWrapper { } @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + sendBroadcast(intent); + } + + @Override public void removeStickyBroadcast(Intent intent) { // ignored } diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 96c818571df0..64d29787f75f 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -433,6 +433,12 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + throw new UnsupportedOperationException(); + } + @Override public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index b09a14fd1b39..63411b070d58 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1634,6 +1634,11 @@ public final class BridgeContext extends Context { } @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + // pass + } + + @Override public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, |