diff options
172 files changed, 4755 insertions, 1072 deletions
diff --git a/api/current.txt b/api/current.txt index b6f60f9f5639..5dfb0053983e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5031,13 +5031,13 @@ package android.app { method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder); method public java.lang.CharSequence getCancelLabel(); method public java.lang.CharSequence getConfirmLabel(); - method public boolean getHintContentIntentLaunchesActivity(); + method public boolean getHintLaunchesActivity(); method public java.lang.CharSequence getInProgressLabel(); method public boolean isAvailableOffline(); method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean); method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence); method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence); - method public android.app.Notification.Action.WearableExtender setHintContentIntentLaunchesActivity(boolean); + method public android.app.Notification.Action.WearableExtender setHintLaunchesActivity(boolean); method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence); } @@ -29583,6 +29583,7 @@ package android.os.storage { public class StorageManager { method public java.lang.String getMountedObbPath(java.lang.String); method public android.os.storage.StorageVolume getPrimaryStorageVolume(); + method public android.os.storage.StorageVolume getStorageVolume(java.io.File); method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes(); method public boolean isEncrypted(java.io.File); method public boolean isObbMounted(java.lang.String); @@ -49060,6 +49061,7 @@ package java.io { ctor public BufferedReader(java.io.Reader, int); ctor public BufferedReader(java.io.Reader); method public void close() throws java.io.IOException; + method public java.util.stream.Stream<java.lang.String> lines(); method public int read(char[], int, int) throws java.io.IOException; method public java.lang.String readLine() throws java.io.IOException; } @@ -49930,6 +49932,11 @@ package java.io { ctor public UTFDataFormatException(java.lang.String); } + public class UncheckedIOException extends java.lang.RuntimeException { + ctor public UncheckedIOException(java.lang.String, java.io.IOException); + ctor public UncheckedIOException(java.io.IOException); + } + public class UnsupportedEncodingException extends java.io.IOException { ctor public UnsupportedEncodingException(); ctor public UnsupportedEncodingException(java.lang.String); @@ -57586,6 +57593,7 @@ package java.util { method public void set(int, int); method public void set(int, int, boolean); method public int size(); + method public java.util.stream.IntStream stream(); method public byte[] toByteArray(); method public long[] toLongArray(); method public static java.util.BitSet valueOf(long[]); @@ -58041,6 +58049,7 @@ package java.util { method public java.util.Set<java.util.Map.Entry<K, V>> entrySet(); method public void forEach(java.util.function.BiConsumer<? super K, ? super V>); method public boolean replace(K, V, V); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); } public class HashSet extends java.util.AbstractSet implements java.lang.Cloneable java.io.Serializable java.util.Set { @@ -58084,6 +58093,7 @@ package java.util { method public synchronized boolean remove(java.lang.Object, java.lang.Object); method public synchronized boolean replace(K, V, V); method public synchronized V replace(K, V); + method public synchronized void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); method public synchronized int size(); method public java.util.Collection<V> values(); } @@ -58624,6 +58634,18 @@ package java.util { public class Random implements java.io.Serializable { ctor public Random(); ctor public Random(long); + method public java.util.stream.DoubleStream doubles(long); + method public java.util.stream.DoubleStream doubles(); + method public java.util.stream.DoubleStream doubles(long, double, double); + method public java.util.stream.DoubleStream doubles(double, double); + method public java.util.stream.IntStream ints(long); + method public java.util.stream.IntStream ints(); + method public java.util.stream.IntStream ints(long, int, int); + method public java.util.stream.IntStream ints(int, int); + method public java.util.stream.LongStream longs(long); + method public java.util.stream.LongStream longs(); + method public java.util.stream.LongStream longs(long, long, long); + method public java.util.stream.LongStream longs(long, long); method protected int next(int); method public boolean nextBoolean(); method public void nextBytes(byte[]); @@ -59065,6 +59087,7 @@ package java.util { method public java.util.NavigableSet<K> navigableKeySet(); method public java.util.Map.Entry<K, V> pollFirstEntry(); method public java.util.Map.Entry<K, V> pollLastEntry(); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); method public java.util.NavigableMap<K, V> subMap(K, boolean, K, boolean); method public java.util.SortedMap<K, V> subMap(K, K); method public java.util.NavigableMap<K, V> tailMap(K, boolean); @@ -59166,6 +59189,7 @@ package java.util { ctor public WeakHashMap(java.util.Map<? extends K, ? extends V>); method public java.util.Set<java.util.Map.Entry<K, V>> entrySet(); method public void forEach(java.util.function.BiConsumer<? super K, ? super V>); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); } } @@ -60139,18 +60163,6 @@ package java.util.concurrent { public class ThreadLocalRandom extends java.util.Random { method public static java.util.concurrent.ThreadLocalRandom current(); - method public java.util.stream.DoubleStream doubles(long); - method public java.util.stream.DoubleStream doubles(); - method public java.util.stream.DoubleStream doubles(long, double, double); - method public java.util.stream.DoubleStream doubles(double, double); - method public java.util.stream.IntStream ints(long); - method public java.util.stream.IntStream ints(); - method public java.util.stream.IntStream ints(long, int, int); - method public java.util.stream.IntStream ints(int, int); - method public java.util.stream.LongStream longs(long); - method public java.util.stream.LongStream longs(); - method public java.util.stream.LongStream longs(long, long, long); - method public java.util.stream.LongStream longs(long, long); method public double nextDouble(double); method public double nextDouble(double, double); method public int nextInt(int, int); @@ -61523,6 +61535,7 @@ package java.util.regex { } public final class Pattern implements java.io.Serializable { + method public java.util.function.Predicate<java.lang.String> asPredicate(); method public static java.util.regex.Pattern compile(java.lang.String); method public static java.util.regex.Pattern compile(java.lang.String, int) throws java.util.regex.PatternSyntaxException; method public int flags(); @@ -61532,6 +61545,7 @@ package java.util.regex { method public static java.lang.String quote(java.lang.String); method public java.lang.String[] split(java.lang.CharSequence, int); method public java.lang.String[] split(java.lang.CharSequence); + method public java.util.stream.Stream<java.lang.String> splitAsStream(java.lang.CharSequence); field public static final int CANON_EQ = 128; // 0x80 field public static final int CASE_INSENSITIVE = 2; // 0x2 field public static final int COMMENTS = 4; // 0x4 diff --git a/api/system-current.txt b/api/system-current.txt index 1871b258ae38..3febd2915acc 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5165,13 +5165,13 @@ package android.app { method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder); method public java.lang.CharSequence getCancelLabel(); method public java.lang.CharSequence getConfirmLabel(); - method public boolean getHintContentIntentLaunchesActivity(); + method public boolean getHintLaunchesActivity(); method public java.lang.CharSequence getInProgressLabel(); method public boolean isAvailableOffline(); method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean); method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence); method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence); - method public android.app.Notification.Action.WearableExtender setHintContentIntentLaunchesActivity(boolean); + method public android.app.Notification.Action.WearableExtender setHintLaunchesActivity(boolean); method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence); } @@ -31889,6 +31889,7 @@ package android.os.storage { public class StorageManager { method public java.lang.String getMountedObbPath(java.lang.String); method public android.os.storage.StorageVolume getPrimaryStorageVolume(); + method public android.os.storage.StorageVolume getStorageVolume(java.io.File); method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes(); method public boolean isEncrypted(java.io.File); method public boolean isObbMounted(java.lang.String); @@ -52173,6 +52174,7 @@ package java.io { ctor public BufferedReader(java.io.Reader, int); ctor public BufferedReader(java.io.Reader); method public void close() throws java.io.IOException; + method public java.util.stream.Stream<java.lang.String> lines(); method public int read(char[], int, int) throws java.io.IOException; method public java.lang.String readLine() throws java.io.IOException; } @@ -53043,6 +53045,11 @@ package java.io { ctor public UTFDataFormatException(java.lang.String); } + public class UncheckedIOException extends java.lang.RuntimeException { + ctor public UncheckedIOException(java.lang.String, java.io.IOException); + ctor public UncheckedIOException(java.io.IOException); + } + public class UnsupportedEncodingException extends java.io.IOException { ctor public UnsupportedEncodingException(); ctor public UnsupportedEncodingException(java.lang.String); @@ -60699,6 +60706,7 @@ package java.util { method public void set(int, int); method public void set(int, int, boolean); method public int size(); + method public java.util.stream.IntStream stream(); method public byte[] toByteArray(); method public long[] toLongArray(); method public static java.util.BitSet valueOf(long[]); @@ -61154,6 +61162,7 @@ package java.util { method public java.util.Set<java.util.Map.Entry<K, V>> entrySet(); method public void forEach(java.util.function.BiConsumer<? super K, ? super V>); method public boolean replace(K, V, V); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); } public class HashSet extends java.util.AbstractSet implements java.lang.Cloneable java.io.Serializable java.util.Set { @@ -61197,6 +61206,7 @@ package java.util { method public synchronized boolean remove(java.lang.Object, java.lang.Object); method public synchronized boolean replace(K, V, V); method public synchronized V replace(K, V); + method public synchronized void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); method public synchronized int size(); method public java.util.Collection<V> values(); } @@ -61737,6 +61747,18 @@ package java.util { public class Random implements java.io.Serializable { ctor public Random(); ctor public Random(long); + method public java.util.stream.DoubleStream doubles(long); + method public java.util.stream.DoubleStream doubles(); + method public java.util.stream.DoubleStream doubles(long, double, double); + method public java.util.stream.DoubleStream doubles(double, double); + method public java.util.stream.IntStream ints(long); + method public java.util.stream.IntStream ints(); + method public java.util.stream.IntStream ints(long, int, int); + method public java.util.stream.IntStream ints(int, int); + method public java.util.stream.LongStream longs(long); + method public java.util.stream.LongStream longs(); + method public java.util.stream.LongStream longs(long, long, long); + method public java.util.stream.LongStream longs(long, long); method protected int next(int); method public boolean nextBoolean(); method public void nextBytes(byte[]); @@ -62178,6 +62200,7 @@ package java.util { method public java.util.NavigableSet<K> navigableKeySet(); method public java.util.Map.Entry<K, V> pollFirstEntry(); method public java.util.Map.Entry<K, V> pollLastEntry(); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); method public java.util.NavigableMap<K, V> subMap(K, boolean, K, boolean); method public java.util.SortedMap<K, V> subMap(K, K); method public java.util.NavigableMap<K, V> tailMap(K, boolean); @@ -62279,6 +62302,7 @@ package java.util { ctor public WeakHashMap(java.util.Map<? extends K, ? extends V>); method public java.util.Set<java.util.Map.Entry<K, V>> entrySet(); method public void forEach(java.util.function.BiConsumer<? super K, ? super V>); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); } } @@ -63252,18 +63276,6 @@ package java.util.concurrent { public class ThreadLocalRandom extends java.util.Random { method public static java.util.concurrent.ThreadLocalRandom current(); - method public java.util.stream.DoubleStream doubles(long); - method public java.util.stream.DoubleStream doubles(); - method public java.util.stream.DoubleStream doubles(long, double, double); - method public java.util.stream.DoubleStream doubles(double, double); - method public java.util.stream.IntStream ints(long); - method public java.util.stream.IntStream ints(); - method public java.util.stream.IntStream ints(long, int, int); - method public java.util.stream.IntStream ints(int, int); - method public java.util.stream.LongStream longs(long); - method public java.util.stream.LongStream longs(); - method public java.util.stream.LongStream longs(long, long, long); - method public java.util.stream.LongStream longs(long, long); method public double nextDouble(double); method public double nextDouble(double, double); method public int nextInt(int, int); @@ -64636,6 +64648,7 @@ package java.util.regex { } public final class Pattern implements java.io.Serializable { + method public java.util.function.Predicate<java.lang.String> asPredicate(); method public static java.util.regex.Pattern compile(java.lang.String); method public static java.util.regex.Pattern compile(java.lang.String, int) throws java.util.regex.PatternSyntaxException; method public int flags(); @@ -64645,6 +64658,7 @@ package java.util.regex { method public static java.lang.String quote(java.lang.String); method public java.lang.String[] split(java.lang.CharSequence, int); method public java.lang.String[] split(java.lang.CharSequence); + method public java.util.stream.Stream<java.lang.String> splitAsStream(java.lang.CharSequence); field public static final int CANON_EQ = 128; // 0x80 field public static final int CASE_INSENSITIVE = 2; // 0x2 field public static final int COMMENTS = 4; // 0x4 diff --git a/api/test-current.txt b/api/test-current.txt index f2a321de88e4..7805613d291a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5031,13 +5031,13 @@ package android.app { method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder); method public java.lang.CharSequence getCancelLabel(); method public java.lang.CharSequence getConfirmLabel(); - method public boolean getHintContentIntentLaunchesActivity(); + method public boolean getHintLaunchesActivity(); method public java.lang.CharSequence getInProgressLabel(); method public boolean isAvailableOffline(); method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean); method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence); method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence); - method public android.app.Notification.Action.WearableExtender setHintContentIntentLaunchesActivity(boolean); + method public android.app.Notification.Action.WearableExtender setHintLaunchesActivity(boolean); method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence); } @@ -9505,6 +9505,7 @@ package android.content.pm { } public class LauncherApps { + ctor public LauncherApps(android.content.Context); method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo); method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, java.lang.String, android.os.UserHandle); @@ -10091,6 +10092,7 @@ package android.content.pm { } public class ShortcutManager { + ctor public ShortcutManager(android.content.Context); method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo); method public void deleteAllDynamicShortcuts(); method public void deleteDynamicShortcut(java.lang.String); @@ -29650,6 +29652,7 @@ package android.os.storage { public class StorageManager { method public java.lang.String getMountedObbPath(java.lang.String); method public android.os.storage.StorageVolume getPrimaryStorageVolume(); + method public android.os.storage.StorageVolume getStorageVolume(java.io.File); method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes(); method public boolean isEncrypted(java.io.File); method public boolean isObbMounted(java.lang.String); @@ -49136,6 +49139,7 @@ package java.io { ctor public BufferedReader(java.io.Reader, int); ctor public BufferedReader(java.io.Reader); method public void close() throws java.io.IOException; + method public java.util.stream.Stream<java.lang.String> lines(); method public int read(char[], int, int) throws java.io.IOException; method public java.lang.String readLine() throws java.io.IOException; } @@ -50006,6 +50010,11 @@ package java.io { ctor public UTFDataFormatException(java.lang.String); } + public class UncheckedIOException extends java.lang.RuntimeException { + ctor public UncheckedIOException(java.lang.String, java.io.IOException); + ctor public UncheckedIOException(java.io.IOException); + } + public class UnsupportedEncodingException extends java.io.IOException { ctor public UnsupportedEncodingException(); ctor public UnsupportedEncodingException(java.lang.String); @@ -57662,6 +57671,7 @@ package java.util { method public void set(int, int); method public void set(int, int, boolean); method public int size(); + method public java.util.stream.IntStream stream(); method public byte[] toByteArray(); method public long[] toLongArray(); method public static java.util.BitSet valueOf(long[]); @@ -58117,6 +58127,7 @@ package java.util { method public java.util.Set<java.util.Map.Entry<K, V>> entrySet(); method public void forEach(java.util.function.BiConsumer<? super K, ? super V>); method public boolean replace(K, V, V); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); } public class HashSet extends java.util.AbstractSet implements java.lang.Cloneable java.io.Serializable java.util.Set { @@ -58160,6 +58171,7 @@ package java.util { method public synchronized boolean remove(java.lang.Object, java.lang.Object); method public synchronized boolean replace(K, V, V); method public synchronized V replace(K, V); + method public synchronized void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); method public synchronized int size(); method public java.util.Collection<V> values(); } @@ -58700,6 +58712,18 @@ package java.util { public class Random implements java.io.Serializable { ctor public Random(); ctor public Random(long); + method public java.util.stream.DoubleStream doubles(long); + method public java.util.stream.DoubleStream doubles(); + method public java.util.stream.DoubleStream doubles(long, double, double); + method public java.util.stream.DoubleStream doubles(double, double); + method public java.util.stream.IntStream ints(long); + method public java.util.stream.IntStream ints(); + method public java.util.stream.IntStream ints(long, int, int); + method public java.util.stream.IntStream ints(int, int); + method public java.util.stream.LongStream longs(long); + method public java.util.stream.LongStream longs(); + method public java.util.stream.LongStream longs(long, long, long); + method public java.util.stream.LongStream longs(long, long); method protected int next(int); method public boolean nextBoolean(); method public void nextBytes(byte[]); @@ -59141,6 +59165,7 @@ package java.util { method public java.util.NavigableSet<K> navigableKeySet(); method public java.util.Map.Entry<K, V> pollFirstEntry(); method public java.util.Map.Entry<K, V> pollLastEntry(); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); method public java.util.NavigableMap<K, V> subMap(K, boolean, K, boolean); method public java.util.SortedMap<K, V> subMap(K, K); method public java.util.NavigableMap<K, V> tailMap(K, boolean); @@ -59242,6 +59267,7 @@ package java.util { ctor public WeakHashMap(java.util.Map<? extends K, ? extends V>); method public java.util.Set<java.util.Map.Entry<K, V>> entrySet(); method public void forEach(java.util.function.BiConsumer<? super K, ? super V>); + method public void replaceAll(java.util.function.BiFunction<? super K, ? super V, ? extends V>); } } @@ -60215,18 +60241,6 @@ package java.util.concurrent { public class ThreadLocalRandom extends java.util.Random { method public static java.util.concurrent.ThreadLocalRandom current(); - method public java.util.stream.DoubleStream doubles(long); - method public java.util.stream.DoubleStream doubles(); - method public java.util.stream.DoubleStream doubles(long, double, double); - method public java.util.stream.DoubleStream doubles(double, double); - method public java.util.stream.IntStream ints(long); - method public java.util.stream.IntStream ints(); - method public java.util.stream.IntStream ints(long, int, int); - method public java.util.stream.IntStream ints(int, int); - method public java.util.stream.LongStream longs(long); - method public java.util.stream.LongStream longs(); - method public java.util.stream.LongStream longs(long, long, long); - method public java.util.stream.LongStream longs(long, long); method public double nextDouble(double); method public double nextDouble(double, double); method public int nextInt(int, int); @@ -61599,6 +61613,7 @@ package java.util.regex { } public final class Pattern implements java.io.Serializable { + method public java.util.function.Predicate<java.lang.String> asPredicate(); method public static java.util.regex.Pattern compile(java.lang.String); method public static java.util.regex.Pattern compile(java.lang.String, int) throws java.util.regex.PatternSyntaxException; method public int flags(); @@ -61608,6 +61623,7 @@ package java.util.regex { method public static java.lang.String quote(java.lang.String); method public java.lang.String[] split(java.lang.CharSequence, int); method public java.lang.String[] split(java.lang.CharSequence); + method public java.util.stream.Stream<java.lang.String> splitAsStream(java.lang.CharSequence); field public static final int CANON_EQ = 128; // 0x80 field public static final int CASE_INSENSITIVE = 2; // 0x2 field public static final int COMMENTS = 4; // 0x4 diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index f0e35c9b061d..f203f46f4408 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -318,7 +318,7 @@ class LoaderManagerImpl extends LoaderManager { if (mStarted) { if (mReportNextStart) { mReportNextStart = false; - if (mHaveData) { + if (mHaveData && !mRetaining) { callOnLoadFinished(mLoader, mData); } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4bf1aa38fc13..520acf502f0d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -43,6 +44,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.os.UserHandle; +import android.text.BidiFormatter; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; @@ -588,8 +590,8 @@ public class Notification implements Parcelable private static final int COLOR_INVALID = 1; /** - * Sphere of visibility of this notification, which affects how and when the SystemUI reveals - * the notification's presence and contents in untrusted situations (namely, on the secure + * Sphere of visibility of this notification, which affects how and when the SystemUI reveals + * the notification's presence and contents in untrusted situations (namely, on the secure * lockscreen). * * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always @@ -1419,7 +1421,7 @@ public class Notification implements Parcelable * an activity and transitions should be generated, false otherwise. * @return this object for method chaining */ - public WearableExtender setHintContentIntentLaunchesActivity( + public WearableExtender setHintLaunchesActivity( boolean hintLaunchesActivity) { setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); return this; @@ -1432,7 +1434,7 @@ public class Notification implements Parcelable * should be generated, false otherwise. The default value is {@code false} if this was * never set. */ - public boolean getHintContentIntentLaunchesActivity() { + public boolean getHintLaunchesActivity() { return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; } } @@ -1955,9 +1957,16 @@ public class Notification implements Parcelable * @hide */ public static void addFieldsFromContext(Context context, Notification notification) { - notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, - context.getApplicationInfo()); - notification.extras.putInt(EXTRA_ORIGINATING_USERID, context.getUserId()); + addFieldsFromContext(context.getApplicationInfo(), context.getUserId(), notification); + } + + /** + * @hide + */ + public static void addFieldsFromContext(ApplicationInfo ai, int userId, + Notification notification) { + notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); + notification.extras.putInt(EXTRA_ORIGINATING_USERID, userId); } @Override @@ -2220,7 +2229,8 @@ public class Notification implements Parcelable Log.d(TAG, "Unknown style class: " + templateClass); } else { try { - final Constructor<? extends Style> ctor = styleClass.getConstructor(); + final Constructor<? extends Style> ctor = + styleClass.getDeclaredConstructor(); ctor.setAccessible(true); final Style style = ctor.newInstance(); style.restoreFromExtras(mN.extras); @@ -3119,6 +3129,18 @@ public class Notification implements Parcelable * @param hasProgress whether the progress bar should be shown and set */ private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) { + final Bundle ex = mN.extras; + + CharSequence title = processLegacyText(ex.getCharSequence(EXTRA_TITLE)); + CharSequence text = processLegacyText(ex.getCharSequence(EXTRA_TEXT)); + return applyStandardTemplate(resId, hasProgress, title, text); + } + + /** + * @param hasProgress whether the progress bar should be shown and set + */ + private RemoteViews applyStandardTemplate(int resId, boolean hasProgress, + CharSequence title, CharSequence text) { RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); resetStandardTemplate(contentView); @@ -3127,17 +3149,15 @@ public class Notification implements Parcelable bindNotificationHeader(contentView); bindLargeIcon(contentView); - if (ex.getCharSequence(EXTRA_TITLE) != null) { + if (title != null) { contentView.setViewVisibility(R.id.title, View.VISIBLE); - contentView.setTextViewText(R.id.title, - processLegacyText(ex.getCharSequence(EXTRA_TITLE))); + contentView.setTextViewText(R.id.title, title); } boolean showProgress = handleProgressBar(hasProgress, contentView, ex); - if (ex.getCharSequence(EXTRA_TEXT) != null) { + if (text != null) { int textId = showProgress ? com.android.internal.R.id.text_line_1 : com.android.internal.R.id.text; - contentView.setTextViewText(textId, processLegacyText( - ex.getCharSequence(EXTRA_TEXT))); + contentView.setTextViewText(textId, text); contentView.setViewVisibility(textId, View.VISIBLE); } @@ -3742,6 +3762,10 @@ public class Notification implements Parcelable return R.layout.notification_template_material_inbox; } + private int getMessagingLayoutResource() { + return R.layout.notification_template_material_messaging; + } + private int getActionLayoutResource() { return R.layout.notification_material_action; } @@ -4368,13 +4392,100 @@ public class Notification implements Parcelable /** * @hide */ + @Override + public RemoteViews makeContentView() { + Message m = findLatestIncomingMessage(); + CharSequence title = mConversationTitle != null + ? mConversationTitle + : (m == null) ? null : m.mSender; + CharSequence text = (m == null) + ? null + : mConversationTitle != null ? makeMessageLine(m) : m.mText; + + return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(), + false /* hasProgress */, + title, + text); + } + + private Message findLatestIncomingMessage() { + for (int i = mMessages.size() - 1; i >= 0; i--) { + Message m = mMessages.get(i); + // Incoming messages have a non-empty sender. + if (!TextUtils.isEmpty(m.mSender)) { + return m; + } + } + return null; + } + + /** + * @hide + */ + @Override public RemoteViews makeBigContentView() { - // TODO handset to write implementation - RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource()); + CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle) + ? super.mBigContentTitle + : mConversationTitle; + boolean hasTitle = !TextUtils.isEmpty(title); + + RemoteViews contentView = mBuilder.applyStandardTemplate( + mBuilder.getMessagingLayoutResource(), + false /* hasProgress */, + title, + null /* text */); + + int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, + R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; + + // Make sure all rows are gone in case we reuse a view. + for (int rowId : rowIds) { + contentView.setViewVisibility(rowId, View.GONE); + } + int i=0; + int titlePadding = mBuilder.mContext.getResources().getDimensionPixelSize( + R.dimen.notification_messaging_spacing); + contentView.setViewLayoutMarginBottom(R.id.line1, hasTitle ? titlePadding : 0); + contentView.setInt(R.id.notification_messaging, "setNumIndentLines", + mBuilder.mN.mLargeIcon == null ? 0 : (hasTitle ? 1 : 2)); + + int firstMessage = Math.max(0, mMessages.size() - rowIds.length); + while (firstMessage + i < mMessages.size() && i < rowIds.length) { + Message m = mMessages.get(firstMessage + i); + int rowId = rowIds[i]; + + contentView.setViewVisibility(rowId, View.VISIBLE); + contentView.setTextViewText(rowId, makeMessageLine(m)); + + i++; + } return contentView; } + private CharSequence makeMessageLine(Message m) { + BidiFormatter bidi = BidiFormatter.getInstance(); + SpannableStringBuilder sb = new SpannableStringBuilder(); + if (TextUtils.isEmpty(m.mSender)) { + CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName; + sb.append(bidi.unicodeWrap(replyName), + makeFontColorSpan(mBuilder.resolveContrastColor()), + 0 /* flags */); + } else { + sb.append(bidi.unicodeWrap(m.mSender), + makeFontColorSpan(Color.BLACK), + 0 /* flags */); + } + CharSequence text = m.mText == null ? "" : m.mText; + sb.append(" ").append(bidi.unicodeWrap(text)); + return sb; + } + + private static TextAppearanceSpan makeFontColorSpan(int color) { + return new TextAppearanceSpan(null, 0, 0, + ColorStateList.valueOf(color), null); + } + public static final class Message implements Parcelable { private final CharSequence mText; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index bdc4404057e5..d5d4ca7ad401 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -589,9 +589,7 @@ final class SystemServiceRegistry { new CachedServiceFetcher<LauncherApps>() { @Override public LauncherApps createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE); - ILauncherApps service = ILauncherApps.Stub.asInterface(b); - return new LauncherApps(ctx, service); + return new LauncherApps(ctx); }}); registerService(Context.RESTRICTIONS_SERVICE, RestrictionsManager.class, @@ -758,8 +756,7 @@ final class SystemServiceRegistry { new CachedServiceFetcher<ShortcutManager>() { @Override public ShortcutManager createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(Context.SHORTCUT_SERVICE); - return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b)); + return new ShortcutManager(ctx); }}); registerService(Context.SYSTEM_HEALTH_SERVICE, SystemHealthManager.class, diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 828ac385b62d..9b4f43ae9feb 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -254,7 +254,8 @@ public class JobInfo implements Parcelable { } /** - * Flex time for this job. Only valid if this is a periodic job. + * Flex time for this job. Only valid if this is a periodic job. The job can + * execute at any time in a window of flex length at the end of the period. */ public long getFlexMillis() { long interval = getIntervalMillis(); diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 104feb5dc7cd..585d2a3c4b25 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -366,6 +366,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * * <p>NOTE: {@code WebView} does not honor this flag. * + * <p>This flag is ignored on Android N and above if an Android Network Security Config is + * present. + * * <p>This flag comes from * {@link android.R.styleable#AndroidManifestApplication_usesCleartextTraffic * android:usesCleartextTraffic} of the <application> tag. diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 342dbe2303e0..7c2d4aac6947 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -19,6 +19,7 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -30,6 +31,7 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; @@ -261,6 +263,13 @@ public class LauncherApps { mPm = context.getPackageManager(); } + /** @hide */ + @TestApi + public LauncherApps(Context context) { + this(context, ILauncherApps.Stub.asInterface( + ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE))); + } + /** * Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and * {@link Intent#CATEGORY_LAUNCHER}, for a specified user. diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index e4a98b5f37e3..919ccda91ba9 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -16,8 +16,11 @@ package android.content.pm; import android.annotation.NonNull; +import android.annotation.TestApi; import android.content.Context; +import android.os.IBinder; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; @@ -97,6 +100,15 @@ public class ShortcutManager { } /** + * @hide + */ + @TestApi + public ShortcutManager(Context context) { + this(context, IShortcutService.Stub.asInterface( + ServiceManager.getService(Context.SHORTCUT_SERVICE))); + } + + /** * Publish a list of shortcuts. All existing dynamic shortcuts from the caller application * will be replaced. * diff --git a/core/java/android/inputmethodservice/CompactExtractEditLayout.java b/core/java/android/inputmethodservice/CompactExtractEditLayout.java new file mode 100644 index 000000000000..35c54b284275 --- /dev/null +++ b/core/java/android/inputmethodservice/CompactExtractEditLayout.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 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.inputmethodservice; + +import android.content.Context; +import android.content.res.Resources; +import android.annotation.FractionRes; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +/** + * A special purpose layout for the editor extract view for tiny (sub 250dp) screens. + * The layout is based on sizes proportional to screen pixel size to provide for the + * best layout fidelity on varying pixel sizes and densities. + * + * @hide + */ +public class CompactExtractEditLayout extends LinearLayout { + private View mInputExtractEditText; + private View mInputExtractAccessories; + private View mInputExtractAction; + private boolean mPerformLayoutChanges; + + public CompactExtractEditLayout(Context context) { + super(context); + } + + public CompactExtractEditLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CompactExtractEditLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mInputExtractEditText = findViewById(com.android.internal.R.id.inputExtractEditText); + mInputExtractAccessories = findViewById(com.android.internal.R.id.inputExtractAccessories); + mInputExtractAction = findViewById(com.android.internal.R.id.inputExtractAction); + + if (mInputExtractEditText != null && mInputExtractAccessories != null + && mInputExtractAction != null) { + mPerformLayoutChanges = true; + } + } + + private int applyFractionInt(@FractionRes int fraction, int whole) { + return Math.round(getResources().getFraction(fraction, whole, whole)); + } + + private static void setLayoutHeight(View v, int px) { + ViewGroup.LayoutParams lp = v.getLayoutParams(); + lp.height = px; + v.setLayoutParams(lp); + } + + private static void setLayoutMarginBottom(View v, int px) { + ViewGroup.MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); + lp.bottomMargin = px; + v.setLayoutParams(lp); + } + + private void applyProportionalLayout(int screenWidthPx, int screenHeightPx) { + if (getResources().getConfiguration().isScreenRound()) { + setGravity(Gravity.BOTTOM); + } + setLayoutHeight(this, applyFractionInt( + com.android.internal.R.fraction.input_extract_layout_height, screenHeightPx)); + + setPadding( + applyFractionInt(com.android.internal.R.fraction.input_extract_layout_padding_left, + screenWidthPx), + 0, + applyFractionInt(com.android.internal.R.fraction.input_extract_layout_padding_right, + screenWidthPx), + 0); + + setLayoutMarginBottom(mInputExtractEditText, + applyFractionInt(com.android.internal.R.fraction.input_extract_text_margin_bottom, + screenHeightPx)); + + setLayoutMarginBottom(mInputExtractAccessories, + applyFractionInt(com.android.internal.R.fraction.input_extract_action_margin_bottom, + screenHeightPx)); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mPerformLayoutChanges) { + Resources res = getResources(); + DisplayMetrics dm = res.getDisplayMetrics(); + int heightPixels = dm.heightPixels; + int widthPixels = dm.widthPixels; + applyProportionalLayout(widthPixels, heightPixels); + } + } +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index cc201bc78bb5..085b97cc0f6d 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -68,9 +68,10 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; -import android.widget.Button; import android.widget.FrameLayout; +import android.widget.ImageButton; import android.widget.LinearLayout; +import android.widget.TextView; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -302,7 +303,7 @@ public class InputMethodService extends AbstractInputMethodService { boolean mExtractViewHidden; ExtractEditText mExtractEditText; ViewGroup mExtractAccessories; - Button mExtractAction; + View mExtractAction; ExtractedText mExtractedText; int mExtractedToken; @@ -1344,7 +1345,7 @@ public class InputMethodService extends AbstractInputMethodService { mExtractEditText = (ExtractEditText)view.findViewById( com.android.internal.R.id.inputExtractEditText); mExtractEditText.setIME(this); - mExtractAction = (Button)view.findViewById( + mExtractAction = view.findViewById( com.android.internal.R.id.inputExtractAction); if (mExtractAction != null) { mExtractAccessories = (ViewGroup)view.findViewById( @@ -2408,7 +2409,35 @@ public class InputMethodService extends AbstractInputMethodService { return getText(com.android.internal.R.string.ime_action_default); } } - + + /** + * Return a drawable resource id that can be used as a button icon for the given + * {@link EditorInfo#imeOptions EditorInfo.imeOptions}. + * + * @param imeOptions The value from @link EditorInfo#imeOptions EditorInfo.imeOptions}. + * + * @return Returns a drawable resource id to use. + */ + @DrawableRes + private int getIconForImeAction(int imeOptions) { + switch (imeOptions&EditorInfo.IME_MASK_ACTION) { + case EditorInfo.IME_ACTION_GO: + return com.android.internal.R.drawable.ic_input_extract_action_go; + case EditorInfo.IME_ACTION_SEARCH: + return com.android.internal.R.drawable.ic_input_extract_action_search; + case EditorInfo.IME_ACTION_SEND: + return com.android.internal.R.drawable.ic_input_extract_action_send; + case EditorInfo.IME_ACTION_NEXT: + return com.android.internal.R.drawable.ic_input_extract_action_next; + case EditorInfo.IME_ACTION_DONE: + return com.android.internal.R.drawable.ic_input_extract_action_done; + case EditorInfo.IME_ACTION_PREVIOUS: + return com.android.internal.R.drawable.ic_input_extract_action_previous; + default: + return com.android.internal.R.drawable.ic_input_extract_action_return; + } + } + /** * Called when the fullscreen-mode extracting editor info has changed, * to determine whether the extracting (extract text and candidates) portion @@ -2459,10 +2488,20 @@ public class InputMethodService extends AbstractInputMethodService { if (hasAction) { mExtractAccessories.setVisibility(View.VISIBLE); if (mExtractAction != null) { - if (ei.actionLabel != null) { - mExtractAction.setText(ei.actionLabel); + if (mExtractAction instanceof ImageButton) { + ((ImageButton) mExtractAction) + .setImageResource(getIconForImeAction(ei.imeOptions)); + if (ei.actionLabel != null) { + mExtractAction.setContentDescription(ei.actionLabel); + } else { + mExtractAction.setContentDescription(getTextForImeAction(ei.imeOptions)); + } } else { - mExtractAction.setText(getTextForImeAction(ei.imeOptions)); + if (ei.actionLabel != null) { + ((TextView) mExtractAction).setText(ei.actionLabel); + } else { + ((TextView) mExtractAction).setText(getTextForImeAction(ei.imeOptions)); + } } mExtractAction.setOnClickListener(mActionClickListener); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index c4528372987f..773e7dd601c8 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -3094,14 +3094,8 @@ public abstract class BatteryStats implements Parcelable { dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA, u.getWifiControllerActivity(), which); - // Dump Bluetooth scan data, per UID. - final long bleScanTimeUs = u.getBluetoothScanTimer().getTotalTimeLocked( + dumpTimer(pw, uid, category, BLUETOOTH_MISC_DATA, u.getBluetoothScanTimer(), rawRealtime, which); - final int bleScanCount = u.getBluetoothScanTimer().getCountLocked(which); - if (bleScanTimeUs != 0 || bleScanCount != 0) { - dumpLine(pw, uid, category, BLUETOOTH_MISC_DATA, - bleScanTimeUs / 1000, bleScanCount); - } dumpControllerActivityLine(pw, uid, category, BLUETOOTH_CONTROLLER_DATA, u.getBluetoothControllerActivity(), which); diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index ece12280bb80..da215c61641a 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -824,7 +824,9 @@ public class StorageManager { } } - /** {@hide} */ + /** + * Return the {@link StorageVolume} that contains the given file, or {@code null} if none. + */ public @Nullable StorageVolume getStorageVolume(File file) { return getStorageVolume(getVolumeList(), file); } @@ -836,9 +838,13 @@ public class StorageManager { /** {@hide} */ private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) { + if (file == null) { + return null; + } try { file = file.getCanonicalFile(); } catch (IOException ignored) { + Slog.d(TAG, "Could not get canonical path for " + file); return null; } for (StorageVolume volume : volumes) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 638be5167f86..fdb1cef60152 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1581,6 +1581,8 @@ public final class Settings { public static final class System extends NameValueTable { public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version"; + private static final float DEFAULT_FONT_SCALE = 1.0f; + /** @hide */ public static interface Validator { public boolean validate(String value); @@ -2089,9 +2091,9 @@ public final class Settings { public static void getConfigurationForUser(ContentResolver cr, Configuration outConfig, int userHandle) { outConfig.fontScale = Settings.System.getFloatForUser( - cr, FONT_SCALE, outConfig.fontScale, userHandle); + cr, FONT_SCALE, DEFAULT_FONT_SCALE, userHandle); if (outConfig.fontScale < 0) { - outConfig.fontScale = 1; + outConfig.fontScale = DEFAULT_FONT_SCALE; } outConfig.setLocales(LocaleList.forLanguageTags( Settings.System.getStringForUser(cr, SYSTEM_LOCALES, userHandle))); diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java index 9ed485018c2d..0a452dbc39a1 100644 --- a/core/java/android/util/Patterns.java +++ b/core/java/android/util/Patterns.java @@ -251,9 +251,9 @@ public class Patterns { + "|[1-9][0-9]|[0-9]))"); /** - * Valid UCS characters defined in RFC 3987. + * Valid UCS characters defined in RFC 3987. Excludes space characters. */ - private static final String UCS_CHAR = + private static final String UCS_CHAR = "[" + "\u00A0-\uD7FF" + "\uF900-\uFDCF" + "\uFDF0-\uFFEF" + @@ -270,7 +270,8 @@ public class Patterns { "\uDA80\uDC00-\uDABF\uDFFD" + "\uDAC0\uDC00-\uDAFF\uDFFD" + "\uDB00\uDC00-\uDB3F\uDFFD" + - "\uDB44\uDC00-\uDB7F\uDFFD"; + "\uDB44\uDC00-\uDB7F\uDFFD" + + "&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]"; /** * Valid characters for IRI label defined in RFC 3987. diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index c97247656540..c4ed94f6184d 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -893,6 +893,10 @@ public final class ThreadedRenderer { nSerializeDisplayListTree(mNativeProxy); } + public static boolean copySurfaceInto(Surface surface, Bitmap bitmap) { + return nCopySurfaceInto(surface, bitmap); + } + @Override protected void finalize() throws Throwable { try { @@ -1029,4 +1033,6 @@ public final class ThreadedRenderer { private static native long nAddFrameMetricsObserver(long nativeProxy, FrameMetricsObserver observer); private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver); + + private static native boolean nCopySurfaceInto(Surface surface, Bitmap bitmap); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7e5109637855..307e700e3655 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -10278,7 +10278,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @Visibility boolean dispatchVisibilityAggregated(boolean isVisible) { final boolean thisVisible = getVisibility() == VISIBLE; - if (thisVisible) { + // If we're not visible but something is telling us we are, ignore it. + if (thisVisible || !isVisible) { onVisibilityAggregated(isVisible); } return thisVisible && isVisible; diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 6d2cea6b5c3c..a9b7f4e97e7c 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1856,6 +1856,7 @@ public class RemoteViews implements Parcelable, Filter { public static final int LAYOUT_MARGIN_END = 1; /** Set width */ public static final int LAYOUT_WIDTH = 2; + public static final int LAYOUT_MARGIN_BOTTOM = 3; /** * @param viewId ID of the view alter @@ -1898,6 +1899,12 @@ public class RemoteViews implements Parcelable, Filter { target.setLayoutParams(layoutParams); } break; + case LAYOUT_MARGIN_BOTTOM: + if (layoutParams instanceof ViewGroup.MarginLayoutParams) { + ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = value; + target.setLayoutParams(layoutParams); + } + break; case LAYOUT_WIDTH: layoutParams.width = value; target.setLayoutParams(layoutParams); @@ -2870,6 +2877,16 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. + * + * @hide + */ + public void setViewLayoutMarginBottom(int viewId, int bottomMargin) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM, + bottomMargin)); + } + + /** * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. * @hide */ diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index ff10287f3f89..a585d75e5d7a 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -555,7 +555,15 @@ public class TextClock extends TextView { filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); + // OK, this is gross but needed. This class is supported by the + // remote views mechanism and as a part of that the remote views + // can be inflated by a context for another user without the app + // having interact users permission - just for loading resources. + // For example, when adding widgets from a managed profile to the + // home screen. Therefore, we register the receiver as the user + // the app is running as not the one the context is for. + getContext().registerReceiverAsUser(mIntentReceiver, android.os.Process.myUserHandle(), + filter, null, getHandler()); } private void registerObserver() { diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java index a9d51132cdcc..7e9587adaf26 100644 --- a/core/java/com/android/internal/app/LocaleHelper.java +++ b/core/java/com/android/internal/app/LocaleHelper.java @@ -90,6 +90,15 @@ public class LocaleHelper { return str.toUpperCase(); } + // For some locales we want to use a "dialect" form, for instance + // "Dari" instead of "Persian (Afghanistan)", or "Moldavian" instead of "Romanian (Moldova)" + private static boolean shouldUseDialectName(Locale locale) { + final String lang = locale.getLanguage(); + return "fa".equals(lang) // Persian + || "ro".equals(lang) // Romanian + || "zh".equals(lang); // Chinese + } + /** * Returns the locale localized for display in the provided locale. * @@ -99,8 +108,10 @@ public class LocaleHelper { * @return the localized name of the locale. */ public static String getDisplayName(Locale locale, Locale displayLocale, boolean sentenceCase) { - String result = ULocale.getDisplayNameWithDialect(locale.toLanguageTag(), - ULocale.forLocale(displayLocale)); + final ULocale displayULocale = ULocale.forLocale(displayLocale); + String result = shouldUseDialectName(locale) + ? ULocale.getDisplayNameWithDialect(locale.toLanguageTag(), displayULocale) + : ULocale.getDisplayName(locale.toLanguageTag(), displayULocale); return sentenceCase ? toSentenceCase(result, displayLocale) : result; } @@ -112,9 +123,7 @@ public class LocaleHelper { * @return the localized name of the locale. */ public static String getDisplayName(Locale locale, boolean sentenceCase) { - String result = ULocale.getDisplayNameWithDialect(locale.toLanguageTag(), - ULocale.getDefault()); - return sentenceCase ? toSentenceCase(result, Locale.getDefault()) : result; + return getDisplayName(locale, Locale.getDefault(), sentenceCase); } /** diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index f2bf9e1d9695..1f2acc9d5489 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -760,7 +760,7 @@ public class ResolverActivity extends Activity { } else { try { AppGlobals.getPackageManager().setLastChosenActivity(intent, - intent.resolveTypeIfNeeded(getContentResolver()), + intent.resolveType(getContentResolver()), PackageManager.MATCH_DEFAULT_ONLY, filter, bestMatch, intent.getComponent()); } catch (RemoteException re) { diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 46b49de7fc80..8d117837df83 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -16,6 +16,7 @@ package com.android.internal.inputmethod; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; @@ -284,8 +285,22 @@ public class InputMethodSubtypeSwitchingController { return -1; } + /** + * Provides the basic operation to implement bi-directional IME rotation. + * @param onlyCurrentIme {@code true} to limit the search space to IME subtypes that belong + * to {@code imi}. + * @param imi {@link InputMethodInfo} that will be used in conjunction with {@code subtype} + * from which we find the adjacent IME subtype. + * @param subtype {@link InputMethodSubtype} that will be used in conjunction with + * {@code imi} from which we find the next IME subtype. {@code null} if the input method + * does not have a subtype. + * @param forward {@code true} to do forward search the next IME subtype. Specify + * {@code false} to do backward search. + * @return The IME subtype found. {@code null} if no IME subtype is found. + */ + @Nullable public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, - InputMethodInfo imi, InputMethodSubtype subtype, boolean forward) { + InputMethodInfo imi, @Nullable InputMethodSubtype subtype, boolean forward) { if (imi == null) { return null; } @@ -298,7 +313,7 @@ public class InputMethodSubtypeSwitchingController { } final int N = mImeSubtypeList.size(); for (int i = 1; i < N; ++i) { - // Start searching the next IME/subtype from the next of the current index. + // Start searching the next IME/subtype from +/- 1 indices. final int offset = forward ? i : N - i; final int candidateIndex = (currentIndex + offset) % N; final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); @@ -371,8 +386,22 @@ public class InputMethodSubtypeSwitchingController { mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex; } + /** + * Provides the basic operation to implement bi-directional IME rotation. + * @param onlyCurrentIme {@code true} to limit the search space to IME subtypes that belong + * to {@code imi}. + * @param imi {@link InputMethodInfo} that will be used in conjunction with {@code subtype} + * from which we find the adjacent IME subtype. + * @param subtype {@link InputMethodSubtype} that will be used in conjunction with + * {@code imi} from which we find the next IME subtype. {@code null} if the input method + * does not have a subtype. + * @param forward {@code true} to do forward search the next IME subtype. Specify + * {@code false} to do backward search. + * @return The IME subtype found. {@code null} if no IME subtype is found. + */ + @Nullable public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, - InputMethodInfo imi, InputMethodSubtype subtype, boolean forward) { + InputMethodInfo imi, @Nullable InputMethodSubtype subtype, boolean forward) { int currentUsageRank = getUsageRank(imi, subtype); if (currentUsageRank < 0) { if (DEBUG) { @@ -456,8 +485,22 @@ public class InputMethodSubtypeSwitchingController { mSwitchingUnawareRotationList = switchingUnawareRotationList; } + /** + * Provides the basic operation to implement bi-directional IME rotation. + * @param onlyCurrentIme {@code true} to limit the search space to IME subtypes that belong + * to {@code imi}. + * @param imi {@link InputMethodInfo} that will be used in conjunction with {@code subtype} + * from which we find the adjacent IME subtype. + * @param subtype {@link InputMethodSubtype} that will be used in conjunction with + * {@code imi} from which we find the next IME subtype. {@code null} if the input method + * does not have a subtype. + * @param forward {@code true} to do forward search the next IME subtype. Specify + * {@code false} to do backward search. + * @return The IME subtype found. {@code null} if no IME subtype is found. + */ + @Nullable public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, - InputMethodSubtype subtype, boolean forward) { + @Nullable InputMethodSubtype subtype, boolean forward) { if (imi == null) { return null; } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index f9ac5632b49b..3aa771971cf2 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -210,7 +210,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Drawable mResizingBackgroundDrawable; private Drawable mCaptionBackgroundDrawable; private Drawable mUserCaptionBackgroundDrawable; - private Drawable mOriginalBackgroundDrawable; private float mAvailableWidth; @@ -891,11 +890,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mBackgroundPadding.setEmpty(); } drawableChanged(); - - // Make sure we don't reset to the old drawable when finishing resizing. - if (mResizeMode != RESIZE_MODE_INVALID) { - mOriginalBackgroundDrawable = null; - } } } @@ -1960,9 +1954,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateElevation(); updateColorViews(null /* insets */, false); - - mOriginalBackgroundDrawable = getBackground(); - setBackgroundDrawable(null); } mResizeMode = resizeMode; getViewRootImpl().requestInvalidateRootRenderNode(); @@ -1974,10 +1965,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateColorViews(null /* insets */, false); mResizeMode = RESIZE_MODE_INVALID; getViewRootImpl().requestInvalidateRootRenderNode(); - if (mOriginalBackgroundDrawable != null) { - setBackgroundDrawable(mOriginalBackgroundDrawable); - mOriginalBackgroundDrawable = null; - } } @Override diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java index 78c5e34ba108..e2d8ffc4d9b1 100644 --- a/core/java/com/android/internal/widget/ImageFloatingTextView.java +++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java @@ -16,13 +16,18 @@ package com.android.internal.widget; +import com.android.internal.R; + import android.annotation.Nullable; import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; import android.text.BoringLayout; import android.text.Layout; import android.text.StaticLayout; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.TypedValue; import android.view.RemotableViewMethod; import android.widget.RemoteViews; import android.widget.TextView; @@ -35,7 +40,8 @@ import android.widget.TextView; @RemoteViews.RemoteView public class ImageFloatingTextView extends TextView { - private boolean mHasImage; + /** Number of lines from the top to indent */ + private int mIndentLines; public ImageFloatingTextView(Context context) { this(context, null); @@ -69,10 +75,16 @@ public class ImageFloatingTextView extends TextView { .setEllipsizedWidth(ellipsisWidth) .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - // we set the endmargin on the first 2 lines. this works just in our case but that's - // sufficient for now. - int endMargin = (int) (getResources().getDisplayMetrics().density * 52); - int[] margins = mHasImage ? new int[] {endMargin, endMargin, 0} : null; + // we set the endmargin on the requested number of lines. + int endMargin = getContext().getResources().getDimensionPixelSize( + R.dimen.notification_content_picture_margin); + int[] margins = null; + if (mIndentLines > 0) { + margins = new int[mIndentLines + 1]; + for (int i = 0; i < mIndentLines; i++) { + margins[i] = endMargin; + } + } if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { builder.setIndents(margins, null); } else { @@ -84,8 +96,22 @@ public class ImageFloatingTextView extends TextView { @RemotableViewMethod public void setHasImage(boolean hasImage) { - mHasImage = hasImage; + mIndentLines = hasImage ? 2 : 0; // The new layout will be automatically created when the text is // set again by the notification. } + + /** + * @param lines the number of lines at the top that should be indented by indentEnd + * @return whether a change was made + */ + public boolean setNumIndentLines(int lines) { + if (mIndentLines != lines) { + mIndentLines = lines; + // Invalidate layout. + setHint(getHint()); + return true; + } + return false; + } } diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java new file mode 100644 index 000000000000..dc7b7f5b9646 --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2016 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.internal.widget; + +import com.android.internal.R; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.RemotableViewMethod; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RemoteViews; + +/** + * A custom-built layout for the Notification.MessagingStyle. + * + * Evicts children until they all fit. + */ +@RemoteViews.RemoteView +public class MessagingLinearLayout extends ViewGroup { + + /** + * Spacing to be applied between views. + */ + private int mSpacing; + + /** + * The maximum height allowed. + */ + private int mMaxHeight; + + private int mIndentLines; + + public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.MessagingLinearLayout, 0, + 0); + + final int N = a.getIndexCount(); + for (int i = 0; i < N; i++) { + int attr = a.getIndex(i); + switch (attr) { + case R.styleable.MessagingLinearLayout_maxHeight: + mMaxHeight = a.getDimensionPixelSize(i, 0); + break; + case R.styleable.MessagingLinearLayout_spacing: + mSpacing = a.getDimensionPixelSize(i, 0); + break; + } + } + + if (mMaxHeight <= 0) { + throw new IllegalStateException( + "MessagingLinearLayout: Must specify positive maxHeight"); + } + + a.recycle(); + } + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // This is essentially a bottom-up linear layout that only adds children that fit entirely + // up to a maximum height. + + switch (MeasureSpec.getMode(heightMeasureSpec)) { + case MeasureSpec.AT_MOST: + heightMeasureSpec = MeasureSpec.makeMeasureSpec( + Math.min(mMaxHeight, MeasureSpec.getSize(heightMeasureSpec)), + MeasureSpec.AT_MOST); + break; + case MeasureSpec.UNSPECIFIED: + heightMeasureSpec = MeasureSpec.makeMeasureSpec( + mMaxHeight, + MeasureSpec.AT_MOST); + break; + case MeasureSpec.EXACTLY: + break; + } + final int targetHeight = MeasureSpec.getSize(heightMeasureSpec); + final int count = getChildCount(); + + for (int i = 0; i < count; ++i) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.hide = true; + } + + int totalHeight = mPaddingTop + mPaddingBottom; + boolean first = true; + + // Starting from the bottom: we measure every view as if it were the only one. If it still + // fits, we take it, otherwise we stop there. + for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) { + if (getChildAt(i).getVisibility() == GONE) { + continue; + } + final View child = getChildAt(i); + LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); + + if (child instanceof ImageFloatingTextView) { + // Pretend we need the image padding for all views, we don't know which + // one will end up needing to do this (might end up not using all the space, + // but calculating this exactly would be more expensive). + ((ImageFloatingTextView) child).setNumIndentLines(mIndentLines); + } + + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + + final int childHeight = child.getMeasuredHeight(); + int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin + + lp.bottomMargin + (first ? 0 : mSpacing)); + first = false; + + if (newHeight <= targetHeight) { + totalHeight = newHeight; + lp.hide = false; + } else { + break; + } + } + + // Now that we know which views to take, fix up the indents and see what width we get. + int measuredWidth = mPaddingLeft + mPaddingRight; + int imageLines = mIndentLines; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if (child.getVisibility() == GONE || lp.hide) { + continue; + } + + if (child instanceof ImageFloatingTextView) { + ImageFloatingTextView textChild = (ImageFloatingTextView) child; + if (imageLines == 2 && textChild.getLineCount() > 2) { + // HACK: If we need indent for two lines, and they're coming from the same + // view, we need extra spacing to compensate for the lack of margins, + // so add an extra line of indent. + imageLines = 3; + } + boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines)); + if (changed) { + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + } + imageLines -= textChild.getLineCount(); + } + + measuredWidth = Math.max(measuredWidth, + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + + mPaddingLeft + mPaddingRight); + } + + + setMeasuredDimension( + resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), + widthMeasureSpec), + resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight), + heightMeasureSpec)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int paddingLeft = mPaddingLeft; + + int childTop; + + // Where right end of child should go + final int width = right - left; + final int childRight = width - mPaddingRight; + + final int layoutDirection = getLayoutDirection(); + final int count = getChildCount(); + + childTop = mPaddingTop; + + boolean first = true; + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if (child.getVisibility() == GONE || lp.hide) { + continue; + } + + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + + int childLeft; + if (layoutDirection == LAYOUT_DIRECTION_RTL) { + childLeft = childRight - childWidth - lp.rightMargin; + } else { + childLeft = paddingLeft + lp.leftMargin; + } + + if (!first) { + childTop += mSpacing; + } + + childTop += lp.topMargin; + child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); + + childTop += childHeight + lp.bottomMargin; + + first = false; + } + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.hide) { + return true; + } + return super.drawChild(canvas, child, drawingTime); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(mContext, attrs); + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + + } + + @Override + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { + LayoutParams copy = new LayoutParams(lp.width, lp.height); + if (lp instanceof MarginLayoutParams) { + copy.copyMarginsFrom((MarginLayoutParams) lp); + } + return copy; + } + + @RemotableViewMethod + /** + * Sets how many lines should be indented to avoid a floating image. + */ + public void setNumIndentLines(int numberLines) { + mIndentLines = numberLines; + } + + public static class LayoutParams extends MarginLayoutParams { + + boolean hide = false; + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(int width, int height) { + super(width, height); + } + } +} diff --git a/core/jni/android_graphics_drawable_VectorDrawable.cpp b/core/jni/android_graphics_drawable_VectorDrawable.cpp index e5c4a2d0cfcf..50d86fffe29d 100644 --- a/core/jni/android_graphics_drawable_VectorDrawable.cpp +++ b/core/jni/android_graphics_drawable_VectorDrawable.cpp @@ -343,7 +343,7 @@ static void setTrimPathOffset(JNIEnv*, jobject, jlong fullPathPtr, jfloat trimPa } static const JNINativeMethod gMethods[] = { - {"nCreateRenderer", "!(J)J", (void*)createTree}, + {"nCreateTree", "!(J)J", (void*)createTree}, {"nSetRendererViewportSize", "!(JFF)V", (void*)setTreeViewportSize}, {"nSetRootAlpha", "!(JF)Z", (void*)setRootAlpha}, {"nGetRootAlpha", "!(J)F", (void*)getRootAlpha}, diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp index 90d69d256ec7..5c961d96cf56 100644 --- a/core/jni/android_hardware_location_ContextHubService.cpp +++ b/core/jni/android_hardware_location_ContextHubService.cpp @@ -370,10 +370,6 @@ int handle_os_message(uint32_t msgType, uint32_t hubHandle, retVal = 0; break; - case CONTEXT_HUB_LOAD_OS: - retVal = 0; - break; - default: retVal = -1; break; diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 3d6520903a7b..faa41921ba11 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -663,6 +663,14 @@ static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env, proxy->setContentDrawBounds(left, top, right, bottom); } +static jboolean android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, + jobject clazz, jobject jsurface, jobject jbitmap) { + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); + sp<Surface> surface = android_view_Surface_getSurface(env, jsurface); + return RenderProxy::copySurfaceInto(surface, &bitmap); +} + // ---------------------------------------------------------------------------- // FrameMetricsObserver // ---------------------------------------------------------------------------- @@ -768,6 +776,8 @@ static const JNINativeMethod gMethods[] = { { "nRemoveFrameMetricsObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver }, + { "nCopySurfaceInto", "(Landroid/view/Surface;Landroid/graphics/Bitmap;)Z", + (void*)android_view_ThreadedRenderer_copySurfaceInto }, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 9a2e39c752de..778f79761441 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -420,6 +420,7 @@ <protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" /> <protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" /> <protected-broadcast android:name="com.android.server.action.UPDATE_TWILIGHT_STATE" /> + <protected-broadcast android:name="com.android.server.action.RESET_TWILIGHT_AUTO" /> <protected-broadcast android:name="com.android.server.device_idle.STEP_IDLE_STATE" /> <protected-broadcast android:name="com.android.server.device_idle.STEP_LIGHT_IDLE_STATE" /> <protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" /> diff --git a/core/res/res/drawable-hdpi/ic_launcher_android.png b/core/res/res/drawable-hdpi/ic_launcher_android.png Binary files differindex cce518787b58..2e9b196c9625 100644 --- a/core/res/res/drawable-hdpi/ic_launcher_android.png +++ b/core/res/res/drawable-hdpi/ic_launcher_android.png diff --git a/core/res/res/drawable-ldpi/ic_launcher_android.png b/core/res/res/drawable-ldpi/ic_launcher_android.png Binary files differindex 628a8de9a0bb..245e4b771f17 100644 --- a/core/res/res/drawable-ldpi/ic_launcher_android.png +++ b/core/res/res/drawable-ldpi/ic_launcher_android.png diff --git a/core/res/res/drawable-mdpi/ic_launcher_android.png b/core/res/res/drawable-mdpi/ic_launcher_android.png Binary files differindex 6a97d5b79a28..baacd4f23e43 100644 --- a/core/res/res/drawable-mdpi/ic_launcher_android.png +++ b/core/res/res/drawable-mdpi/ic_launcher_android.png diff --git a/core/res/res/drawable-xhdpi/ic_launcher_android.png b/core/res/res/drawable-xhdpi/ic_launcher_android.png Binary files differindex b1097d6a6279..00b69a53a62b 100644 --- a/core/res/res/drawable-xhdpi/ic_launcher_android.png +++ b/core/res/res/drawable-xhdpi/ic_launcher_android.png diff --git a/core/res/res/drawable-xxhdpi/ic_launcher_android.png b/core/res/res/drawable-xxhdpi/ic_launcher_android.png Binary files differnew file mode 100644 index 000000000000..ad05cd5b337b --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_launcher_android.png diff --git a/core/res/res/drawable/ic_input_extract_action_done.xml b/core/res/res/drawable/ic_input_extract_action_done.xml new file mode 100644 index 000000000000..f6e872eeb694 --- /dev/null +++ b/core/res/res/drawable/ic_input_extract_action_done.xml @@ -0,0 +1,19 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector android:height="24dp" android:viewportHeight="48.0" + android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFF" android:pathData="M18,32.34L9.66,24l-2.83,2.83L18,38l24,-24 -2.83,-2.83z"/> +</vector> diff --git a/core/res/res/drawable/ic_input_extract_action_go.xml b/core/res/res/drawable/ic_input_extract_action_go.xml new file mode 100644 index 000000000000..edbc826b1cb2 --- /dev/null +++ b/core/res/res/drawable/ic_input_extract_action_go.xml @@ -0,0 +1,19 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector android:height="24dp" android:viewportHeight="48.0" + android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFF" android:pathData="M6,22h28.34l-7.17,-7.17L30,12l12,12 -12,12 -2.83,-2.83L34.34,26H6z"/> +</vector> diff --git a/core/res/res/drawable/ic_input_extract_action_next.xml b/core/res/res/drawable/ic_input_extract_action_next.xml new file mode 100644 index 000000000000..ffef3466f52b --- /dev/null +++ b/core/res/res/drawable/ic_input_extract_action_next.xml @@ -0,0 +1,19 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector android:height="24dp" android:viewportHeight="48.0" + android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFF" android:pathData="M23.17,14.83L30.34,22H2v4h28.34l-7.17,7.17L26,36l12,-12 -12,-12 -2.83,2.83zM40,12v24h4V12h-4z"/> +</vector> diff --git a/core/res/res/drawable/ic_input_extract_action_previous.xml b/core/res/res/drawable/ic_input_extract_action_previous.xml new file mode 100644 index 000000000000..89777b0471b0 --- /dev/null +++ b/core/res/res/drawable/ic_input_extract_action_previous.xml @@ -0,0 +1,19 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector android:height="24dp" android:viewportHeight="48.0" + android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFF" android:pathData="M22.83,14.83L15.66,22H44v4H15.66l7.17,7.17L20,36 8,24l12,-12 2.83,2.83zM6,12v24H2V12h4z"/> +</vector> diff --git a/core/res/res/drawable/ic_input_extract_action_return.xml b/core/res/res/drawable/ic_input_extract_action_return.xml new file mode 100644 index 000000000000..cb2de5ad8185 --- /dev/null +++ b/core/res/res/drawable/ic_input_extract_action_return.xml @@ -0,0 +1,19 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector android:height="24dp" android:viewportHeight="48.0" + android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFF" android:pathData="M38,14v8H11.66l7.17,-7.17L16,12 4,24l12,12 2.83,-2.83L11.66,26H42V14z"/> +</vector> diff --git a/core/res/res/drawable/ic_input_extract_action_search.xml b/core/res/res/drawable/ic_input_extract_action_search.xml new file mode 100644 index 000000000000..dcbcdbfcba62 --- /dev/null +++ b/core/res/res/drawable/ic_input_extract_action_search.xml @@ -0,0 +1,19 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector android:height="24dp" android:viewportHeight="48.0" + android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFF" android:pathData="M31,28h-1.59l-0.55,-0.55C30.82,25.18 32,22.23 32,19c0,-7.18 -5.82,-13 -13,-13S6,11.82 6,19s5.82,13 13,13c3.23,0 6.18,-1.18 8.45,-3.13l0.55,0.55L28,31l10,9.98L40.98,38 31,28zM19,28c-4.97,0 -9,-4.03 -9,-9s4.03,-9 9,-9 9,4.03 9,9 -4.03,9 -9,9z"/> +</vector> diff --git a/core/res/res/drawable/ic_input_extract_action_send.xml b/core/res/res/drawable/ic_input_extract_action_send.xml new file mode 100644 index 000000000000..6494bee5f4cd --- /dev/null +++ b/core/res/res/drawable/ic_input_extract_action_send.xml @@ -0,0 +1,24 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="36dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:pathData="M4.02,42L46,24 4.02,6 4,20l30,4 -30,4z" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/core/res/res/drawable/input_extract_action_bg_material_dark.xml b/core/res/res/drawable/input_extract_action_bg_material_dark.xml new file mode 100644 index 000000000000..9c6a6c3d190a --- /dev/null +++ b/core/res/res/drawable/input_extract_action_bg_material_dark.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/input_extract_action_bg_pressed_material_dark" + android:state_pressed="true"/> + <item android:drawable="@drawable/input_extract_action_bg_normal_material_dark"/> +</selector> diff --git a/core/res/res/drawable/input_extract_action_bg_normal_material_dark.xml b/core/res/res/drawable/input_extract_action_bg_normal_material_dark.xml new file mode 100644 index 000000000000..8449978b590a --- /dev/null +++ b/core/res/res/drawable/input_extract_action_bg_normal_material_dark.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 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. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> + <solid android:color="@color/material_deep_teal_200"/> +</shape> diff --git a/core/res/res/drawable/input_extract_action_bg_pressed_material_dark.xml b/core/res/res/drawable/input_extract_action_bg_pressed_material_dark.xml new file mode 100644 index 000000000000..adade10437e2 --- /dev/null +++ b/core/res/res/drawable/input_extract_action_bg_pressed_material_dark.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 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. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> + <solid android:color="@color/material_deep_teal_100"/> +</shape> diff --git a/core/res/res/layout-watch/input_method_extract_view.xml b/core/res/res/layout-watch/input_method_extract_view.xml new file mode 100644 index 000000000000..e3cd2ce7c1e8 --- /dev/null +++ b/core/res/res/layout-watch/input_method_extract_view.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 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. +--> +<android.inputmethodservice.CompactExtractEditLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical" + android:baselineAligned="false"> + + <android.inputmethodservice.ExtractEditText + android:id="@id/inputExtractEditText" + android:layout_width="0dp" + android:layout_height="24dp" + android:background="@null" + android:singleLine="true" + android:inputType="text" + android:layout_weight="1" + android:fontFamily="sans-serif-condensed-light" + android:textColor="@color/primary_text_default_material_dark" + android:textColorHighlight="@color/accent_material_dark" + android:textSize="18dp" + android:cursorVisible="false" + android:gravity="bottom|right" + /> + + <FrameLayout + android:id="@id/inputExtractAccessories" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:visibility="visible"> + <ImageButton + android:id="@id/inputExtractAction" + android:layout_width="@dimen/input_extract_action_button_width" + android:layout_height="@dimen/input_extract_action_button_width" + android:background="@drawable/input_extract_action_bg_material_dark" + android:padding="4dp" + android:scaleType="centerInside" /> + </FrameLayout> +</android.inputmethodservice.CompactExtractEditLayout> diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml new file mode 100644 index 000000000000..7d718e0db991 --- /dev/null +++ b/core/res/res/layout/notification_template_material_messaging.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2016 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 + --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:tag="messaging" + > + <include layout="@layout/notification_template_header" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="top" + android:layout_marginTop="@dimen/notification_content_margin_top" + android:clipToPadding="false" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:paddingStart="@dimen/notification_content_margin_start" + android:paddingEnd="@dimen/notification_content_margin_end" + android:minHeight="@dimen/notification_min_content_height" + android:clipToPadding="false" + android:orientation="vertical" + > + <include layout="@layout/notification_template_part_line1" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <com.android.internal.widget.MessagingLinearLayout + android:id="@+id/notification_messaging" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="@dimen/notification_content_margin_bottom" + android:spacing="@dimen/notification_messaging_spacing" + android:maxHeight="212dp"> + <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0" + style="@style/Widget.Material.Notification.MessagingText" + /> + <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1" + style="@style/Widget.Material.Notification.MessagingText" + /> + <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2" + style="@style/Widget.Material.Notification.MessagingText" + /> + <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3" + style="@style/Widget.Material.Notification.MessagingText" + /> + <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4" + style="@style/Widget.Material.Notification.MessagingText" + /> + <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5" + style="@style/Widget.Material.Notification.MessagingText" + /> + <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6" + style="@style/Widget.Material.Notification.MessagingText" + /> + </com.android.internal.widget.MessagingLinearLayout> + </LinearLayout> + <include layout="@layout/notification_material_action_list" /> + </LinearLayout> + <include layout="@layout/notification_template_right_icon" /> +</FrameLayout> diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml index 15ccc6729792..b652127e85bd 100644 --- a/core/res/res/layout/notification_template_right_icon.xml +++ b/core/res/res/layout/notification_template_right_icon.xml @@ -16,9 +16,9 @@ --> <ImageView android:id="@+id/right_icon" xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="40dp" - android:layout_height="40dp" - android:layout_marginEnd="16dp" + android:layout_width="@dimen/notification_large_icon_width" + android:layout_height="@dimen/notification_large_icon_width" + android:layout_marginEnd="@dimen/notification_content_margin_end" android:layout_marginTop="36dp" android:layout_gravity="top|end" android:scaleType="centerCrop" diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml index 4b8640c91f70..5850e5090926 100644 --- a/core/res/res/layout/resolver_list.xml +++ b/core/res/res/layout/resolver_list.xml @@ -72,7 +72,9 @@ <TextView android:id="@+id/empty" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" + android:background="@color/white" + android:elevation="8dp" android:layout_alwaysShow="true" android:text="@string/noApplications" android:padding="32dp" diff --git a/core/res/res/values-round-watch/dimens.xml b/core/res/res/values-round-watch/dimens.xml new file mode 100644 index 000000000000..a12f49929929 --- /dev/null +++ b/core/res/res/values-round-watch/dimens.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 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. +--> +<resources> + <!-- each of these are relative to the display size --> + <item name="input_extract_layout_height" type="fraction">25.2%</item> + <item name="input_extract_layout_padding_left" type="fraction">7.5%</item> + <item name="input_extract_layout_padding_left_no_action" type="fraction">@fraction/input_extract_layout_padding_right</item> + <item name="input_extract_layout_padding_right" type="fraction">21.4%</item> + <item name="input_extract_text_margin_bottom" type="fraction">5.5%</item> + <item name="input_extract_action_margin_bottom" type="fraction">2.1%</item> + <item name="input_extract_action_button_width" type="dimen">32dp</item> + <item name="input_extract_action_button_height" type="dimen">32dp</item> +</resources> diff --git a/core/res/res/values-w170dp-notround-watch/dimens.xml b/core/res/res/values-w170dp-notround-watch/dimens.xml new file mode 100644 index 000000000000..c91cbc197c56 --- /dev/null +++ b/core/res/res/values-w170dp-notround-watch/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 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. +--> +<resources> + <!-- each of these are relative to the display size --> + <item name="input_extract_layout_padding_right" type="fraction">7.5%</item> +</resources> diff --git a/core/res/res/values-watch/dimens.xml b/core/res/res/values-watch/dimens.xml new file mode 100644 index 000000000000..f103aa91dcfe --- /dev/null +++ b/core/res/res/values-watch/dimens.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 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. +--> +<resources> + <!-- each of these are relative to the display size --> + <item name="input_extract_layout_height" type="fraction">17.5%</item> + <item name="input_extract_layout_padding_left" type="fraction">3.6%</item> + <item name="input_extract_layout_padding_left_no_action" type="fraction">@fraction/input_extract_layout_padding_right</item> + <item name="input_extract_layout_padding_right" type="fraction">2.5%</item> + <item name="input_extract_text_margin_bottom" type="fraction">0%</item> + <item name="input_extract_action_margin_bottom" type="fraction">0%</item> + <item name="input_extract_action_button_width" type="dimen">24dp</item> + <item name="input_extract_action_button_height" type="dimen">24dp</item> +</resources> diff --git a/core/res/res/values-watch/themes.xml b/core/res/res/values-watch/themes.xml index 756a94b4185c..6d6065f68ed4 100644 --- a/core/res/res/values-watch/themes.xml +++ b/core/res/res/values-watch/themes.xml @@ -18,6 +18,7 @@ <style name="Theme.Dialog.AppError" parent="Theme.Micro.Dialog.AppError" /> <style name="Theme.Holo.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" /> <style name="Theme.Holo.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" /> + <style name="Theme.InputMethod" parent="Theme.Micro.InputMethod" /> <style name="Theme.Material.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" /> <style name="Theme.Material.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" /> </resources> diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml index 61753b1f0211..66509fb86eaa 100644 --- a/core/res/res/values-watch/themes_device_defaults.xml +++ b/core/res/res/values-watch/themes_device_defaults.xml @@ -19,14 +19,16 @@ <style name="Theme.DeviceDefault.Dialog" parent="Theme.Micro.Dialog" /> <style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Micro.Dialog" /> <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" /> + <style name="Theme.DeviceDefault.InputMethod" parent="Theme.Micro.InputMethod" /> + <style name="Theme.DeviceDefault.Panel" parent="Theme.Micro.Panel" /> <style name="Theme.DeviceDefault.Light" parent="Theme.Micro.Light" /> <style name="Theme.DeviceDefault.Light.NoActionBar" parent="Theme.Micro.Light" /> <style name="Theme.DeviceDefault.Light.DarkActionBar" parent="Theme.Micro.Light" /> <style name="Theme.DeviceDefault.Light.Dialog" parent="Theme.Micro.Dialog" /> <style name="Theme.DeviceDefault.Light.DialogWhenLarge" parent="Theme.Micro.Dialog" /> <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" /> + <style name="Theme.DeviceDefault.Light.Panel" parent="Theme.Micro.Light.Panel" /> <style name="Theme.DeviceDefault.Settings" parent="Theme.Micro" /> <style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Micro" /> - </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0ed1f133c627..a320ef63face 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8129,6 +8129,11 @@ i <attr name="maxCollapsedHeightSmall" format="dimension" /> </declare-styleable> + <declare-styleable name="MessagingLinearLayout"> + <attr name="maxHeight" /> + <attr name="spacing" /> + </declare-styleable> + <declare-styleable name="ResolverDrawerLayout_LayoutParams"> <attr name="layout_alwaysShow" format="boolean" /> <attr name="layout_ignoreOffset" format="boolean" /> diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index 7399fa9d58ce..c8ca11689299 100644 --- a/core/res/res/values/colors_material.xml +++ b/core/res/res/values/colors_material.xml @@ -75,7 +75,9 @@ <color name="material_grey_100">#fff5f5f5</color> <color name="material_grey_50">#fffafafa</color> + <color name="material_deep_teal_100">#ffb2dfdb</color> <color name="material_deep_teal_200">#ff80cbc4</color> + <color name="material_deep_teal_300">#ff4db6ac</color> <color name="material_deep_teal_500">#ff009688</color> <color name="material_blue_grey_800">#ff37474f</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index cb551e8df695..aada05d4bf82 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -520,12 +520,6 @@ <!-- Wifi driver supports batched scan --> <bool translatable="false" name="config_wifi_batched_scan_supported">false</bool> - <!-- Wifi HAL supported PNO --> - <bool translatable="false" name="config_wifi_hal_pno_enable">false</bool> - - <!-- Wifi SSID white list (can't be enabled if config_wifi_hal_pno_enable is not) --> - <bool translatable="false" name="config_wifi_ssid_white_list_enable">true</bool> - <!-- Idle Receive current for wifi radio. 0 by default--> <integer translatable="false" name="config_wifi_idle_receive_cur_ma">0</integer> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index dd54d57058af..9178305159b9 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -162,9 +162,9 @@ <dimen name="notification_min_height">92dp</dimen> <!-- The width of the big icons in notifications. --> - <dimen name="notification_large_icon_width">64dp</dimen> + <dimen name="notification_large_icon_width">40dp</dimen> <!-- The width of the big icons in notifications. --> - <dimen name="notification_large_icon_height">64dp</dimen> + <dimen name="notification_large_icon_height">40dp</dimen> <!-- The minimum width of the app name in the header if it shrinks --> <dimen name="notification_header_shrink_min_width">72dp</dimen> @@ -181,6 +181,9 @@ <!-- The margin of the content to an image--> <dimen name="notification_content_image_margin_end">8dp</dimen> + <!-- The spacing between messages in Notification.MessagingStyle --> + <dimen name="notification_messaging_spacing">6dp</dimen> + <!-- Preferred width of the search view. --> <dimen name="search_view_preferred_width">320dip</dimen> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 2420c1a96a6c..8a33406eeaa7 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -456,6 +456,14 @@ please see styles_device_defaults.xml. <style name="Widget.Material.Notification.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" /> + <style name="Widget.Material.Notification.MessagingText" parent="Widget.Material.Light.TextView"> + <item name="layout_width">match_parent</item> + <item name="layout_height">wrap_content</item> + <item name="ellipsize">end</item> + <item name="visibility">gone</item> + <item name="textAppearance">@style/TextAppearance.Material.Notification</item> + </style> + <!-- Widget Styles --> <style name="Material"/> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 29c6951557ec..03d21929dd01 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -390,9 +390,7 @@ <java-symbol type="integer" name="config_wifi_supplicant_scan_interval" /> <java-symbol type="integer" name="config_wifi_no_network_periodic_scan_interval" /> <java-symbol type="integer" name="config_wifi_scan_interval_p2p_connected" /> - <java-symbol type="bool" name="config_wifi_hal_pno_enable" /> <java-symbol type="integer" name="config_windowOutsetBottom" /> - <java-symbol type="bool" name="config_wifi_ssid_white_list_enable" /> <java-symbol type="integer" name="db_connection_pool_size" /> <java-symbol type="integer" name="db_journal_size_limit" /> <java-symbol type="integer" name="db_wal_autocheckpoint" /> @@ -2496,6 +2494,8 @@ <java-symbol type="bool" name="config_strongAuthRequiredOnBoot" /> <java-symbol type="layout" name="app_anr_dialog" /> + <java-symbol type="layout" name="notification_template_material_messaging" /> + <java-symbol type="id" name="aerr_wait" /> <java-symbol type="id" name="notification_content_container" /> @@ -2525,11 +2525,34 @@ <java-symbol type="string" name="carrier_app_notification_text" /> <java-symbol type="string" name="negative_duration" /> + <java-symbol type="dimen" name="notification_messaging_spacing" /> + <!-- WallpaperManager config --> <java-symbol type="string" name="config_wallpaperCropperPackage" /> <java-symbol type="id" name="textSpacerNoTitle" /> <java-symbol type="id" name="titleDividerNoCustom" /> + <java-symbol type="id" name="notification_messaging" /> + <java-symbol type="bool" name="config_sustainedPerformanceModeSupported" /> + + <!-- Wearable input extract edit view --> + <java-symbol type="drawable" name="ic_input_extract_action_go" /> + <java-symbol type="drawable" name="ic_input_extract_action_search" /> + <java-symbol type="drawable" name="ic_input_extract_action_send" /> + <java-symbol type="drawable" name="ic_input_extract_action_next" /> + <java-symbol type="drawable" name="ic_input_extract_action_done" /> + <java-symbol type="drawable" name="ic_input_extract_action_previous" /> + <java-symbol type="drawable" name="ic_input_extract_action_return" /> + + <java-symbol type="fraction" name="input_extract_layout_height" /> + <java-symbol type="fraction" name="input_extract_layout_padding_left" /> + <java-symbol type="fraction" name="input_extract_layout_padding_left_no_action" /> + <java-symbol type="fraction" name="input_extract_layout_padding_right" /> + <java-symbol type="fraction" name="input_extract_text_margin_bottom" /> + <java-symbol type="fraction" name="input_extract_action_margin_bottom" /> + + <java-symbol type="dimen" name="input_extract_action_button_width" /> + <java-symbol type="dimen" name="input_extract_action_button_height" /> </resources> diff --git a/core/res/res/values/themes_micro.xml b/core/res/res/values/themes_micro.xml index 478d66c767c0..25a6e006c031 100644 --- a/core/res/res/values/themes_micro.xml +++ b/core/res/res/values/themes_micro.xml @@ -83,4 +83,18 @@ <item name="fontFamily">sans-serif-condensed-light</item> <item name="textColor">@color/micro_text_light</item> </style> + + <style name="Theme.Micro.Panel" parent="Theme.Material.Panel" /> + <style name="Theme.Micro.Light.Panel" parent="Theme.Material.Light.Panel" /> + + <!-- Default theme for material style input methods, which is used by the + {@link android.inputmethodservice.InputMethodService} class. + This inherits from Theme.Panel, but sets up IME appropriate animations + and a few custom attributes. --> + <style name="Theme.Micro.InputMethod" parent="Theme.Micro.Panel"> + <item name="windowAnimationStyle">@style/Animation.InputMethod</item> + <item name="imeFullscreenBackground">#1e282c</item> + <item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item> + <item name="imeExtractExitAnimation">@anim/input_method_extract_exit</item> + </style> </resources> diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java index 348f8fd43867..edb30825a341 100644 --- a/core/tests/coretests/src/android/util/PatternsTest.java +++ b/core/tests/coretests/src/android/util/PatternsTest.java @@ -419,6 +419,36 @@ public class PatternsTest extends TestCase { Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); } + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchUnicodeSpaces() throws Exception { + String part1 = "http://and"; + String part2 = "roid"; + String[] emptySpaces = new String[]{ + "\u00A0", // no-break space + "\u2000", // en quad + "\u2001", // em quad + "\u2002", // en space + "\u2003", // em space + "\u2004", // three-per-em space + "\u2005", // four-per-em space + "\u2006", // six-per-em space + "\u2007", // figure space + "\u2008", // punctuation space + "\u2009", // thin space + "\u200A", // hair space + "\u2028", // line separator + "\u2029", // paragraph separator + "\u202F", // narrow no-break space + "\u3000" // ideographic space + }; + + for (String emptySpace : emptySpaces) { + String url = part1 + emptySpace + part2; + assertFalse("Should not match empty space - code:" + emptySpace.codePointAt(0), + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + } + // Tests for Patterns.IP_ADDRESS @SmallTest diff --git a/docs/html/guide/topics/manifest/application-element.jd b/docs/html/guide/topics/manifest/application-element.jd index 5600b5c5c469..887b4eabe2b7 100644 --- a/docs/html/guide/topics/manifest/application-element.jd +++ b/docs/html/guide/topics/manifest/application-element.jd @@ -472,6 +472,8 @@ from {@link android.content.pm.ApplicationInfo#flags ApplicationInfo.flags} or {@link android.os.StrictMode.VmPolicy.Builder#detectCleartextNetwork() StrictMode.VmPolicy.Builder.detectCleartextNetwork()}. <p>This attribute was added in API level 23.</p> + +<p>This flag is ignored on Android N and above if an Android Network Security Config is present.</p> </dd> <dt><a name="vmSafeMode"></a>{@code android:vmSafeMode}</dt> diff --git a/graphics/java/android/graphics/PixelCopy.java b/graphics/java/android/graphics/PixelCopy.java new file mode 100644 index 000000000000..c5991264e555 --- /dev/null +++ b/graphics/java/android/graphics/PixelCopy.java @@ -0,0 +1,104 @@ +package android.graphics; + +import android.annotation.NonNull; +import android.os.Handler; +import android.view.Surface; +import android.view.SurfaceView; +import android.view.ThreadedRenderer; + +/** + * Provides a mechanisms to issue pixel copy requests to allow for copy + * operations from {@link Surface} to {@link Bitmap} + * + * @hide + */ +public final class PixelCopy { + /** + * Contains the result of a pixel copy request + */ + public static final class Response { + /** + * Indicates whether or not the copy request completed successfully. + * If this is true, then {@link #bitmap} contains the result of the copy. + * If this is false, {@link #bitmap} is unmodified from the originally + * passed destination. + * + * For example a request might fail if the source is protected content + * so copies are not allowed. Similarly if the source has nothing to + * copy from, because either no frames have been produced yet or because + * it has already been destroyed, then this will be false. + */ + public boolean success; + + /** + * The output bitmap. This is always the same object that was passed + * to request() as the 'dest' bitmap. If {@link #success} is true this + * contains a copy of the pixels of the source object. If {@link #success} + * is false then this is unmodified. + */ + @NonNull + public Bitmap bitmap; + } + + public interface OnPixelCopyFinished { + /** + * Callback for when a pixel copy request has completed. This will be called + * regardless of whether the copy succeeded or failed. + * + * @param response Contains the result of the copy request which includes + * whether or not the copy was successful. + */ + void onPixelCopyFinished(PixelCopy.Response response); + } + + /** + * Requests for the display content of a {@link SurfaceView} to be copied + * into a provided {@link Bitmap}. + * + * The contents of the source will be scaled to fit exactly inside the bitmap. + * The pixel format of the source buffer will be converted, as part of the copy, + * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer + * in the SurfaceView's Surface will be used as the source of the copy. + * + * @param source The source from which to copy + * @param dest The destination of the copy. The source will be scaled to + * match the width, height, and format of this bitmap. + * @param listener Callback for when the pixel copy request completes + * @param listenerThread The callback will be invoked on this Handler when + * the copy is finished. + */ + public static void request(@NonNull SurfaceView source, @NonNull Bitmap dest, + @NonNull OnPixelCopyFinished listener, @NonNull Handler listenerThread) { + request(source.getHolder().getSurface(), dest, listener, listenerThread); + } + + /** + * Requests a copy of the pixels from a {@link Surface} to be copied into + * a provided {@link Bitmap}. + * + * The contents of the source will be scaled to fit exactly inside the bitmap. + * The pixel format of the source buffer will be converted, as part of the copy, + * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer + * in the Surface will be used as the source of the copy. + * + * @param source The source from which to copy + * @param dest The destination of the copy. The source will be scaled to + * match the width, height, and format of this bitmap. + * @param listener Callback for when the pixel copy request completes + * @param listenerThread The callback will be invoked on this Handler when + * the copy is finished. + */ + public static void request(@NonNull Surface source, @NonNull Bitmap dest, + @NonNull OnPixelCopyFinished listener, @NonNull Handler listenerThread) { + // TODO: Make this actually async and fast and cool and stuff + final PixelCopy.Response response = new PixelCopy.Response(); + response.success = ThreadedRenderer.copySurfaceInto(source, dest); + response.bitmap = dest; + listenerThread.post(new Runnable() { + @Override + public void run() { + listener.onPixelCopyFinished(response); + } + }); + } +} diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index e75fb9870e0b..0e457800aac9 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -534,13 +534,17 @@ public class VectorDrawable extends Drawable { public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererRefBase != null) { + if (mVectorState.mRootGroup != null || mVectorState.mNativeTree != null) { // This VD has been used to display other VD resource content, clean up. + if (mVectorState.mRootGroup != null) { + // Remove child nodes' reference to tree + mVectorState.mRootGroup.setTree(null); + } mVectorState.mRootGroup = new VGroup(); - if (mVectorState.mNativeRendererRefBase != null) { - mVectorState.mNativeRendererRefBase.release(); + if (mVectorState.mNativeTree != null) { + mVectorState.mNativeTree.release(); } - mVectorState.createNativeRenderer(mVectorState.mRootGroup.mNativePtr); + mVectorState.createNativeTree(mVectorState.mRootGroup); } final VectorDrawableState state = mVectorState; state.setDensity(Drawable.resolveDensity(r, 0)); @@ -734,7 +738,7 @@ public class VectorDrawable extends Drawable { Insets mOpticalInsets = Insets.NONE; String mRootName = null; VGroup mRootGroup; - VirtualRefBasePtr mNativeRendererRefBase = null; + VirtualRefBasePtr mNativeTree = null; int mDensity = DisplayMetrics.DENSITY_DEFAULT; final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>(); @@ -755,7 +759,7 @@ public class VectorDrawable extends Drawable { mTintMode = copy.mTintMode; mAutoMirrored = copy.mAutoMirrored; mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); - createNativeRenderer(mRootGroup.mNativePtr); + createNativeTree(mRootGroup); mBaseWidth = copy.mBaseWidth; mBaseHeight = copy.mBaseHeight; @@ -770,15 +774,16 @@ public class VectorDrawable extends Drawable { } } - private void createNativeRenderer(long rootGroupPtr) { - mNativeRendererRefBase = new VirtualRefBasePtr(nCreateRenderer(rootGroupPtr)); + private void createNativeTree(VGroup rootGroup) { + mNativeTree = new VirtualRefBasePtr(nCreateTree(rootGroup.mNativePtr)); + mRootGroup.setTree(mNativeTree); } long getNativeRenderer() { - if (mNativeRendererRefBase == null) { + if (mNativeTree == null) { return 0; } - return mNativeRendererRefBase.get(); + return mNativeTree.get(); } public boolean canReuseCache() { @@ -817,7 +822,7 @@ public class VectorDrawable extends Drawable { public VectorDrawableState() { mRootGroup = new VGroup(); - createNativeRenderer(mRootGroup.mNativePtr); + createNativeTree(mRootGroup); } @Override @@ -881,16 +886,16 @@ public class VectorDrawable extends Drawable { * has changed. */ public boolean setAlpha(float alpha) { - return nSetRootAlpha(mNativeRendererRefBase.get(), alpha); + return nSetRootAlpha(mNativeTree.get(), alpha); } @SuppressWarnings("unused") public float getAlpha() { - return nGetRootAlpha(mNativeRendererRefBase.get()); + return nGetRootAlpha(mNativeTree.get()); } } - static class VGroup implements VObject { + static class VGroup extends VObject { private static final int ROTATE_INDEX = 0; private static final int PIVOT_X_INDEX = 1; private static final int PIVOT_Y_INDEX = 2; @@ -984,11 +989,18 @@ public class VectorDrawable extends Drawable { public void addChild(VObject child) { nAddChild(mNativePtr, child.getNativePtr()); mChildren.add(child); - mIsStateful |= child.isStateful(); } @Override + public void setTree(VirtualRefBasePtr treeRoot) { + super.setTree(treeRoot); + for (int i = 0; i < mChildren.size(); i++) { + mChildren.get(i).setTree(treeRoot); + } + } + + @Override public long getNativePtr() { return mNativePtr; } @@ -1101,79 +1113,93 @@ public class VectorDrawable extends Drawable { /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ @SuppressWarnings("unused") public float getRotation() { - return nGetRotation(mNativePtr); + return isTreeValid() ? nGetRotation(mNativePtr) : 0; } @SuppressWarnings("unused") public void setRotation(float rotation) { - nSetRotation(mNativePtr, rotation); + if (isTreeValid()) { + nSetRotation(mNativePtr, rotation); + } } @SuppressWarnings("unused") public float getPivotX() { - return nGetPivotX(mNativePtr); + return isTreeValid() ? nGetPivotX(mNativePtr) : 0; } @SuppressWarnings("unused") public void setPivotX(float pivotX) { - nSetPivotX(mNativePtr, pivotX); + if (isTreeValid()) { + nSetPivotX(mNativePtr, pivotX); + } } @SuppressWarnings("unused") public float getPivotY() { - return nGetPivotY(mNativePtr); + return isTreeValid() ? nGetPivotY(mNativePtr) : 0; } @SuppressWarnings("unused") public void setPivotY(float pivotY) { - nSetPivotY(mNativePtr, pivotY); + if (isTreeValid()) { + nSetPivotY(mNativePtr, pivotY); + } } @SuppressWarnings("unused") public float getScaleX() { - return nGetScaleX(mNativePtr); + return isTreeValid() ? nGetScaleX(mNativePtr) : 0; } @SuppressWarnings("unused") public void setScaleX(float scaleX) { - nSetScaleX(mNativePtr, scaleX); + if (isTreeValid()) { + nSetScaleX(mNativePtr, scaleX); + } } @SuppressWarnings("unused") public float getScaleY() { - return nGetScaleY(mNativePtr); + return isTreeValid() ? nGetScaleY(mNativePtr) : 0; } @SuppressWarnings("unused") public void setScaleY(float scaleY) { - nSetScaleY(mNativePtr, scaleY); + if (isTreeValid()) { + nSetScaleY(mNativePtr, scaleY); + } } @SuppressWarnings("unused") public float getTranslateX() { - return nGetTranslateX(mNativePtr); + return isTreeValid() ? nGetTranslateX(mNativePtr) : 0; } @SuppressWarnings("unused") public void setTranslateX(float translateX) { - nSetTranslateX(mNativePtr, translateX); + if (isTreeValid()) { + nSetTranslateX(mNativePtr, translateX); + } } @SuppressWarnings("unused") public float getTranslateY() { - return nGetTranslateY(mNativePtr); + return isTreeValid() ? nGetTranslateY(mNativePtr) : 0; } @SuppressWarnings("unused") public void setTranslateY(float translateY) { - nSetTranslateY(mNativePtr, translateY); + if (isTreeValid()) { + nSetTranslateY(mNativePtr, translateY); + } } } /** * Common Path information for clip path and normal path. */ - static abstract class VPath implements VObject { + static abstract class VPath extends VObject { protected PathParser.PathData mPathData = null; String mPathName; @@ -1203,7 +1229,9 @@ public class VectorDrawable extends Drawable { @SuppressWarnings("unused") public void setPathData(PathParser.PathData pathData) { mPathData.setPathData(pathData); - nSetPathData(getNativePtr(), mPathData.getNativePtr()); + if (isTreeValid()) { + nSetPathData(getNativePtr(), mPathData.getNativePtr()); + } } } @@ -1549,97 +1577,120 @@ public class VectorDrawable extends Drawable { /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ @SuppressWarnings("unused") int getStrokeColor() { - return nGetStrokeColor(mNativePtr); + return isTreeValid() ? nGetStrokeColor(mNativePtr) : 0; } @SuppressWarnings("unused") void setStrokeColor(int strokeColor) { mStrokeColors = null; - nSetStrokeColor(mNativePtr, strokeColor); + if (isTreeValid()) { + nSetStrokeColor(mNativePtr, strokeColor); + } } @SuppressWarnings("unused") float getStrokeWidth() { - return nGetStrokeWidth(mNativePtr); + return isTreeValid() ? nGetStrokeWidth(mNativePtr) : 0; } @SuppressWarnings("unused") void setStrokeWidth(float strokeWidth) { - nSetStrokeWidth(mNativePtr, strokeWidth); + if (isTreeValid()) { + nSetStrokeWidth(mNativePtr, strokeWidth); + } } @SuppressWarnings("unused") float getStrokeAlpha() { - return nGetStrokeAlpha(mNativePtr); + return isTreeValid() ? nGetStrokeAlpha(mNativePtr) : 0; } @SuppressWarnings("unused") void setStrokeAlpha(float strokeAlpha) { - nSetStrokeAlpha(mNativePtr, strokeAlpha); + if (isTreeValid()) { + nSetStrokeAlpha(mNativePtr, strokeAlpha); + } } @SuppressWarnings("unused") int getFillColor() { - return nGetFillColor(mNativePtr); + return isTreeValid() ? nGetFillColor(mNativePtr) : 0; } @SuppressWarnings("unused") void setFillColor(int fillColor) { mFillColors = null; - nSetFillColor(mNativePtr, fillColor); + if (isTreeValid()) { + nSetFillColor(mNativePtr, fillColor); + } } @SuppressWarnings("unused") float getFillAlpha() { - return nGetFillAlpha(mNativePtr); + return isTreeValid() ? nGetFillAlpha(mNativePtr) : 0; } @SuppressWarnings("unused") void setFillAlpha(float fillAlpha) { - nSetFillAlpha(mNativePtr, fillAlpha); + if (isTreeValid()) { + nSetFillAlpha(mNativePtr, fillAlpha); + } } @SuppressWarnings("unused") float getTrimPathStart() { - return nGetTrimPathStart(mNativePtr); + return isTreeValid() ? nGetTrimPathStart(mNativePtr) : 0; } @SuppressWarnings("unused") void setTrimPathStart(float trimPathStart) { - nSetTrimPathStart(mNativePtr, trimPathStart); + if (isTreeValid()) { + nSetTrimPathStart(mNativePtr, trimPathStart); + } } @SuppressWarnings("unused") float getTrimPathEnd() { - return nGetTrimPathEnd(mNativePtr); + return isTreeValid() ? nGetTrimPathEnd(mNativePtr) : 0; } @SuppressWarnings("unused") void setTrimPathEnd(float trimPathEnd) { - nSetTrimPathEnd(mNativePtr, trimPathEnd); + if (isTreeValid()) { + nSetTrimPathEnd(mNativePtr, trimPathEnd); + } } @SuppressWarnings("unused") float getTrimPathOffset() { - return nGetTrimPathOffset(mNativePtr); + return isTreeValid() ? nGetTrimPathOffset(mNativePtr) : 0; } @SuppressWarnings("unused") void setTrimPathOffset(float trimPathOffset) { - nSetTrimPathOffset(mNativePtr, trimPathOffset); + if (isTreeValid()) { + nSetTrimPathOffset(mNativePtr, trimPathOffset); + } } } - interface VObject { - long getNativePtr(); - void inflate(Resources r, AttributeSet attrs, Theme theme); - boolean canApplyTheme(); - void applyTheme(Theme t); - boolean onStateChange(int[] state); - boolean isStateful(); + abstract static class VObject { + VirtualRefBasePtr mTreePtr = null; + boolean isTreeValid() { + return mTreePtr != null && mTreePtr.get() != 0; + } + void setTree(VirtualRefBasePtr ptr) { + mTreePtr = ptr; + } + abstract long getNativePtr(); + abstract void inflate(Resources r, AttributeSet attrs, Theme theme); + abstract boolean canApplyTheme(); + abstract void applyTheme(Theme t); + abstract boolean onStateChange(int[] state); + abstract boolean isStateful(); } - private static native long nCreateRenderer(long rootGroupPtr); + private static native long nCreateTree(long rootGroupPtr); private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth, float viewportHeight); private static native boolean nSetRootAlpha(long rendererPtr, float alpha); diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 0606b0b1a158..717a1e6eacc4 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -84,6 +84,7 @@ hwui_src_files := \ Properties.cpp \ PropertyValuesHolder.cpp \ PropertyValuesAnimatorSet.cpp \ + Readback.cpp \ RenderBufferCache.cpp \ RenderNode.cpp \ RenderProperties.cpp \ diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index 8f914acc5c76..a8ace8c10edd 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -19,6 +19,7 @@ #include <SkColor.h> #include <SkPaint.h> #include <SkPath.h> +#include <SkPathEffect.h> #include <SkRect.h> #include <utils/JenkinsHash.h> @@ -35,18 +36,34 @@ namespace android { namespace uirenderer { +template <class T> +static bool compareWidthHeight(const T& lhs, const T& rhs) { + return (lhs.mWidth == rhs.mWidth) && (lhs.mHeight == rhs.mHeight); +} + +static bool compareRoundRects(const PathDescription::Shape::RoundRect& lhs, + const PathDescription::Shape::RoundRect& rhs) { + return compareWidthHeight(lhs, rhs) && lhs.mRx == rhs.mRx && lhs.mRy == rhs.mRy; +} + +static bool compareArcs(const PathDescription::Shape::Arc& lhs, const PathDescription::Shape::Arc& rhs) { + return compareWidthHeight(lhs, rhs) && lhs.mStartAngle == rhs.mStartAngle && + lhs.mSweepAngle == rhs.mSweepAngle && lhs.mUseCenter == rhs.mUseCenter; +} + /////////////////////////////////////////////////////////////////////////////// // Cache entries /////////////////////////////////////////////////////////////////////////////// PathDescription::PathDescription() - : type(kShapeNone) + : type(ShapeType::None) , join(SkPaint::kDefault_Join) , cap(SkPaint::kDefault_Cap) , style(SkPaint::kFill_Style) , miter(4.0f) , strokeWidth(1.0f) , pathEffect(nullptr) { + // Shape bits should be set to zeroes, because they are used for hash calculation. memset(&shape, 0, sizeof(Shape)); } @@ -58,11 +75,12 @@ PathDescription::PathDescription(ShapeType type, const SkPaint* paint) , miter(paint->getStrokeMiter()) , strokeWidth(paint->getStrokeWidth()) , pathEffect(paint->getPathEffect()) { + // Shape bits should be set to zeroes, because they are used for hash calculation. memset(&shape, 0, sizeof(Shape)); } hash_t PathDescription::hash() const { - uint32_t hash = JenkinsHashMix(0, type); + uint32_t hash = JenkinsHashMix(0, static_cast<int>(type)); hash = JenkinsHashMix(hash, join); hash = JenkinsHashMix(hash, cap); hash = JenkinsHashMix(hash, style); @@ -73,6 +91,32 @@ hash_t PathDescription::hash() const { return JenkinsHashWhiten(hash); } +bool PathDescription::operator==(const PathDescription& rhs) const { + if (type != rhs.type) return false; + if (join != rhs.join) return false; + if (cap != rhs.cap) return false; + if (style != rhs.style) return false; + if (miter != rhs.miter) return false; + if (strokeWidth != rhs.strokeWidth) return false; + if (pathEffect != rhs.pathEffect) return false; + switch (type) { + case ShapeType::None: + return 0; + case ShapeType::Rect: + return compareWidthHeight(shape.rect, rhs.shape.rect); + case ShapeType::RoundRect: + return compareRoundRects(shape.roundRect, rhs.shape.roundRect); + case ShapeType::Circle: + return shape.circle.mRadius == rhs.shape.circle.mRadius; + case ShapeType::Oval: + return compareWidthHeight(shape.oval, rhs.shape.oval); + case ShapeType::Arc: + return compareArcs(shape.arc, rhs.shape.arc); + case ShapeType::Path: + return shape.path.mGenerationID == rhs.shape.path.mGenerationID; + } +} + /////////////////////////////////////////////////////////////////////////////// // Utilities /////////////////////////////////////////////////////////////////////////////// @@ -322,7 +366,7 @@ void PathCache::clearGarbage() { LruCache<PathDescription, PathTexture*>::Iterator iter(mCache); while (iter.next()) { const PathDescription& key = iter.key(); - if (key.type == kShapePath && key.shape.path.mGenerationID == generationID) { + if (key.type == ShapeType::Path && key.shape.path.mGenerationID == generationID) { pathsToRemove.push(key); } } @@ -336,7 +380,7 @@ void PathCache::clearGarbage() { } PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) { - PathDescription entry(kShapePath, paint); + PathDescription entry(ShapeType::Path, paint); entry.shape.path.mGenerationID = path->getGenerationID(); PathTexture* texture = mCache.get(entry); @@ -366,9 +410,8 @@ PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) { return texture; } -void PathCache::remove(const SkPath* path, const SkPaint* paint) -{ - PathDescription entry(kShapePath, paint); +void PathCache::remove(const SkPath* path, const SkPaint* paint) { + PathDescription entry(ShapeType::Path, paint); entry.shape.path.mGenerationID = path->getGenerationID(); mCache.remove(entry); } @@ -378,7 +421,7 @@ void PathCache::precache(const SkPath* path, const SkPaint* paint) { return; } - PathDescription entry(kShapePath, paint); + PathDescription entry(ShapeType::Path, paint); entry.shape.path.mGenerationID = path->getGenerationID(); PathTexture* texture = mCache.get(entry); @@ -417,7 +460,7 @@ void PathCache::precache(const SkPath* path, const SkPaint* paint) { PathTexture* PathCache::getRoundRect(float width, float height, float rx, float ry, const SkPaint* paint) { - PathDescription entry(kShapeRoundRect, paint); + PathDescription entry(ShapeType::RoundRect, paint); entry.shape.roundRect.mWidth = width; entry.shape.roundRect.mHeight = height; entry.shape.roundRect.mRx = rx; @@ -442,7 +485,7 @@ PathTexture* PathCache::getRoundRect(float width, float height, /////////////////////////////////////////////////////////////////////////////// PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) { - PathDescription entry(kShapeCircle, paint); + PathDescription entry(ShapeType::Circle, paint); entry.shape.circle.mRadius = radius; PathTexture* texture = get(entry); @@ -462,7 +505,7 @@ PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) { /////////////////////////////////////////////////////////////////////////////// PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) { - PathDescription entry(kShapeOval, paint); + PathDescription entry(ShapeType::Oval, paint); entry.shape.oval.mWidth = width; entry.shape.oval.mHeight = height; @@ -485,7 +528,7 @@ PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) /////////////////////////////////////////////////////////////////////////////// PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) { - PathDescription entry(kShapeRect, paint); + PathDescription entry(ShapeType::Rect, paint); entry.shape.rect.mWidth = width; entry.shape.rect.mHeight = height; @@ -509,7 +552,7 @@ PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) PathTexture* PathCache::getArc(float width, float height, float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) { - PathDescription entry(kShapeArc, paint); + PathDescription entry(ShapeType::Arc, paint); entry.shape.arc.mWidth = width; entry.shape.arc.mHeight = height; entry.shape.arc.mStartAngle = startAngle; diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index d2633aa427c5..6368ddd49966 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -25,6 +25,7 @@ #include "utils/Pair.h" #include <GLES2/gl2.h> +#include <SkPaint.h> #include <SkPath.h> #include <utils/LruCache.h> #include <utils/Mutex.h> @@ -108,18 +109,18 @@ private: sp<Task<SkBitmap*> > mTask; }; // struct PathTexture -enum ShapeType { - kShapeNone, - kShapeRect, - kShapeRoundRect, - kShapeCircle, - kShapeOval, - kShapeArc, - kShapePath +enum class ShapeType { + None, + Rect, + RoundRect, + Circle, + Oval, + Arc, + Path }; struct PathDescription { - DESCRIPTION_TYPE(PathDescription); + HASHABLE_TYPE(PathDescription); ShapeType type; SkPaint::Join join; SkPaint::Cap cap; @@ -159,8 +160,6 @@ struct PathDescription { PathDescription(); PathDescription(ShapeType shapeType, const SkPaint* paint); - - hash_t hash() const; }; /** diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp new file mode 100644 index 000000000000..d7df77c2f3ed --- /dev/null +++ b/libs/hwui/Readback.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "Readback.h" + +#include "Caches.h" +#include "Image.h" +#include "GlopBuilder.h" +#include "renderstate/RenderState.h" +#include "renderthread/EglManager.h" +#include "utils/GLUtils.h" + +#include <GLES2/gl2.h> +#include <ui/Fence.h> +#include <ui/GraphicBuffer.h> + +namespace android { +namespace uirenderer { + +bool Readback::copySurfaceInto(renderthread::RenderThread& renderThread, + Surface& surface, SkBitmap* bitmap) { + // TODO: Clean this up and unify it with LayerRenderer::copyLayer, + // of which most of this is copied from. + renderThread.eglManager().initialize(); + + Caches& caches = Caches::getInstance(); + RenderState& renderState = renderThread.renderState(); + int destWidth = bitmap->width(); + int destHeight = bitmap->height(); + if (destWidth > caches.maxTextureSize + || destHeight > caches.maxTextureSize) { + ALOGW("Can't copy surface into bitmap, %dx%d exceeds max texture size %d", + destWidth, destHeight, caches.maxTextureSize); + return false; + } + GLuint fbo = renderState.createFramebuffer(); + if (!fbo) { + ALOGW("Could not obtain an FBO"); + return false; + } + + SkAutoLockPixels alp(*bitmap); + + GLuint texture; + + GLenum format; + GLenum type; + + switch (bitmap->colorType()) { + case kAlpha_8_SkColorType: + format = GL_ALPHA; + type = GL_UNSIGNED_BYTE; + break; + case kRGB_565_SkColorType: + format = GL_RGB; + type = GL_UNSIGNED_SHORT_5_6_5; + break; + case kARGB_4444_SkColorType: + format = GL_RGBA; + type = GL_UNSIGNED_SHORT_4_4_4_4; + break; + case kN32_SkColorType: + default: + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + break; + } + + renderState.bindFramebuffer(fbo); + + // TODO: Use layerPool or something to get this maybe? But since we + // need explicit format control we can't currently. + + // Setup the rendertarget + glGenTextures(1, &texture); + caches.textureState().activateTexture(0); + caches.textureState().bindTexture(texture); + glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, format, destWidth, destHeight, + 0, format, type, nullptr); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, texture, 0); + + // Setup the source + sp<GraphicBuffer> sourceBuffer; + sp<Fence> sourceFence; + // FIXME: Waiting on an API from libgui for this + // surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence); + if (!sourceBuffer.get()) { + ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); + return false; + } + int err = sourceFence->wait(500 /* ms */); + if (err != NO_ERROR) { + ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); + return false; + } + Image sourceImage(sourceBuffer); + if (!sourceImage.getTexture()) { + ALOGW("Failed to make an EGLImage from the GraphicBuffer"); + return false; + } + Texture sourceTexture(caches); + sourceTexture.wrap(sourceImage.getTexture(), + sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */); + + { + // Draw & readback + renderState.setViewport(destWidth, destHeight); + renderState.scissor().setEnabled(false); + renderState.blend().syncEnabled(); + renderState.stencil().disable(); + + Rect destRect(destWidth, destHeight); + Glop glop; + GlopBuilder(renderState, caches, &glop) + .setRoundRectClipState(nullptr) + .setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO + .setFillLayer(sourceTexture, nullptr, 1.0f, SkXfermode::kSrc_Mode, + Blend::ModeOrderSwap::NoSwap) + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewMapUnitToRect(destRect) + .build(); + Matrix4 ortho; + ortho.loadOrtho(destWidth, destHeight); + renderState.render(glop, ortho); + + glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, + type, bitmap->getPixels()); + } + + // Cleanup + caches.textureState().deleteTexture(texture); + renderState.deleteFramebuffer(fbo); + + GL_CHECKPOINT(MODERATE); + + return true; +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h new file mode 100644 index 000000000000..ea03c829f492 --- /dev/null +++ b/libs/hwui/Readback.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "renderthread/RenderThread.h" + +#include <SkBitmap.h> +#include <gui/Surface.h> + +namespace android { +namespace uirenderer { + +class Readback { +public: + static bool copySurfaceInto(renderthread::RenderThread& renderThread, + Surface& surface, SkBitmap* bitmap); +}; + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp index 14c8f3926e31..cfdc0848c8b0 100644 --- a/libs/hwui/TessellationCache.cpp +++ b/libs/hwui/TessellationCache.cpp @@ -35,13 +35,14 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// TessellationCache::Description::Description() - : type(kNone) + : type(Type::None) , scaleX(1.0f) , scaleY(1.0f) , aa(false) , cap(SkPaint::kDefault_Cap) , style(SkPaint::kFill_Style) , strokeWidth(1.0f) { + // Shape bits should be set to zeroes, because they are used for hash calculation. memset(&shape, 0, sizeof(Shape)); } @@ -52,11 +53,30 @@ TessellationCache::Description::Description(Type type, const Matrix4& transform, , style(paint.getStyle()) , strokeWidth(paint.getStrokeWidth()) { PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY); + // Shape bits should be set to zeroes, because they are used for hash calculation. memset(&shape, 0, sizeof(Shape)); } +bool TessellationCache::Description::operator==(const TessellationCache::Description& rhs) const { + if (type != rhs.type) return false; + if (scaleX != rhs.scaleX) return false; + if (scaleY != rhs.scaleY) return false; + if (aa != rhs.aa) return false; + if (cap != rhs.cap) return false; + if (style != rhs.style) return false; + if (strokeWidth != rhs.strokeWidth) return false; + if (type == Type::None) return true; + const Shape::RoundRect& lRect = shape.roundRect; + const Shape::RoundRect& rRect = rhs.shape.roundRect; + + if (lRect.width != rRect.width) return false; + if (lRect.height != rRect.height) return false; + if (lRect.rx != rRect.rx) return false; + return lRect.ry == rRect.ry; +} + hash_t TessellationCache::Description::hash() const { - uint32_t hash = JenkinsHashMix(0, type); + uint32_t hash = JenkinsHashMix(0, static_cast<int>(type)); hash = JenkinsHashMix(hash, aa); hash = JenkinsHashMix(hash, cap); hash = JenkinsHashMix(hash, style); @@ -77,17 +97,23 @@ void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPain TessellationCache::ShadowDescription::ShadowDescription() : nodeKey(nullptr) { - memset(&matrixData, 0, 16 * sizeof(float)); + memset(&matrixData, 0, sizeof(matrixData)); } -TessellationCache::ShadowDescription::ShadowDescription(const void* nodeKey, const Matrix4* drawTransform) +TessellationCache::ShadowDescription::ShadowDescription(const SkPath* nodeKey, const Matrix4* drawTransform) : nodeKey(nodeKey) { - memcpy(&matrixData, drawTransform->data, 16 * sizeof(float)); + memcpy(&matrixData, drawTransform->data, sizeof(matrixData)); +} + +bool TessellationCache::ShadowDescription::operator==( + const TessellationCache::ShadowDescription& rhs) const { + return nodeKey == rhs.nodeKey + && memcmp(&matrixData, &rhs.matrixData, sizeof(matrixData)) == 0; } hash_t TessellationCache::ShadowDescription::hash() const { uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*) &nodeKey, sizeof(const void*)); - hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, 16 * sizeof(float)); + hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, sizeof(matrixData)); return JenkinsHashWhiten(hash); } @@ -428,7 +454,7 @@ static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& d TessellationCache::Buffer* TessellationCache::getRoundRectBuffer( const Matrix4& transform, const SkPaint& paint, float width, float height, float rx, float ry) { - Description entry(Description::kRoundRect, transform, paint); + Description entry(Description::Type::RoundRect, transform, paint); entry.shape.roundRect.width = width; entry.shape.roundRect.height = height; entry.shape.roundRect.rx = rx; diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h index 0bd6365db60f..6141b4ef63d7 100644 --- a/libs/hwui/TessellationCache.h +++ b/libs/hwui/TessellationCache.h @@ -52,10 +52,10 @@ public: typedef Pair<VertexBuffer*, VertexBuffer*> vertexBuffer_pair_t; struct Description { - DESCRIPTION_TYPE(Description); - enum Type { - kNone, - kRoundRect, + HASHABLE_TYPE(Description); + enum class Type { + None, + RoundRect, }; Type type; @@ -76,18 +76,16 @@ public: Description(); Description(Type type, const Matrix4& transform, const SkPaint& paint); - hash_t hash() const; void setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const; }; struct ShadowDescription { - DESCRIPTION_TYPE(ShadowDescription); - const void* nodeKey; + HASHABLE_TYPE(ShadowDescription); + const SkPath* nodeKey; float matrixData[16]; ShadowDescription(); - ShadowDescription(const void* nodeKey, const Matrix4* drawTransform); - hash_t hash() const; + ShadowDescription(const SkPath* nodeKey, const Matrix4* drawTransform); }; class ShadowTask : public Task<vertexBuffer_pair_t> { diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 1116383e4eab..096093caff51 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -19,6 +19,7 @@ #include "DeferredLayerUpdater.h" #include "DisplayList.h" #include "LayerRenderer.h" +#include "Readback.h" #include "Rect.h" #include "renderthread/CanvasContext.h" #include "renderthread/RenderTask.h" @@ -604,6 +605,20 @@ void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observer) { post(task); } +CREATE_BRIDGE3(copySurfaceInto, RenderThread* thread, + Surface* surface, SkBitmap* bitmap) { + return (void*) Readback::copySurfaceInto(*args->thread, + *args->surface, args->bitmap); +} + +bool RenderProxy::copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap) { + SETUP_TASK(copySurfaceInto); + args->bitmap = bitmap; + args->surface = surface.get(); + args->thread = &RenderThread::getInstance(); + return (bool) staticPostAndWait(task); +} + void RenderProxy::post(RenderTask* task) { mRenderThread.queue(task); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index ecc296b4738e..98aace0da6b5 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -126,6 +126,8 @@ public: ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer); ANDROID_API long getDroppedFrameReportCount(); + ANDROID_API static bool copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap); + private: RenderThread& mRenderThread; CanvasContext* mContext; diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h index ccf2287400dc..7212897bf5d3 100644 --- a/libs/hwui/utils/Macros.h +++ b/libs/hwui/utils/Macros.h @@ -23,12 +23,10 @@ Type(const Type&) = delete; \ void operator=(const Type&) = delete -#define DESCRIPTION_TYPE(Type) \ - int compare(const Type& rhs) const { return memcmp(this, &rhs, sizeof(Type));} \ - bool operator==(const Type& other) const { return compare(other) == 0; } \ - bool operator!=(const Type& other) const { return compare(other) != 0; } \ - friend inline int strictly_order_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs) < 0; } \ - friend inline int compare_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs); } \ +#define HASHABLE_TYPE(Type) \ + bool operator==(const Type& other) const; \ + hash_t hash() const; \ + bool operator!=(const Type& other) const { return !(*this == other); } \ friend inline hash_t hash_type(const Type& entry) { return entry.hash(); } #define REQUIRE_COMPATIBLE_LAYOUT(Type) \ diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index b78869eb4790..55d5f42fb78c 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1107,6 +1107,9 @@ public class MediaPlayer implements SubtitleController.Listener * as this call returns. * * @param afd the AssetFileDescriptor for the file you want to play + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if afd is not a valid AssetFileDescriptor + * @throws IOException if afd can not be read */ public void setDataSource(@NonNull AssetFileDescriptor afd) throws IOException, IllegalArgumentException, IllegalStateException { @@ -1127,6 +1130,8 @@ public class MediaPlayer implements SubtitleController.Listener * * @param fd the FileDescriptor for the file you want to play * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if fd is not a valid FileDescriptor + * @throws IOException if fd can not be read */ public void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, IllegalStateException { @@ -1143,6 +1148,8 @@ public class MediaPlayer implements SubtitleController.Listener * @param offset the offset into the file where the data to be played starts, in bytes * @param length the length in bytes of the data to be played * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if fd is not a valid FileDescriptor + * @throws IOException if fd can not be read */ public void setDataSource(FileDescriptor fd, long offset, long length) throws IOException, IllegalArgumentException, IllegalStateException { @@ -1157,6 +1164,7 @@ public class MediaPlayer implements SubtitleController.Listener * * @param dataSource the MediaDataSource for the media you want to play * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if dataSource is not a valid MediaDataSource */ public void setDataSource(MediaDataSource dataSource) throws IllegalArgumentException, IllegalStateException { diff --git a/packages/DocumentsUI/res/color/item_root_icon.xml b/packages/DocumentsUI/res/color/item_root_icon.xml index 0aa2c1341db4..e1d7e61941ab 100644 --- a/packages/DocumentsUI/res/color/item_root_icon.xml +++ b/packages/DocumentsUI/res/color/item_root_icon.xml @@ -15,5 +15,10 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@*android:color/secondary_text_material_light" /> + <item + android:state_activated="false" + android:color="@*android:color/secondary_text_material_light" /> + <item + android:state_activated="true" + android:color="@color/root_activated_color" /> </selector> diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java index 450341f9d4fc..2288fe74184f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java @@ -139,11 +139,13 @@ public abstract class DocumentHolder } // Do everything in global coordinates - it makes things simpler. - Rect rect = new Rect(); - mSelectionHotspot.getGlobalVisibleRect(rect); + int[] coords = new int[2]; + mSelectionHotspot.getLocationOnScreen(coords); + Rect rect = new Rect(coords[0], coords[1], coords[0] + mSelectionHotspot.getWidth(), + coords[1] + mSelectionHotspot.getHeight()); // If the tap occurred within the icon rect, consider it a selection. - if (rect.contains((int)event.getRawX(), (int)event.getRawY())) { + if (rect.contains((int) event.getRawX(), (int) event.getRawY())) { return mEventListener.onSelect(this); } else { return mEventListener.onActivate(this); diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java index 329afdd4f17c..6ed4ea1db499 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java @@ -196,9 +196,10 @@ class DocumentLoader implements AutoCloseable { } task.loadObjectInfoList(NUM_LOADING_ENTRIES); final boolean shouldNotify = - task.mLastNotified.getTime() < - new Date().getTime() - NOTIFY_PERIOD_MS || - task.getState() != LoaderTask.STATE_LOADING; + task.getState() != LoaderTask.STATE_CANCELLED && + (task.mLastNotified.getTime() < + new Date().getTime() - NOTIFY_PERIOD_MS || + task.getState() != LoaderTask.STATE_LOADING); if (shouldNotify) { task.notify(mResolver); } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java index 60dd7e16a1d9..a3c6bd77cefb 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java @@ -143,9 +143,9 @@ public class DocumentLoaderTest extends AndroidTestCase { } } - public void testCancelTask() throws IOException, InterruptedException { + public void testCancelTask() throws IOException, InterruptedException, TimeoutException { setUpDocument(mManager, - DocumentLoader.NUM_INITIAL_ENTRIES + DocumentLoader.NUM_LOADING_ENTRIES + 1); + DocumentLoader.NUM_INITIAL_ENTRIES + 1); // Block the first iteration in the background thread. mManager.blockDocument( @@ -155,19 +155,24 @@ public class DocumentLoaderTest extends AndroidTestCase { MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) { assertTrue(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); } - Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS); + + final Uri uri = DocumentsContract.buildChildDocumentsUri( + MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId); + assertEquals(0, mResolver.getChangeCount(uri)); // Clear task while the first iteration is being blocked. + mLoader.cancelTask(mParentIdentifier); mManager.unblockDocument( 0, DocumentLoader.NUM_INITIAL_ENTRIES + 1); - mLoader.cancelTask(mParentIdentifier); - - Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS * 2); + Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS); + assertEquals(0, mResolver.getChangeCount(uri)); // Check if it's OK to query invalidated task. try (final Cursor cursor = mLoader.queryChildDocuments( MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) { + assertTrue(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); } + mResolver.waitForNotification(uri, 1); } private void setUpLoader() { diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 084acacf8c78..985fe3ccc1bc 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -804,7 +804,7 @@ <string name="disabled_by_admin">Disabled by administrator</string> <!-- Option in navigation drawer that leads to Settings main screen [CHAR LIMIT=30] --> - <string name="home">Home</string> + <string name="home">Settings Home</string> <string-array name="battery_labels" translatable="false"> <item>0%</item> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index d353f31e59b1..888103439a02 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -534,6 +534,7 @@ public class ApplicationsState { Comparator<AppEntry> mRebuildComparator; ArrayList<AppEntry> mRebuildResult; ArrayList<AppEntry> mLastAppList; + boolean mRebuildForeground; Session(Callbacks callbacks) { mCallbacks = callbacks; @@ -572,6 +573,11 @@ public class ApplicationsState { // Creates a new list of app entries with the given filter and comparator. public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) { + return rebuild(filter, comparator, true); + } + + public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator, + boolean foreground) { synchronized (mRebuildSync) { synchronized (mEntriesMap) { mRebuildingSessions.add(this); @@ -579,6 +585,7 @@ public class ApplicationsState { mRebuildAsync = false; mRebuildFilter = filter; mRebuildComparator = comparator; + mRebuildForeground = foreground; mRebuildResult = null; if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) { Message msg = mBackgroundHandler.obtainMessage( @@ -620,10 +627,12 @@ public class ApplicationsState { mRebuildRequested = false; mRebuildFilter = null; mRebuildComparator = null; + if (mRebuildForeground) { + Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); + mRebuildForeground = false; + } } - Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); - if (filter != null) { filter.init(); } @@ -640,7 +649,10 @@ public class ApplicationsState { if (filter == null || filter.filterApp(entry)) { synchronized (mEntriesMap) { if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock"); - entry.ensureLabel(mContext); + if (comparator != null) { + // Only need the label if we are going to be sorting. + entry.ensureLabel(mContext); + } if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry); filteredApps.add(entry); if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock"); @@ -648,7 +660,9 @@ public class ApplicationsState { } } - Collections.sort(filteredApps, comparator); + if (comparator != null) { + Collections.sort(filteredApps, comparator); + } synchronized (mRebuildSync) { if (!mRebuildRequested) { diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java index ff7019024165..bcbc6ac28f0a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java @@ -216,6 +216,8 @@ public class SettingsDrawerActivity extends Activity { if (sDashboardCategories == null) { sTileCache = new HashMap<>(); sConfigTracker = new InterestingConfigChanges(); + // Apply initial current config. + sConfigTracker.applyNewConfig(getResources()); sDashboardCategories = TileUtils.getCategories(this, sTileCache); } return sDashboardCategories; diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 7ca76141a84c..796dff52304d 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -220,6 +220,7 @@ public class BugreportProgressService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { + Log.v(TAG, "onStartCommand(): " + dumpIntent(intent)); if (intent != null) { // Handle it in a separate thread. final Message msg = mMainHandler.obtainMessage(); @@ -297,6 +298,7 @@ public class BugreportProgressService extends Service { return; } final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT); + Log.v(TAG, "handleMessage(): " + dumpIntent((Intent) parcel)); final Intent intent; if (parcel instanceof Intent) { // The real intent was passed to BugreportReceiver, which delegated to the service. @@ -707,7 +709,8 @@ public class BugreportProgressService extends Service { for (int i = 0; i < mProcesses.size(); i++) { final BugreportInfo info = mProcesses.valueAt(i); if (info.finished) { - Log.d(TAG, "Not updating progress because share notification was already sent"); + Log.d(TAG, "Not updating progress for " + info.id + " while taking screenshot" + + " because share notification was already sent"); continue; } updateProgress(info); @@ -846,7 +849,15 @@ public class BugreportProgressService extends Service { private static Intent buildSendIntent(Context context, BugreportInfo info) { // Files are kept on private storage, so turn into Uris that we can // grant temporary permissions for. - final Uri bugreportUri = getUri(context, info.bugreportFile); + final Uri bugreportUri; + try { + bugreportUri = getUri(context, info.bugreportFile); + } catch (IllegalArgumentException e) { + // Should not happen on production, but happens when a Shell is sideloaded and + // FileProvider cannot find a configured root for it. + Log.wtf(TAG, "Could not get URI for " + info.bugreportFile, e); + return null; + } final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); final String mimeType = "application/vnd.android.bugreport"; @@ -907,6 +918,12 @@ public class BugreportProgressService extends Service { addDetailsToZipFile(mContext, info); final Intent sendIntent = buildSendIntent(mContext, info); + if (sendIntent == null) { + Log.w(TAG, "Stopping progres on ID " + id + " because share intent could not be built"); + stopProgress(id); + return; + } + final Intent notifIntent; // Send through warning dialog by default @@ -1165,6 +1182,52 @@ public class BugreportProgressService extends Service { } } + /** + * Dumps an intent, extracting the relevant extras. + */ + static String dumpIntent(Intent intent) { + if (intent == null) { + return "NO INTENT"; + } + String action = intent.getAction(); + if (action == null) { + // Happens when BugreportReceiver calls startService... + action = "no action"; + } + final StringBuilder buffer = new StringBuilder(action).append(" extras: "); + addExtra(buffer, intent, EXTRA_ID); + addExtra(buffer, intent, EXTRA_PID); + addExtra(buffer, intent, EXTRA_MAX); + addExtra(buffer, intent, EXTRA_NAME); + addExtra(buffer, intent, EXTRA_DESCRIPTION); + addExtra(buffer, intent, EXTRA_BUGREPORT); + addExtra(buffer, intent, EXTRA_SCREENSHOT); + addExtra(buffer, intent, EXTRA_INFO); + + if (intent.hasExtra(EXTRA_ORIGINAL_INTENT)) { + buffer.append(SHORT_EXTRA_ORIGINAL_INTENT).append(": "); + final Intent originalIntent = intent.getParcelableExtra(EXTRA_ORIGINAL_INTENT); + buffer.append(dumpIntent(originalIntent)); + } else { + buffer.append("no ").append(SHORT_EXTRA_ORIGINAL_INTENT); + } + + return buffer.toString(); + } + + private static final String SHORT_EXTRA_ORIGINAL_INTENT = + EXTRA_ORIGINAL_INTENT.substring(EXTRA_ORIGINAL_INTENT.lastIndexOf('.') + 1); + + private static void addExtra(StringBuilder buffer, Intent intent, String name) { + final String shortName = name.substring(name.lastIndexOf('.') + 1); + if (intent.hasExtra(name)) { + buffer.append(shortName).append('=').append(intent.getExtra(name)); + } else { + buffer.append("no ").append(shortName); + } + buffer.append(", "); + } + private static boolean setSystemProperty(String key, String value) { try { if (DEBUG) Log.v(TAG, "Setting system property " + key + " to " + value); diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java index cbd17bfa3971..f6e558f73459 100644 --- a/packages/Shell/src/com/android/shell/BugreportReceiver.java +++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java @@ -20,6 +20,7 @@ import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT; import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT; import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED; import static com.android.shell.BugreportProgressService.getFileExtra; +import static com.android.shell.BugreportProgressService.dumpIntent; import java.io.File; @@ -51,7 +52,7 @@ public class BugreportReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - Log.d(TAG, "onReceive: " + intent); + Log.d(TAG, "onReceive(): " + dumpIntent(intent)); // Clean up older bugreports in background cleanupOldFiles(this, intent, INTENT_BUGREPORT_FINISHED, MIN_KEEP_COUNT, MIN_KEEP_AGE); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index f5854f5f7a36..c248adfce690 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -159,6 +159,9 @@ <!-- DND access --> <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" /> + <!-- It's like, reality, but, you know, virtual --> + <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" /> + <application android:name=".SystemUIApplication" android:persistent="true" @@ -235,6 +238,19 @@ android:value="com.android.settings.category.system" /> </activity> + <activity-alias android:name=".DemoMode" + android:targetActivity=".tuner.TunerActivity" + android:icon="@drawable/tuner" + android:theme="@style/TunerSettings" + android:label="@string/demo_mode" + android:process=":tuner" + android:exported="true"> + <intent-filter> + <action android:name="com.android.settings.action.DEMO_MODE" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity-alias> + <!-- Service used by secondary users to register themselves with the system user. --> <service android:name=".recents.RecentsSystemUserService" android:exported="false" diff --git a/packages/SystemUI/res/anim/tv_pip_overlay_fade_in_animation.xml b/packages/SystemUI/res/anim/tv_pip_overlay_fade_in_animation.xml new file mode 100644 index 000000000000..33bceaa77ce9 --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_overlay_fade_in_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="alpha" + android:valueTo="1" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="350" /> diff --git a/packages/SystemUI/res/anim/tv_pip_overlay_fade_out_animation.xml b/packages/SystemUI/res/anim/tv_pip_overlay_fade_out_animation.xml new file mode 100644 index 000000000000..a12ddffc0bfc --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_overlay_fade_out_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="alpha" + android:valueTo="0" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="500" /> diff --git a/packages/SystemUI/res/drawable/recents_info_dark.xml b/packages/SystemUI/res/drawable/recents_info_dark.xml index b1a22425ef2d..555a69ae9123 100644 --- a/packages/SystemUI/res/drawable/recents_info_dark.xml +++ b/packages/SystemUI/res/drawable/recents_info_dark.xml @@ -14,8 +14,8 @@ Copyright (C) 2014 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48.0dp" - android:height="48.0dp" + android:width="24.0dp" + android:height="24.0dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path diff --git a/packages/SystemUI/res/drawable/recents_info_light.xml b/packages/SystemUI/res/drawable/recents_info_light.xml index bc58c3b406d3..65e7bf5fa41d 100644 --- a/packages/SystemUI/res/drawable/recents_info_light.xml +++ b/packages/SystemUI/res/drawable/recents_info_light.xml @@ -14,8 +14,8 @@ Copyright (C) 2014 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48.0dp" - android:height="48.0dp" + android:width="24.0dp" + android:height="24.0dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path diff --git a/packages/SystemUI/res/layout/recents.xml b/packages/SystemUI/res/layout/recents.xml index 186aaf6b40e8..ae89631219fb 100644 --- a/packages/SystemUI/res/layout/recents.xml +++ b/packages/SystemUI/res/layout/recents.xml @@ -25,6 +25,14 @@ android:layout_height="match_parent"> </com.android.systemui.recents.views.RecentsView> + <!-- Incompatible task overlay --> + <ViewStub android:id="@+id/incompatible_app_overlay_stub" + android:inflatedId="@+id/incompatible_app_overlay" + android:layout="@layout/recents_incompatible_app_overlay" + android:layout_width="match_parent" + android:layout_height="128dp" + android:layout_gravity="center_horizontal|top" /> + <!-- Nav Bar Scrim View --> <ImageView android:id="@+id/nav_bar_scrim" diff --git a/packages/SystemUI/res/layout/recents_incompatible_app_overlay.xml b/packages/SystemUI/res/layout/recents_incompatible_app_overlay.xml new file mode 100644 index 000000000000..2b49dd33fd26 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_incompatible_app_overlay.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:alpha="0" + android:background="#88000000" + android:forceHasOverlappingRendering="false"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:drawableTop="@drawable/recents_info_light" + android:drawablePadding="8dp" + android:text="@string/recents_incompatible_app_message" /> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml index c813818ed568..b1b2f1ed34e0 100644 --- a/packages/SystemUI/res/layout/recents_task_view.xml +++ b/packages/SystemUI/res/layout/recents_task_view.xml @@ -26,7 +26,9 @@ android:id="@+id/task_view_thumbnail" android:layout_width="match_parent" android:layout_height="match_parent" /> + <include layout="@layout/recents_task_view_header" /> + <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/lock_to_app_fab" android:layout_width="@dimen/recents_lock_to_app_size" @@ -45,6 +47,17 @@ android:layout_gravity="center" android:src="@drawable/recents_lock_to_app_pin" /> </com.android.systemui.statusbar.AlphaOptimizedFrameLayout> + + <!-- The incompatible app toast --> + <ViewStub android:id="@+id/incompatible_app_toast_stub" + android:inflatedId="@+id/incompatible_app_toast" + android:layout="@*android:layout/transient_notification" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:layout_marginTop="48dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" /> </FrameLayout> </com.android.systemui.recents.views.TaskView> diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml index 2b3c5df93b54..2df57bfbd55b 100644 --- a/packages/SystemUI/res/layout/recents_task_view_header.xml +++ b/packages/SystemUI/res/layout/recents_task_view_header.xml @@ -31,40 +31,19 @@ android:paddingBottom="8dp" android:paddingStart="16dp" android:paddingEnd="12dp" /> - <LinearLayout - android:id="@+id/title_container" + <TextView + android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" - android:orientation="vertical"> - <TextView - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:textSize="16sp" - android:textColor="#ffffffff" - android:text="@string/recents_empty_message" - android:fontFamily="sans-serif-medium" - android:singleLine="true" - android:maxLines="1" - android:ellipsize="marquee" - android:fadingEdge="horizontal" /> - <TextView - android:id="@+id/sub_title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:textSize="11sp" - android:textColor="#ffffffff" - android:text="@string/recents_launch_non_dockable_task_label" - android:fontFamily="sans-serif-medium" - android:singleLine="true" - android:maxLines="1" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:visibility="gone" /> - </LinearLayout> + android:textSize="16sp" + android:textColor="#ffffffff" + android:text="@string/recents_empty_message" + android:fontFamily="sans-serif-medium" + android:singleLine="true" + android:maxLines="1" + android:ellipsize="marquee" + android:fadingEdge="horizontal" /> <com.android.systemui.recents.views.FixedSizeImageView android:id="@+id/move_task" android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9f41dff86603..dc9ffa9a5088 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -735,10 +735,8 @@ <string name="recents_launch_disabled_message"><xliff:g id="app" example="Calendar">%s</xliff:g> is disabled in safe-mode.</string> <!-- Recents: Stack action button string. [CHAR LIMIT=NONE] --> <string name="recents_stack_action_button_label">Clear all</string> - <!-- Recents: Non-dockable task drag message. [CHAR LIMIT=NONE] --> - <string name="recents_drag_non_dockable_task_message">This app does not support multi-window</string> - <!-- Recents: Non-dockable task launch sub header. [CHAR LIMIT=NONE] --> - <string name="recents_launch_non_dockable_task_label">App does not support multi-window</string> + <!-- Recents: Incompatible task message. [CHAR LIMIT=NONE] --> + <string name="recents_incompatible_app_message">App doesn\'t support split screen</string> <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] --> <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string> diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java index 087f61eda211..076b5bcd0861 100755 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java @@ -37,7 +37,7 @@ import android.provider.Settings; import com.android.systemui.statusbar.policy.BatteryController; -public class BatteryMeterDrawable extends Drawable implements DemoMode, +public class BatteryMeterDrawable extends Drawable implements BatteryController.BatteryStateChangeCallback { private static final float ASPECT_RATIO = 9.5f / 14.5f; @@ -184,14 +184,12 @@ public class BatteryMeterDrawable extends Drawable implements DemoMode, mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver); updateShowPercent(); - if (mDemoMode) return; mBatteryController.addStateChangedCallback(this); } public void stopListening() { mListening = false; mContext.getContentResolver().unregisterContentObserver(mSettingObserver); - if (mDemoMode) return; mBatteryController.removeStateChangedCallback(this); } @@ -507,35 +505,6 @@ public class BatteryMeterDrawable extends Drawable implements DemoMode, return 0; } - private boolean mDemoMode; - - @Override - public void dispatchDemoCommand(String command, Bundle args) { - if (!mDemoMode && command.equals(COMMAND_ENTER)) { - mBatteryController.removeStateChangedCallback(this); - mDemoMode = true; - if (mListening) { - mBatteryController.removeStateChangedCallback(this); - } - } else if (mDemoMode && command.equals(COMMAND_EXIT)) { - mDemoMode = false; - postInvalidate(); - if (mListening) { - mBatteryController.addStateChangedCallback(this); - } - } else if (mDemoMode && command.equals(COMMAND_BATTERY)) { - String level = args.getString("level"); - String plugged = args.getString("plugged"); - if (level != null) { - mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100); - } - if (plugged != null) { - mPluggedIn = Boolean.parseBoolean(plugged); - } - postInvalidate(); - } - } - private final class SettingObserver extends ContentObserver { public SettingObserver() { super(new Handler()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 4d959d8cdadb..af81c196e4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -87,6 +87,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha public void setOnKeyguard(boolean onKeyguard) { mOnKeyguard = onKeyguard; + mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE); if (mOnKeyguard) { clearAnimationState(); } @@ -290,7 +291,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha @Override public void onAnimationStarted() { - mQuickQsPanel.setVisibility(View.VISIBLE); + mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE); if (mOnFirstPage) { final int N = mTopFiveQs.size(); for (int i = 0; i < N; i++) { @@ -302,12 +303,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private void clearAnimationState() { final int N = mAllViews.size(); mQuickQsPanel.setAlpha(0); - mQuickQsPanel.setVisibility(View.VISIBLE); for (int i = 0; i < N; i++) { View v = mAllViews.get(i); v.setAlpha(1); - v.setTranslationX(1); - v.setTranslationY(1); + v.setTranslationX(0); + v.setTranslationY(0); } final int N2 = mTopFiveQs.size(); for (int i = 0; i < N2; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java index e3a4909a7dcb..ef7556267e0c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java @@ -70,8 +70,8 @@ public class QSContainer extends FrameLayout { super.onFinishInflate(); mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel); mQSDetail = (QSDetail) findViewById(R.id.qs_detail); - mQSDetail.setQsPanel(mQSPanel); mHeader = (BaseStatusBarHeader) findViewById(R.id.header); + mQSDetail.setQsPanel(mQSPanel, mHeader); mQSAnimator = new QSAnimator(this, (QuickQSPanel) mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); mQSCustomizer = (QSCustomizer) findViewById(R.id.qs_customize); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 50c0cca8d8e7..0cf7e4793941 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -35,6 +35,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.qs.QSTile.DetailAdapter; +import com.android.systemui.statusbar.phone.BaseStatusBarHeader; import com.android.systemui.statusbar.phone.QSTileHost; public class QSDetail extends LinearLayout { @@ -62,6 +63,7 @@ public class QSDetail extends LinearLayout { private boolean mClosingDetail; private boolean mFullyExpanded; private View mQsDetailHeaderBack; + private BaseStatusBarHeader mHeader; public QSDetail(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -107,8 +109,9 @@ public class QSDetail extends LinearLayout { mDetailDoneButton.setOnClickListener(doneListener); } - public void setQsPanel(QSPanel panel) { + public void setQsPanel(QSPanel panel, BaseStatusBarHeader header) { mQsPanel = panel; + mHeader = header; mQsPanel.setCallback(mQsPanelCallback); } @@ -195,6 +198,7 @@ public class QSDetail extends LinearLayout { mClosingDetail = true; mDetailAdapter = null; listener = mTeardownDetailWhenDone; + mHeader.setVisibility(View.VISIBLE); mQsPanel.setGridContentVisibility(true); mQsPanelCallback.onScanStateChanged(false); } @@ -273,6 +277,7 @@ public class QSDetail extends LinearLayout { // Only hide content if still in detail state. if (mDetailAdapter != null) { mQsPanel.setGridContentVisibility(false); + mHeader.setVisibility(View.INVISIBLE); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 287bb224dbee..82daaa608fa2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -435,7 +435,7 @@ public class Recents extends SystemUI mDraggingInRecentsCurrentUser = currentUser; return true; } else { - Toast.makeText(mContext, R.string.recents_drag_non_dockable_task_message, + Toast.makeText(mContext, R.string.recents_incompatible_app_message, Toast.LENGTH_SHORT).show(); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index b1d955573d19..6b476eeb43c0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -38,6 +38,7 @@ import android.view.WindowManager.LayoutParams; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsProto.MetricsEvent; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; @@ -58,8 +59,10 @@ import com.android.systemui.recents.events.component.RecentsVisibilityChangedEve import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; +import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent; import com.android.systemui.recents.events.ui.RecentsDrawnEvent; import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; +import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; @@ -68,12 +71,12 @@ import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.RecentsPackageMonitor; import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.views.AnimationProps; import com.android.systemui.recents.views.RecentsView; import com.android.systemui.recents.views.SystemBarScrimViews; import com.android.systemui.statusbar.BaseStatusBar; @@ -90,6 +93,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD private final static boolean DEBUG = false; public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1; + public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150; private RecentsPackageMonitor mPackageMonitor; private long mLastTabKeyEventTime; @@ -101,6 +105,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // Top level views private RecentsView mRecentsView; private SystemBarScrimViews mScrimViews; + private View mIncompatibleAppOverlay; // Runnables to finish the Recents activity private Intent mHomeIntent; @@ -674,6 +679,30 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD MetricsLogger.count(this, "overview_app_info", 1); } + public final void onBusEvent(ShowIncompatibleAppOverlayEvent event) { + if (mIncompatibleAppOverlay == null) { + mIncompatibleAppOverlay = Utilities.findViewStubById(this, + R.id.incompatible_app_overlay_stub).inflate(); + mIncompatibleAppOverlay.setWillNotDraw(false); + mIncompatibleAppOverlay.setVisibility(View.VISIBLE); + } + mIncompatibleAppOverlay.animate() + .alpha(1f) + .setDuration(INCOMPATIBLE_APP_ALPHA_DURATION) + .setInterpolator(Interpolators.ALPHA_IN) + .start(); + } + + public final void onBusEvent(HideIncompatibleAppOverlayEvent event) { + if (mIncompatibleAppOverlay != null) { + mIncompatibleAppOverlay.animate() + .alpha(0f) + .setDuration(INCOMPATIBLE_APP_ALPHA_DURATION) + .setInterpolator(Interpolators.ALPHA_OUT) + .start(); + } + } + public final void onBusEvent(DeleteTaskDataEvent event) { // Remove any stored data from the loader RecentsTaskLoader loader = Recents.getTaskLoader(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/HideIncompatibleAppOverlayEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/HideIncompatibleAppOverlayEvent.java new file mode 100644 index 000000000000..d6ef636b23a6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/HideIncompatibleAppOverlayEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016 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.recents.events.ui; + +import com.android.systemui.recents.events.EventBus; + +/** + * This is sent when a user stops draggin an incompatible app task. + */ +public class HideIncompatibleAppOverlayEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowIncompatibleAppOverlayEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowIncompatibleAppOverlayEvent.java new file mode 100644 index 000000000000..3a4350e3a0ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowIncompatibleAppOverlayEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016 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.recents.events.ui; + +import com.android.systemui.recents.events.EventBus; + +/** + * This is sent when a user starts dragging an incompatible app task. + */ +public class ShowIncompatibleAppOverlayEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java index 69d98af5a2b2..4ecda542ca36 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java @@ -19,17 +19,20 @@ package com.android.systemui.recents.misc; import android.animation.Animator; import android.animation.AnimatorSet; import android.annotation.FloatRange; +import android.app.Activity; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.Trace; import android.util.ArraySet; import android.util.IntProperty; import android.util.Property; import android.util.TypedValue; import android.view.View; import android.view.ViewParent; +import android.view.ViewStub; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.views.TaskViewTransform; @@ -220,6 +223,20 @@ public class Utilities { } /** + * Returns a view stub for the given view id. + */ + public static ViewStub findViewStubById(View v, int stubId) { + return (ViewStub) v.findViewById(stubId); + } + + /** + * Returns a view stub for the given view id. + */ + public static ViewStub findViewStubById(Activity a, int stubId) { + return (ViewStub) a.findViewById(stubId); + } + + /** * Updates {@param transforms} to be the same size as {@param tasks}. */ public static void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) { @@ -245,6 +262,14 @@ public class Utilities { } /** + * Adds a trace event for debugging. + */ + public static void addTraceEvent(String event) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, event); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + + /** * Returns a lightweight dump of a rect. */ public static String dumpRect(Rect r) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java index 13e1a14ead63..e7e81d62340f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java @@ -55,6 +55,7 @@ import com.android.systemui.recents.tv.animations.HomeRecentsEnterExitAnimationH import com.android.systemui.recents.tv.views.RecentsTvView; import com.android.systemui.recents.tv.views.TaskStackHorizontalGridView; import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter; +import com.android.systemui.recents.views.AnimationProps; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.tv.pip.PipManager; import com.android.systemui.tv.pip.PipRecentsOverlayManager; @@ -246,7 +247,7 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { dismissEvent.addPostAnimationCallback(mFinishLaunchHomeRunnable); dismissEvent.addPostAnimationCallback(closeSystemWindows); - if(mTaskStackHorizontalGridView.getChildCount() > 0) { + if(mTaskStackHorizontalGridView.getChildCount() > 0 && animateTaskViews) { mHomeRecentsEnterExitAnimationHolder.startExitAnimation(dismissEvent); } else { closeSystemWindows.run(); @@ -494,12 +495,10 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { } public final void onBusEvent(AllTaskViewsDismissedEvent event) { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.hasDockedTask()) { + if (mPipManager.isPipShown()) { mRecentsView.showEmptyView(); } else { - // Just go straight home (no animation necessary because there are no more task views) - dismissRecentsToHome(false /* animateTaskViews */); + dismissRecentsToHome(false); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java index fbcfa9779682..3e668afbb47e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java @@ -26,13 +26,13 @@ import com.android.systemui.R; public class DismissAnimationsHolder { private LinearLayout mDismissArea; - private LinearLayout mTaskCardView; + private LinearLayout mRecentsTvCard; private int mCardYDelta; private long mShortDuration; private long mLongDuration; public DismissAnimationsHolder(TaskCardView taskCardView) { - mTaskCardView = (LinearLayout) taskCardView.findViewById(R.id.recents_tv_card); + mRecentsTvCard = (LinearLayout) taskCardView.findViewById(R.id.recents_tv_card); mDismissArea = (LinearLayout) taskCardView.findViewById(R.id.card_dismiss); Resources res = taskCardView.getResources(); @@ -47,7 +47,7 @@ public class DismissAnimationsHolder { .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(1.0f); - mTaskCardView.animate() + mRecentsTvCard.animate() .setDuration(mShortDuration) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .translationYBy(mCardYDelta) @@ -60,7 +60,7 @@ public class DismissAnimationsHolder { .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(0.0f); - mTaskCardView.animate() + mRecentsTvCard.animate() .setDuration(mShortDuration) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .translationYBy(-mCardYDelta) @@ -73,11 +73,17 @@ public class DismissAnimationsHolder { .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(0.0f); - mTaskCardView.animate() + mRecentsTvCard.animate() .setDuration(mLongDuration) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .translationYBy(mCardYDelta) .alpha(0.0f) .setListener(listener); } + + public void reset() { + mRecentsTvCard.setAlpha(1.0f); + mRecentsTvCard.setTranslationY(0); + mRecentsTvCard.animate().setListener(null); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java index 53fdf62c6620..b876fc701372 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java @@ -250,8 +250,9 @@ public class RecentsTvView extends FrameLayout { public TaskStackHorizontalGridView setTaskStackViewAdapter( TaskStackHorizontalViewAdapter taskStackViewAdapter) { - if(mTaskStackHorizontalView != null) { + if (mTaskStackHorizontalView != null) { mTaskStackHorizontalView.setAdapter(taskStackViewAdapter); + taskStackViewAdapter.setTaskStackHorizontalGridView(mTaskStackHorizontalView); } return mTaskStackHorizontalView; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java index 99d478b2e8d1..46e77802ceb1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java @@ -189,6 +189,7 @@ public class TaskCardView extends LinearLayout { } public void startDismissTaskAnimation(Animator.AnimatorListener listener) { + mDismissState = false; mDismissAnimationsHolder.startDismissAnimation(listener); } @@ -201,4 +202,10 @@ public class TaskCardView extends LinearLayout { super.onDetachedFromWindow(); setDismissState(false); } + + public void reset() { + mDismissState = false; + mRecentsRowFocusAnimationHolder.reset(); + mDismissAnimationsHolder.reset(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java index 603721a56eca..77ab8c1c1f8c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java @@ -179,13 +179,14 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T @Override public void onStackTaskAdded(TaskStack stack, Task newTask) { - getAdapter().notifyItemInserted(stack.getStackTasks().indexOf(newTask)); + ((TaskStackHorizontalViewAdapter) getAdapter()).addTaskAt(newTask, + stack.indexOfStackTask(newTask)); } @Override public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture) { - getAdapter().notifyItemRemoved(stack.getStackTasks().indexOf(removedTask)); + ((TaskStackHorizontalViewAdapter) getAdapter()).removeTask(removedTask); if (mFocusedTask == removedTask) { resetFocusedTask(removedTask); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java index 97712ea9ce86..eff184507740 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java @@ -27,7 +27,9 @@ import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.LaunchTvTaskEvent; import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; +import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.views.AnimationProps; import java.util.ArrayList; import java.util.List; @@ -40,6 +42,7 @@ public class TaskStackHorizontalViewAdapter extends //Full class name is 30 characters private static final String TAG = "TaskStackViewAdapter"; private List<Task> mTaskList; + private TaskStackHorizontalGridView mGridView; public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ private TaskCardView mTaskCardView; @@ -62,7 +65,7 @@ public class TaskStackHorizontalViewAdapter extends try { if (mTaskCardView.isInDismissState()) { mTaskCardView.startDismissTaskAnimation( - getRemoveAtListener(getAdapterPosition(), mTaskCardView)); + getRemoveAtListener(getAdapterPosition(), mTaskCardView.getTask())); } else { EventBus.getDefault().send(new LaunchTvTaskEvent(mTaskCardView, mTask, null, INVALID_STACK_ID)); @@ -74,6 +77,28 @@ public class TaskStackHorizontalViewAdapter extends } } + + private Animator.AnimatorListener getRemoveAtListener(final int position, + final Task task) { + return new Animator.AnimatorListener() { + + @Override + public void onAnimationStart(Animator animation) { } + + @Override + public void onAnimationEnd(Animator animation) { + removeAt(position); + EventBus.getDefault().send(new DeleteTaskDataEvent(task)); + } + + @Override + public void onAnimationCancel(Animator animation) { } + + @Override + public void onAnimationRepeat(Animator animation) { } + }; + + } } public TaskStackHorizontalViewAdapter(List tasks) { @@ -101,39 +126,43 @@ public class TaskStackHorizontalViewAdapter extends } @Override - public int getItemCount() { - return mTaskList.size(); + public void onViewDetachedFromWindow(ViewHolder holder) { + holder.mTaskCardView.reset(); } - private Animator.AnimatorListener getRemoveAtListener(final int position, - final TaskCardView taskCardView) { - return new Animator.AnimatorListener() { - - @Override - public void onAnimationStart(Animator animation) { } - - @Override - public void onAnimationEnd(Animator animation) { - removeAt(position); - EventBus.getDefault().send(new DeleteTaskDataEvent(taskCardView.getTask())); - } - - @Override - public void onAnimationCancel(Animator animation) { } - - @Override - public void onAnimationRepeat(Animator animation) { } - }; - + @Override + public int getItemCount() { + return mTaskList.size(); } private void removeAt(int position) { - mTaskList.remove(position); + Task removedTask = mTaskList.remove(position); + if (mGridView != null) { + mGridView.getStack().removeTask(removedTask, AnimationProps.IMMEDIATE, + false); + } notifyItemRemoved(position); } + public void removeTask(Task task) { + int position = mTaskList.indexOf(task); + if (position >= 0) { + mTaskList.remove(position); + notifyItemRemoved(position); + } + } + public int getPositionOfTask(Task task) { int position = mTaskList.indexOf(task); return (position >= 0) ? position : 0; } + + public void setTaskStackHorizontalGridView(TaskStackHorizontalGridView gridView) { + mGridView = gridView; + } + + public void addTaskAt(Task task, int position) { + mTaskList.add(position, task); + notifyItemInserted(position); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java index a867bdeecb69..22acb889a941 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -20,19 +20,17 @@ import android.app.ActivityManager; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; -import android.provider.Settings; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.ViewDebug; -import android.widget.Toast; import com.android.internal.policy.DividerSnapAlgorithm; -import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsImpl; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; +import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent; +import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent; import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; @@ -166,8 +164,7 @@ public class RecentsViewTouchHandler { if (ActivityManager.supportsMultiWindow() && !ssp.hasDockedTask() && mDividerSnapAlgorithm.isSplitScreenFeasible()) { if (!event.task.isDockable) { - Toast.makeText(mRv.getContext(), R.string.recents_drag_non_dockable_task_message, - Toast.LENGTH_SHORT).show(); + EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent()); } else { // Add the dock state drop targets (these take priority) TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation(); @@ -184,6 +181,9 @@ public class RecentsViewTouchHandler { } public final void onBusEvent(DragEndEvent event) { + if (!mDragTask.isDockable) { + EventBus.getDefault().send(new HideIncompatibleAppOverlayEvent()); + } mDragRequested = false; mDragTask = null; mTaskView = null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index b75a91e8c42b..e4da8b369914 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -23,13 +23,13 @@ import android.content.res.Resources; import android.graphics.Path; import android.graphics.Rect; import android.util.ArraySet; +import android.util.MutableFloat; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.ViewDebug; import com.android.systemui.R; import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.RecentsDebugFlags; @@ -628,22 +628,24 @@ public class TaskStackLayoutAlgorithm { /** * Updates this stack when a scroll happens. + * */ - public void updateFocusStateOnScroll(float stackScroll, float deltaScroll) { - if (deltaScroll == 0f) { - return; + public float updateFocusStateOnScroll(float lastTargetStackScroll, float targetStackScroll, + float lastStackScroll) { + if (targetStackScroll == lastStackScroll) { + return targetStackScroll; } + float deltaScroll = targetStackScroll - lastStackScroll; + float deltaTargetScroll = targetStackScroll - lastTargetStackScroll; + float newScroll = targetStackScroll; + mUnfocusedRange.offset(targetStackScroll); for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) { int taskId = mTaskIndexOverrideMap.keyAt(i); float x = mTaskIndexMap.get(taskId); float overrideX = mTaskIndexOverrideMap.get(taskId, 0f); float newOverrideX = overrideX + deltaScroll; - mUnfocusedRange.offset(stackScroll); - boolean outOfBounds = mUnfocusedRange.getNormalizedX(newOverrideX) < 0f || - mUnfocusedRange.getNormalizedX(newOverrideX) > 1f; - if (outOfBounds || (overrideX >= x && x >= newOverrideX) || - (overrideX <= x && x <= newOverrideX)) { + if (isInvalidOverrideX(x, overrideX, newOverrideX)) { // Remove the override once we reach the original task index mTaskIndexOverrideMap.removeAt(i); } else if ((overrideX >= x && deltaScroll <= 0f) || @@ -652,11 +654,23 @@ public class TaskStackLayoutAlgorithm { mTaskIndexOverrideMap.put(taskId, newOverrideX); } else { // Scrolling override x away from x, we should still move the scroll towards x - float deltaX = overrideX - x; - newOverrideX = Math.signum(deltaX) * (Math.abs(deltaX) - Math.abs(deltaScroll)); - mTaskIndexOverrideMap.put(taskId, x + newOverrideX); + newScroll = lastStackScroll; + newOverrideX = overrideX - deltaTargetScroll; + if (isInvalidOverrideX(x, overrideX, newOverrideX)) { + mTaskIndexOverrideMap.removeAt(i); + } else{ + mTaskIndexOverrideMap.put(taskId, newOverrideX); + } } } + return newScroll; + } + + private boolean isInvalidOverrideX(float x, float overrideX, float newOverrideX) { + boolean outOfBounds = mUnfocusedRange.getNormalizedX(newOverrideX) < 0f || + mUnfocusedRange.getNormalizedX(newOverrideX) > 1f; + return outOfBounds || (overrideX >= x && x >= newOverrideX) || + (overrideX <= x && x <= newOverrideX); } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 13c8403ab0ab..0fc45ed8951c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -1618,7 +1618,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (animation != null) { relayoutTaskViewsOnNextFrame(animation); } - mLayoutAlgorithm.updateFocusStateOnScroll(curScroll, curScroll - prevScroll); if (mEnterAnimationComplete) { if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index 19b3c943ae65..1fa73c6ef61c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -22,6 +22,7 @@ import android.animation.ObjectAnimator; import android.content.Context; import android.util.FloatProperty; import android.util.Log; +import android.util.MutableFloat; import android.util.Property; import android.view.ViewDebug; import android.widget.OverScroller; @@ -66,6 +67,8 @@ public class TaskStackViewScroller { @ViewDebug.ExportedProperty(category="recents") float mStackScrollP; + @ViewDebug.ExportedProperty(category="recents") + float mLastDeltaP = 0f; float mFlingDownScrollP; int mFlingDownY; @@ -84,6 +87,11 @@ public class TaskStackViewScroller { /** Resets the task scroller. */ void reset() { mStackScrollP = 0f; + mLastDeltaP = 0f; + } + + void resetDeltaScroll() { + mLastDeltaP = 0f; } /** Gets the current stack scroll */ @@ -99,14 +107,27 @@ public class TaskStackViewScroller { } /** + * Sets the current stack scroll immediately, and returns the difference between the target + * scroll and the actual scroll after accounting for the effect on the focus state. + */ + public float setDeltaStackScroll(float downP, float deltaP) { + float targetScroll = downP + deltaP; + float newScroll = mLayoutAlgorithm.updateFocusStateOnScroll(downP + mLastDeltaP, targetScroll, + mStackScrollP); + setStackScroll(newScroll, AnimationProps.IMMEDIATE); + mLastDeltaP = deltaP; + return newScroll - targetScroll; + } + + /** * Sets the current stack scroll, but indicates to the callback the preferred animation to * update to this new scroll. */ - public void setStackScroll(float s, AnimationProps animation) { - float prevStackScroll = mStackScrollP; - mStackScrollP = s; + public void setStackScroll(float newScroll, AnimationProps animation) { + float prevScroll = mStackScrollP; + mStackScrollP = newScroll; if (mCb != null) { - mCb.onStackScrollChanged(prevStackScroll, mStackScrollP, animation); + mCb.onStackScrollChanged(prevScroll, mStackScrollP, animation); } } @@ -115,9 +136,9 @@ public class TaskStackViewScroller { * @return whether the stack progress changed. */ public boolean setStackScrollToInitialState() { - float prevStackScrollP = mStackScrollP; + float prevScroll = mStackScrollP; setStackScroll(mLayoutAlgorithm.mInitialScrollP); - return Float.compare(prevStackScrollP, mStackScrollP) != 0; + return Float.compare(prevScroll, mStackScrollP) != 0; } /** @@ -227,10 +248,9 @@ public class TaskStackViewScroller { boolean computeScroll() { if (mScroller.computeScrollOffset()) { float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY()); - float scroll = mFlingDownScrollP + deltaP; - setStackScroll(scroll); + mFlingDownScrollP += setDeltaStackScroll(mFlingDownScrollP, deltaP); if (DEBUG) { - Log.d(TAG, "computeScroll: " + scroll); + Log.d(TAG, "computeScroll: " + (mFlingDownScrollP + deltaP)); } return true; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index ee0de1ad05df..3cdb1fb27854 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -200,6 +200,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Stop the current scroll if it is still flinging mScroller.stopScroller(); mScroller.stopBoundScrollAnimation(); + mScroller.resetDeltaScroll(); Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator); // Finish any existing task animations from the delete @@ -223,6 +224,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mDownY = (int) ev.getY(index); mLastY = mDownY; mDownScrollP = mScroller.getStackScroll(); + mScroller.resetDeltaScroll(); mVelocityTracker.addMovement(ev); break; } @@ -256,20 +258,21 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // If we just move linearly on the screen, then that would map to 1/arclength // of the curve, so just move the scroll proportional to that float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y); - float curScrollP = mDownScrollP + deltaP; // Modulate the overscroll to prevent users from pulling the stack too far float minScrollP = layoutAlgorithm.mMinScrollP; float maxScrollP = layoutAlgorithm.mMaxScrollP; + float curScrollP = mDownScrollP + deltaP; if (curScrollP < minScrollP || curScrollP > maxScrollP) { float clampedScrollP = Utilities.clamp(curScrollP, minScrollP, maxScrollP); float overscrollP = (curScrollP - clampedScrollP); float overscrollX = Math.abs(overscrollP) / MAX_OVERSCROLL; - curScrollP = clampedScrollP + (Math.signum(overscrollP) * - (OVERSCROLL_INTERP.getInterpolation(overscrollX) * MAX_OVERSCROLL)); + float interpX = OVERSCROLL_INTERP.getInterpolation(overscrollX); + curScrollP = clampedScrollP + Math.signum(overscrollP) * + (interpX * MAX_OVERSCROLL); } - - mScroller.setStackScroll(curScrollP); + mDownScrollP += mScroller.setDeltaStackScroll(mDownScrollP, + curScrollP - mDownScrollP); mStackViewScrolledEvent.updateY(y - mLastY); EventBus.getDefault().send(mStackViewScrolledEvent); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 6e585ae48f96..6be8a4a469bd 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -39,6 +39,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewOutlineProvider; +import android.widget.TextView; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; @@ -157,6 +158,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks @ViewDebug.ExportedProperty(deepExport=true, prefix="header_") TaskViewHeader mHeaderView; View mActionButtonView; + View mIncompatibleAppToastView; TaskViewCallbacks mCb; @ViewDebug.ExportedProperty(category="recents") @@ -345,6 +347,9 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks mActionButtonView.setScaleY(1f); mActionButtonView.setAlpha(0f); mActionButtonView.setTranslationZ(mActionButtonTranslationZ); + if (mIncompatibleAppToastView != null) { + mIncompatibleAppToastView.setVisibility(View.INVISIBLE); + } } /** @@ -536,6 +541,10 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks // These values will be animated in when onStartLaunchTargetEnterAnimation() is called setDimAlphaWithoutHeader(0); mActionButtonView.setAlpha(0f); + if (mIncompatibleAppToastView != null && + mIncompatibleAppToastView.getVisibility() == View.VISIBLE) { + mIncompatibleAppToastView.setAlpha(0f); + } } @Override @@ -554,6 +563,15 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks if (screenPinningEnabled) { showActionButton(true /* fadeIn */, duration /* fadeInDuration */); } + + if (mIncompatibleAppToastView != null && + mIncompatibleAppToastView.getVisibility() == View.VISIBLE) { + mIncompatibleAppToastView.animate() + .alpha(1f) + .setDuration(duration) + .setInterpolator(Interpolators.ALPHA_IN) + .start(); + } } @Override @@ -587,6 +605,18 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks mTask = t; mTask.addCallback(this); mIsDisabledInSafeMode = !mTask.isSystemApp && ssp.isInSafeMode(); + + if (!t.isDockable && ssp.hasDockedTask()) { + if (mIncompatibleAppToastView == null) { + mIncompatibleAppToastView = Utilities.findViewStubById(this, + R.id.incompatible_app_toast_stub).inflate(); + TextView msg = (TextView) findViewById(com.android.internal.R.id.message); + msg.setText(R.string.recents_incompatible_app_message); + } + mIncompatibleAppToastView.setVisibility(View.VISIBLE); + } else if (mIncompatibleAppToastView != null) { + mIncompatibleAppToastView.setVisibility(View.INVISIBLE); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 570ff8a07fe4..16d8e53401eb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -39,7 +39,6 @@ import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewDebug; import android.view.ViewGroup; -import android.view.ViewStub; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ProgressBar; @@ -141,15 +140,12 @@ public class TaskViewHeader extends FrameLayout // Header views ImageView mIconView; TextView mTitleView; - TextView mSubTitleView; ImageView mMoveTaskButton; ImageView mDismissButton; - ViewStub mAppOverlayViewStub; FrameLayout mAppOverlayView; ImageView mAppIconView; ImageView mAppInfoView; TextView mAppTitleView; - ViewStub mFocusTimerIndicatorStub; ProgressBar mFocusTimerIndicator; // Header drawables @@ -242,13 +238,10 @@ public class TaskViewHeader extends FrameLayout mIconView.setClickable(false); mIconView.setOnLongClickListener(this); mTitleView = (TextView) findViewById(R.id.title); - mSubTitleView = (TextView) findViewById(R.id.sub_title); mDismissButton = (ImageView) findViewById(R.id.dismiss_task); if (ssp.hasFreeformWorkspaceSupport()) { mMoveTaskButton = (ImageView) findViewById(R.id.move_task); } - mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub); - mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub); onConfigurationChanged(); } @@ -305,8 +298,7 @@ public class TaskViewHeader extends FrameLayout R.dimen.recents_task_view_header_button_padding_tablet_land, R.dimen.recents_task_view_header_button_padding, R.dimen.recents_task_view_header_button_padding_tablet_land); - updateLayoutParams(mIconView, findViewById(R.id.title_container), mMoveTaskButton, - mDismissButton); + updateLayoutParams(mIconView, mTitleView, mMoveTaskButton, mDismissButton); if (mAppOverlayView != null) { updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView); } @@ -462,13 +454,6 @@ public class TaskViewHeader extends FrameLayout mTitleView.setContentDescription(t.titleDescription); mTitleView.setTextColor(t.useLightOnPrimaryColor ? mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); - if (!t.isDockable && ssp.hasDockedTask()) { - mSubTitleView.setVisibility(View.VISIBLE); - mSubTitleView.setTextColor(t.useLightOnPrimaryColor ? - mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); - } else { - mSubTitleView.setVisibility(View.GONE); - } mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? mLightDismissDrawable : mDarkDismissDrawable); mDismissButton.setContentDescription(t.dismissDescription); @@ -491,7 +476,8 @@ public class TaskViewHeader extends FrameLayout if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) { if (mFocusTimerIndicator == null) { - mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate(); + mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this, + R.id.focus_timer_indicator_stub).inflate(); } mFocusTimerIndicator.getProgressDrawable() .setColorFilter( @@ -637,7 +623,8 @@ public class TaskViewHeader extends FrameLayout // Inflate the overlay if necessary if (mAppOverlayView == null) { - mAppOverlayView = (FrameLayout) mAppOverlayViewStub.inflate(); + mAppOverlayView = (FrameLayout) Utilities.findViewStubById(this, + R.id.app_overlay_stub).inflate(); mAppOverlayView.setBackground(mOverlayBackground); mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon); mAppIconView.setOnClickListener(this); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 3005535535cc..8461d8e75b34 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -31,12 +31,13 @@ import android.graphics.Rect; import android.graphics.Region.Op; import android.hardware.display.DisplayManager; import android.os.Bundle; +import android.os.Vibrator; import android.util.AttributeSet; import android.view.Display; import android.view.DisplayInfo; import android.view.GestureDetector; -import android.view.GestureDetector.OnDoubleTapListener; import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.VelocityTracker; @@ -45,7 +46,6 @@ import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -133,6 +133,8 @@ public class DividerView extends FrameLayout implements OnTouchListener, private boolean mGrowRecents; private ValueAnimator mCurrentAnimator; private boolean mEntranceAnimationRunning; + private boolean mExitAnimationRunning; + private int mExitStartPosition; private GestureDetector mGestureDetector; private boolean mDockedStackMinimized; @@ -445,6 +447,7 @@ public class DividerView extends FrameLayout implements OnTouchListener, mDockSide = WindowManager.DOCKED_INVALID; mCurrentAnimator = null; mEntranceAnimationRunning = false; + mExitAnimationRunning = false; EventBus.getDefault().send(new StoppedDragingEvent()); } }); @@ -654,6 +657,13 @@ public class DividerView extends FrameLayout implements OnTouchListener, mOtherTaskRect); mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null, mOtherTaskRect, null); + } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) { + calculateBoundsForPosition(taskPosition, + mDockSide, mDockedTaskRect); + calculateBoundsForPosition(mExitStartPosition, + DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); + mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null, + mOtherTaskRect, null); } else if (taskPosition != TASK_POSITION_SAME) { calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), mOtherRect); @@ -866,6 +876,9 @@ public class DividerView extends FrameLayout implements OnTouchListener, mEntranceAnimationRunning = true; resizeStack(position, mSnapAlgorithm.getMiddleTarget().position, mSnapAlgorithm.getMiddleTarget()); + + // Vibrate after docking + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); } public final void onBusEvent(RecentsDrawnEvent drawnEvent) { @@ -892,8 +905,13 @@ public class DividerView extends FrameLayout implements OnTouchListener, : mSnapAlgorithm.getDismissStartTarget(); // Don't start immediately - give a little bit time to settle the drag resize change. - stopDragging(getCurrentPosition(), target, 336 /* duration */, 100 /* startDelay */, + mExitAnimationRunning = true; + mExitStartPosition = getCurrentPosition(); + stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */, Interpolators.TOUCH_RESPONSE); + + // Vibrate after undocking + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 1b2393afcccf..3ac7b26a9968 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -63,6 +63,8 @@ import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; +import android.service.vr.IVrManager; +import android.service.vr.IVrStateCallbacks; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; @@ -262,11 +264,24 @@ public abstract class BaseStatusBar extends SystemUI implements protected AssistManager mAssistManager; + protected boolean mVrMode; + @Override // NotificationData.Environment public boolean isDeviceProvisioned() { return mDeviceProvisioned; } + private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { + @Override + public void onVrStateChanged(boolean enabled) { + mVrMode = enabled; + } + }; + + public boolean isDeviceInVrMode() { + return mVrMode; + } + protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -776,6 +791,14 @@ public abstract class BaseStatusBar extends SystemUI implements mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter, null, null); updateCurrentProfilesCache(); + + IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager")); + try { + vrManager.registerListener(mVrStateCallbacks); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register VR mode state listener: " + e); + } + } protected void notifyUserAboutHiddenNotifications() { @@ -2353,6 +2376,10 @@ public abstract class BaseStatusBar extends SystemUI implements } protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) { + if (isDeviceInVrMode()) { + return false; + } + if (mNotificationData.shouldFilterOut(sbn)) { if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index 86c1fca17a88..c2521b3d7d56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -65,8 +65,6 @@ import java.util.List; import static android.content.Context.LAYOUT_INFLATER_SERVICE; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; -import com.google.android.collect.Lists; - /** * Contains functionality for handling keyboard shortcuts. */ @@ -371,7 +369,7 @@ public final class KeyboardShortcuts { private KeyboardShortcutGroup getDefaultApplicationShortcuts() { final int userId = mContext.getUserId(); - List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = Lists.newArrayList(); + List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>(); // Assist. final AssistUtils assistUtils = new AssistUtils(mContext); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 5b005237e5d4..491ffde8071f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -348,6 +348,12 @@ public class NotificationContentView extends FrameLayout { setVisible(isShown()); } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener); + } + private void setVisible(final boolean isVisible) { if (isVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java index a5ebbbab36b3..9ed50224ed2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java @@ -232,8 +232,9 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC return; } final boolean isRtl = mParent.isLayoutRtl(); - final float left = isRtl ? -(mParent.getWidth() - mHorizSpaceForGear) : 0; - final float right = isRtl ? 0 : (mParent.getWidth() - mHorizSpaceForGear); + // TODO No need to cast to float here once b/28050538 is fixed. + final float left = (float) (isRtl ? -(mParent.getWidth() - mHorizSpaceForGear) : 0); + final float right = (float) (isRtl ? 0 : (mParent.getWidth() - mHorizSpaceForGear)); setTranslationX(onLeft ? left : right); mOnLeft = onLeft; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java index 03b51c653ac4..244536d22ef3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java @@ -240,6 +240,11 @@ public class CarBatteryController extends BroadcastReceiver implements BatteryCo } @Override + public void dispatchDemoCommand(String command, Bundle args) { + // TODO: Car demo mode. + } + + @Override public boolean isPowerSave() { // Power save is not valid for the car, so always return false. return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 75430ffe2d0a..f68b24bb3316 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -1374,6 +1374,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private boolean shouldSuppressFullScreenIntent(String key) { + if (isDeviceInVrMode()) { + return true; + } + if (mPowerManager.isInteractive()) { return mNotificationData.shouldSuppressScreenOn(key); } else { @@ -3591,11 +3595,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, dispatchDemoCommandToView(command, args, R.id.clock); } if (modeChange || command.equals(COMMAND_BATTERY)) { - dispatchDemoCommandToView(command, args, R.id.battery); + mBatteryController.dispatchDemoCommand(command, args); } if (modeChange || command.equals(COMMAND_STATUS)) { mIconController.dispatchDemoCommand(command, args); - } if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) { mNetworkController.dispatchDemoCommand(command, args); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index ea64fd8e96e3..559436b10eed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -16,10 +16,12 @@ package com.android.systemui.statusbar.policy; +import com.android.systemui.DemoMode; + import java.io.FileDescriptor; import java.io.PrintWriter; -public interface BatteryController { +public interface BatteryController extends DemoMode { /** * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 24207f3f35b9..5d734c682b49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -21,9 +21,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; +import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.util.Log; +import com.android.systemui.DemoMode; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -43,6 +45,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>(); private final PowerManager mPowerManager; private final Handler mHandler; + private final Context mContext; protected int mLevel; protected boolean mPluggedIn; @@ -52,17 +55,21 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private boolean mTestmode = false; public BatteryControllerImpl(Context context) { + mContext = context; mHandler = new Handler(); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + registerReceiver(); + updatePowerSave(); + } + + private void registerReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING); filter.addAction(ACTION_LEVEL_TEST); - context.registerReceiver(this, filter); - - updatePowerSave(); + mContext.registerReceiver(this, filter); } @Override @@ -176,4 +183,28 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave); } } + + private boolean mDemoMode; + + @Override + public void dispatchDemoCommand(String command, Bundle args) { + if (!mDemoMode && command.equals(COMMAND_ENTER)) { + mDemoMode = true; + mContext.unregisterReceiver(this); + } else if (mDemoMode && command.equals(COMMAND_EXIT)) { + mDemoMode = false; + registerReceiver(); + updatePowerSave(); + } else if (mDemoMode && command.equals(COMMAND_BATTERY)) { + String level = args.getString("level"); + String plugged = args.getString("plugged"); + if (level != null) { + mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100); + } + if (plugged != null) { + mPluggedIn = Boolean.parseBoolean(plugged); + } + fireBatteryLevelChanged(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java index 6dd196b8b0c0..c4c64e7995ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java @@ -33,24 +33,29 @@ public class DataSaverController { } private void handleRestrictBackgroundChanged(boolean isDataSaving) { - final int N = mListeners.size(); - for (int i = 0; i < N; i++) { - mListeners.get(i).onDataSaverChanged(isDataSaving); + synchronized (mListeners) { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onDataSaverChanged(isDataSaving); + } } } public void addListener(Listener listener) { - mListeners.add(listener); - if (mListeners.size() == 1) { - mPolicyManager.registerListener(mPolicyListener); + synchronized (mListeners) { + mListeners.add(listener); + if (mListeners.size() == 1) { + mPolicyManager.registerListener(mPolicyListener); + } } listener.onDataSaverChanged(isDataSaverEnabled()); } public void remListener(Listener listener) { - mListeners.remove(listener); - if (mListeners.size() == 0) { - mPolicyManager.unregisterListener(mPolicyListener); + synchronized (mListeners) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + mPolicyManager.unregisterListener(mPolicyListener); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 7c5cdfbfcec1..8b52bf65a673 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -3799,6 +3799,7 @@ public class NotificationStackScrollLayout extends ViewGroup } else { getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater); } + mContinuousShadowUpdate = continuousShadowUpdate; } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java index 748ee97c0621..ae104cd968ad 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java @@ -36,11 +36,15 @@ public class TunerActivity extends SettingsDrawerActivity implements super.onCreate(savedInstanceState); if (getFragmentManager().findFragmentByTag(TAG_TUNER) == null) { + boolean showDemoMode = getIntent().getAction().equals( + "com.android.settings.action.DEMO_MODE"); boolean showNightMode = getIntent().getBooleanExtra( NightModeFragment.EXTRA_SHOW_NIGHT_MODE, false); + final PreferenceFragment fragment = showNightMode ? new NightModeFragment() + : showDemoMode ? new DemoModeFragment() + : new TunerFragment(); getFragmentManager().beginTransaction().replace(R.id.content_frame, - showNightMode ? new NightModeFragment() : new TunerFragment(), - TAG_TUNER).commit(); + fragment, TAG_TUNER).commit(); } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java index e205ff5749b0..011e159bb12f 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java @@ -16,6 +16,8 @@ package com.android.systemui.tv.pip; +import android.animation.Animator; +import android.animation.AnimatorInflater; import android.app.Activity; import android.app.ActivityOptions; import android.content.Context; @@ -50,9 +52,11 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener private ImageView mGuideButtonPlayPauseImageView; private final Runnable mHideGuideOverlayRunnable = new Runnable() { public void run() { - mGuideOverlayView.setVisibility(View.GONE); + mFadeOutAnimation.start(); } }; + private Animator mFadeInAnimation; + private Animator mFadeOutAnimation; /** * Shows PIP overlay UI only if it's not there. @@ -74,11 +78,18 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener setContentView(R.layout.tv_pip_overlay); mGuideOverlayView = findViewById(R.id.guide_overlay); mPipManager.addListener(this); + mFadeInAnimation = AnimatorInflater.loadAnimator( + this, R.anim.tv_pip_overlay_fade_in_animation); + mFadeInAnimation.setTarget(mGuideOverlayView); + mFadeOutAnimation = AnimatorInflater.loadAnimator( + this, R.anim.tv_pip_overlay_fade_out_animation); + mFadeOutAnimation.setTarget(mGuideOverlayView); } @Override protected void onResume() { super.onResume(); + mFadeInAnimation.start(); mHandler.removeCallbacks(mHideGuideOverlayRunnable); mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 180d9180a9f7..2c53e2962049 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -16,6 +16,7 @@ package com.android.systemui.usb; +import android.annotation.NonNull; import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; @@ -97,6 +98,11 @@ public class StorageNotification extends SystemUI { public void onDiskScanned(DiskInfo disk, int volumeCount) { onDiskScannedInternal(disk, volumeCount); } + + @Override + public void onDiskDestroyed(DiskInfo disk) { + onDiskDestroyedInternal(disk); + } }; private final BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() { @@ -238,6 +244,15 @@ public class StorageNotification extends SystemUI { } } + /** + * Remove all notifications for a disk when it goes away. + * + * @param disk The disk that went away. + */ + private void onDiskDestroyedInternal(@NonNull DiskInfo disk) { + mNotificationManager.cancelAsUser(disk.getId(), DISK_ID, UserHandle.ALL); + } + private void onVolumeStateChangedInternal(VolumeInfo vol) { switch (vol.getType()) { case VolumeInfo.TYPE_PRIVATE: diff --git a/rs/java/android/renderscript/ScriptGroup.java b/rs/java/android/renderscript/ScriptGroup.java index 219f16b91baf..35ae8b406e45 100644 --- a/rs/java/android/renderscript/ScriptGroup.java +++ b/rs/java/android/renderscript/ScriptGroup.java @@ -187,6 +187,23 @@ public final class ScriptGroup extends BaseObj { guard.open("destroy"); } + /** + * Destroys this Closure and the Allocation for its return value + */ + public void destroy() { + super.destroy(); + if (mReturnValue != null) { + mReturnValue.destroy(); + } + } + + protected void finalize() throws Throwable { + // Set null mReturnValue to avoid double-destroying it, in case its + // finalizer races ahead. + mReturnValue = null; + super.finalize(); + } + private void retrieveValueAndDependenceInfo(RenderScript rs, int index, Script.FieldID fid, Object obj, long[] values, int[] sizes, @@ -1015,6 +1032,8 @@ public final class ScriptGroup extends BaseObj { throw new RSIllegalArgumentException("invalid script group name"); } ScriptGroup ret = new ScriptGroup(mRS, name, mClosures, mInputs, outputs); + mClosures = new ArrayList<Closure>(); + mInputs = new ArrayList<Input>(); return ret; } @@ -1042,4 +1061,20 @@ public final class ScriptGroup extends BaseObj { } + /** + * Destroy this ScriptGroup and all Closures in it + */ + public void destroy() { + super.destroy(); + for(Closure c : mClosures) { + c.destroy(); + } + } + + protected void finalize() throws Throwable { + // Clear out the list mClosures to avoid double-destroying the closures, + // in case their finalizers race ahead. + mClosures.clear(); + super.finalize(); + } } diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index aa986489e1c2..5a9048898d02 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -58,7 +58,6 @@ public class GestureLauncherService extends SystemService { * as a camera launch. */ private static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300; - private static final long CAMERA_POWER_DOUBLE_TAP_MIN_TIME_MS = 120; /** The listener that receives the gesture event. */ private final GestureEventListener mGestureListener = new GestureEventListener(); @@ -260,8 +259,7 @@ public class GestureLauncherService extends SystemService { synchronized (this) { doubleTapInterval = event.getEventTime() - mLastPowerDown; if (mCameraDoubleTapPowerEnabled - && doubleTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - && doubleTapInterval > CAMERA_POWER_DOUBLE_TAP_MIN_TIME_MS) { + && doubleTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) { launched = true; intercept = interactive; } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 22cc066434dd..544e6455fab5 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -2966,16 +2966,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } setInputMethodLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId); - if (mSubtypeSwitchedByShortCutToast != null) { - mSubtypeSwitchedByShortCutToast.cancel(); - mSubtypeSwitchedByShortCutToast = null; - } - if ((mImeWindowVis & InputMethodService.IME_VISIBLE) != 0) { - // IME window is shown. The user should be able to visually understand that the - // subtype is changed in most of cases. To avoid UI overlap, we do not show a toast - // in this case. - return; - } final InputMethodInfo newInputMethodInfo = mMethodMap.get(mCurMethodId); if (newInputMethodInfo == null) { return; @@ -2983,8 +2973,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final CharSequence toastText = InputMethodUtils.getImeAndSubtypeDisplayName(mContext, newInputMethodInfo, mCurrentSubtype); if (!TextUtils.isEmpty(toastText)) { - mSubtypeSwitchedByShortCutToast = Toast.makeText(mContext, toastText.toString(), - Toast.LENGTH_SHORT); + if (mSubtypeSwitchedByShortCutToast == null) { + mSubtypeSwitchedByShortCutToast = Toast.makeText(mContext, toastText, + Toast.LENGTH_SHORT); + } else { + mSubtypeSwitchedByShortCutToast.setText(toastText); + } mSubtypeSwitchedByShortCutToast.show(); } } diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java index d136f1af7654..9ab63003c8dc 100644 --- a/services/core/java/com/android/server/LockSettingsStorage.java +++ b/services/core/java/com/android/server/LockSettingsStorage.java @@ -401,7 +401,8 @@ class LockSettingsStorage { return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE); } - private String getChildProfileLockFile(int userId) { + @VisibleForTesting + String getChildProfileLockFile(int userId) { return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE); } diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index fd9a94d4e2ef..229a3f4dd835 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -1010,7 +1010,7 @@ class MountService extends IMountService.Stub Configuration config = new Configuration(); config.setLocale(locale); try { - ActivityManagerNative.getDefault().updateConfiguration(config); + ActivityManagerNative.getDefault().updatePersistentConfiguration(config); } catch (RemoteException e) { Slog.e(TAG, "Error setting system locale from mount service", e); } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 9bc6bffddaf3..05e4245fd175 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -85,6 +85,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -267,10 +268,10 @@ public class AccountManagerService private int debugDbInsertionPoint = -1; private SQLiteStatement statementForLogging; - UserAccounts(Context context, int userId) { + UserAccounts(Context context, int userId, File preNDbFile, File deDbFile) { this.userId = userId; synchronized (cacheLock) { - openHelper = DeDatabaseHelper.create(context, userId); + openHelper = DeDatabaseHelper.create(context, userId, preNDbFile, deDbFile); } } } @@ -540,7 +541,9 @@ public class AccountManagerService UserAccounts accounts = mUsers.get(userId); boolean validateAccounts = false; if (accounts == null) { - accounts = new UserAccounts(mContext, userId); + File preNDbFile = new File(getPreNDatabaseName(userId)); + File deDbFile = new File(getDeDatabaseName(userId)); + accounts = new UserAccounts(mContext, userId, preNDbFile, deDbFile); initializeDebugDbSizeAndCompileSqlStatementForLogging( accounts.openHelper.getWritableDatabase(), accounts); mUsers.append(userId, accounts); @@ -551,8 +554,10 @@ public class AccountManagerService if (!accounts.openHelper.isCeDatabaseAttached() && mUnlockedUsers.get(userId)) { Log.i(TAG, "User " + userId + " is unlocked - opening CE database"); synchronized (accounts.cacheLock) { - CeDatabaseHelper.create(mContext, userId); - accounts.openHelper.attachCeDatabase(); + File preNDatabaseFile = new File(getPreNDatabaseName(userId)); + File ceDatabaseFile = new File(getCeDatabaseName(userId)); + CeDatabaseHelper.create(mContext, userId, preNDatabaseFile, ceDatabaseFile); + accounts.openHelper.attachCeDatabase(ceDatabaseFile); } syncDeCeAccountsLocked(accounts); } @@ -647,7 +652,8 @@ public class AccountManagerService } } - private void onUserUnlocked(Intent intent) { + @VisibleForTesting + void onUserUnlocked(Intent intent) { int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "onUserUnlocked " + userId); @@ -1553,7 +1559,7 @@ public class AccountManagerService } } - /* For testing */ + @VisibleForTesting protected void removeAccountInternal(Account account) { removeAccountInternal(getUserAccountsForCaller(), account, getCallingUid()); } @@ -4044,7 +4050,8 @@ public class AccountManagerService } } - static String getPreNDatabaseName(int userId) { + @VisibleForTesting + String getPreNDatabaseName(int userId) { File systemDir = Environment.getDataSystemDirectory(); File databaseFile = new File(Environment.getUserSystemDirectory(userId), PRE_N_DATABASE_NAME); @@ -4070,13 +4077,15 @@ public class AccountManagerService return databaseFile.getPath(); } - static String getDeDatabaseName(int userId) { + @VisibleForTesting + String getDeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemDeDirectory(userId), DE_DATABASE_NAME); return databaseFile.getPath(); } - static String getCeDatabaseName(int userId) { + @VisibleForTesting + String getCeDatabaseName(int userId) { File databaseFile = new File(Environment.getDataSystemCeDirectory(userId), CE_DATABASE_NAME); return databaseFile.getPath(); @@ -4217,13 +4226,11 @@ public class AccountManagerService } static class PreNDatabaseHelper extends SQLiteOpenHelper { - private final Context mContext; private final int mUserId; - public PreNDatabaseHelper(Context context, int userId) { - super(context, AccountManagerService.getPreNDatabaseName(userId), null, - PRE_N_DATABASE_VERSION); + public PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) { + super(context, preNDatabaseName, null, PRE_N_DATABASE_VERSION); mContext = context; mUserId = userId; } @@ -4360,8 +4367,8 @@ public class AccountManagerService private final int mUserId; private volatile boolean mCeAttached; - private DeDatabaseHelper(Context context, int userId) { - super(context, getDeDatabaseName(userId), null, DE_DATABASE_VERSION); + private DeDatabaseHelper(Context context, int userId, String deDatabaseName) { + super(context, deDatabaseName, null, DE_DATABASE_VERSION); mUserId = userId; } @@ -4426,8 +4433,7 @@ public class AccountManagerService } } - public void attachCeDatabase() { - File ceDbFile = new File(getCeDatabaseName(mUserId)); + public void attachCeDatabase(File ceDbFile) { SQLiteDatabase db = getWritableDatabase(); db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb"); mCeAttached = true; @@ -4440,8 +4446,8 @@ public class AccountManagerService public SQLiteDatabase getReadableDatabaseUserIsUnlocked() { if(!mCeAttached) { - Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user " - + mUserId + " is still locked ", new Throwable()); + Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user " + mUserId + + " is still locked. CE database is not yet available.", new Throwable()); } return super.getReadableDatabase(); } @@ -4449,7 +4455,7 @@ public class AccountManagerService public SQLiteDatabase getWritableDatabaseUserIsUnlocked() { if(!mCeAttached) { Log.wtf(TAG, "getWritableDatabaseUserIsUnlocked called while user " + mUserId - + " is still locked ", new Throwable()); + + " is still locked. CE database is not yet available.", new Throwable()); } return super.getWritableDatabase(); } @@ -4502,20 +4508,24 @@ public class AccountManagerService db.execSQL("DETACH DATABASE preNDb"); } - static DeDatabaseHelper create(Context context, int userId) { - File oldDb = new File(getPreNDatabaseName(userId)); - File newDb = new File(getDeDatabaseName(userId)); - boolean newDbExists = newDb.exists(); - DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId); + static DeDatabaseHelper create( + Context context, + int userId, + File preNDatabaseFile, + File deDatabaseFile) { + boolean newDbExists = deDatabaseFile.exists(); + DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId, + deDatabaseFile.getPath()); // If the db just created, and there is a legacy db, migrate it - if (!newDbExists && oldDb.exists()) { + if (!newDbExists && preNDatabaseFile.exists()) { // Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION - PreNDatabaseHelper preNDatabaseHelper = new PreNDatabaseHelper(context, userId); + PreNDatabaseHelper preNDatabaseHelper = new PreNDatabaseHelper(context, userId, + preNDatabaseFile.getPath()); // Open the database to force upgrade if required preNDatabaseHelper.getWritableDatabase(); preNDatabaseHelper.close(); // Move data without SPII to DE - deDatabaseHelper.migratePreNDbToDe(oldDb); + deDatabaseHelper.migratePreNDbToDe(preNDatabaseFile); } return deDatabaseHelper; } @@ -4523,8 +4533,8 @@ public class AccountManagerService static class CeDatabaseHelper extends SQLiteOpenHelper { - public CeDatabaseHelper(Context context, int userId) { - super(context, getCeDatabaseName(userId), null, CE_DATABASE_VERSION); + public CeDatabaseHelper(Context context, String ceDatabaseName) { + super(context, ceDatabaseName, null, CE_DATABASE_VERSION); } /** @@ -4640,27 +4650,28 @@ public class AccountManagerService * @param context * @param userId id of the user where the database is located */ - static CeDatabaseHelper create(Context context, int userId) { - - File oldDatabaseFile = new File(getPreNDatabaseName(userId)); - File ceDatabaseFile = new File(getCeDatabaseName(userId)); + static CeDatabaseHelper create( + Context context, + int userId, + File preNDatabaseFile, + File ceDatabaseFile) { boolean newDbExists = ceDatabaseFile.exists(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "CeDatabaseHelper.create userId=" + userId + " oldDbExists=" - + oldDatabaseFile.exists() + " newDbExists=" + newDbExists); + + preNDatabaseFile.exists() + " newDbExists=" + newDbExists); } boolean removeOldDb = false; - if (!newDbExists && oldDatabaseFile.exists()) { - removeOldDb = migratePreNDbToCe(oldDatabaseFile, ceDatabaseFile); + if (!newDbExists && preNDatabaseFile.exists()) { + removeOldDb = migratePreNDbToCe(preNDatabaseFile, ceDatabaseFile); } // Try to open and upgrade if necessary - CeDatabaseHelper ceHelper = new CeDatabaseHelper(context, userId); + CeDatabaseHelper ceHelper = new CeDatabaseHelper(context, ceDatabaseFile.getPath()); ceHelper.getWritableDatabase(); ceHelper.close(); if (removeOldDb) { // TODO STOPSHIP - backup file during testing. Remove file before the release - Log.i(TAG, "Migration complete - creating backup of old db " + oldDatabaseFile); - renameToBakFile(oldDatabaseFile); + Log.i(TAG, "Migration complete - creating backup of old db " + preNDatabaseFile); + renameToBakFile(preNDatabaseFile); } return ceHelper; } @@ -4825,12 +4836,14 @@ public class AccountManagerService } } + @VisibleForTesting protected void installNotification(final int notificationId, final Notification n, UserHandle user) { ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) .notifyAsUser(null, notificationId, n, user); } + @VisibleForTesting protected void cancelNotification(int id, UserHandle user) { long identityToken = clearCallingIdentity(); try { @@ -5368,7 +5381,7 @@ public class AccountManagerService HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account); if (userDataForAccount == null) { // need to populate the cache for this account - final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked(); userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); accounts.userDataCache.put(account, userDataForAccount); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b11dc1182f33..3ec51e3dfa42 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17144,7 +17144,7 @@ public final class ActivityManagerService extends ActivityManagerNative // but historically it has not been protected and apps may be using it // to poke their own app widget. So, instead of making it protected, // just limit it to the caller. - if (callerApp == null) { + if (callerPackage == null) { String msg = "Permission Denial: not allowed to send broadcast " + action + " from unknown caller."; Slog.w(TAG, msg); @@ -17153,17 +17153,17 @@ public final class ActivityManagerService extends ActivityManagerNative // They are good enough to send to an explicit component... verify // it is being sent to the calling app. if (!intent.getComponent().getPackageName().equals( - callerApp.info.packageName)) { + callerPackage)) { String msg = "Permission Denial: not allowed to send broadcast " + action + " to " + intent.getComponent().getPackageName() + " from " - + callerApp.info.packageName; + + callerPackage; Slog.w(TAG, msg); throw new SecurityException(msg); } } else { // Limit broadcast to their own package. - intent.setPackage(callerApp.info.packageName); + intent.setPackage(callerPackage); } } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index d34e8fc461ea..53c602408d5d 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -4248,6 +4248,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // Work Challenge is present) let startActivityInPackage handle the intercepting. if (!mService.mUserController.shouldConfirmCredentials(task.userId) && task.getRootActivity() != null) { + mActivityMetricsLogger.notifyActivityLaunching(); mService.moveTaskToFrontLocked(task.taskId, 0, bOptions); // If we are launching the task in the docked stack, put it into resizing mode so diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java index 1825c88628cc..0e192eab498f 100644 --- a/services/core/java/com/android/server/am/PreBootBroadcaster.java +++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java @@ -69,6 +69,12 @@ public abstract class PreBootBroadcaster extends IIntentReceiver.Stub { return; } + if (!mService.isUserRunning(mUserId, 0)) { + Slog.i(TAG, "User " + mUserId + " is no longer running; skipping remaining receivers"); + onFinished(); + return; + } + final ResolveInfo ri = mTargets.get(mIndex++); final ComponentName componentName = ri.activityInfo.getComponentName(); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3cd194b73a8d..9a5988c5aaf5 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1496,7 +1496,7 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification sbn = mNotificationList.get(i).sbn; if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId && (sbn.getNotification().flags - & Notification.FLAG_AUTOGROUP_SUMMARY) != 0) { + & Notification.FLAG_AUTOGROUP_SUMMARY) == 0) { // We could pass back a cloneLight() but clients might get confused and // try to send this thing back to notify() again, which would not work // very well. @@ -2009,7 +2009,7 @@ public class NotificationManagerService extends SystemService { @Override public ComponentName getEffectsSuppressor() { enforceSystemOrSystemUIOrVolume("INotificationManager.getEffectsSuppressor"); - return mEffectsSuppressors.get(0); + return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null; } @Override @@ -2459,12 +2459,10 @@ public class NotificationManagerService extends SystemService { // Fix the notification as best we can. try { - if (!"android".equals(pkg) && !"system".equals(pkg)) { - Notification.addFieldsFromContext(getContext().createApplicationContext( - getContext().getPackageManager().getApplicationInfoAsUser( - pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId), - Context.CONTEXT_RESTRICTED), notification); - } + final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser( + pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId); + Notification.addFieldsFromContext(ai, userId, notification); } catch (NameNotFoundException e) { Slog.e(TAG, "Cannot create a context for sending app", e); return; @@ -2545,18 +2543,13 @@ public class NotificationManagerService extends SystemService { // Handle grouped notifications and bail out early if we // can to avoid extracting signals. handleGroupedNotificationLocked(r, old, callingUid, callingPid); - boolean ignoreNotification = - removeUnusedGroupedNotificationLocked(r, old, callingUid, callingPid); - if (DBG) Slog.d(TAG, "ignoreNotification is " + ignoreNotification); // This conditional is a dirty hack to limit the logging done on // behalf of the download manager without affecting other apps. if (!pkg.equals("com.android.providers.downloads") || Log.isLoggable("DownloadManager", Log.VERBOSE)) { int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW; - if (ignoreNotification) { - enqueueStatus = EVENTLOG_ENQUEUE_STATUS_IGNORED; - } else if (old != null) { + if (old != null) { enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE; } EventLogTags.writeNotificationEnqueue(callingUid, callingPid, @@ -2564,10 +2557,6 @@ public class NotificationManagerService extends SystemService { enqueueStatus); } - if (ignoreNotification) { - return; - } - mRankingHelper.extractSignals(r); final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid); @@ -2683,58 +2672,6 @@ public class NotificationManagerService extends SystemService { } } - /** - * Performs group notification optimizations if SysUI is the only active - * notification listener and returns whether the given notification should - * be ignored. - * - * <p>Returns true if the given notification is a child of a group with a - * summary, which means that SysUI will never show it, and hence the new - * notification can be safely ignored. Also cancels any previous instance - * of the ignored notification.</p> - * - * <p>For summaries, cancels all children of that group, as SysUI will - * never show them anymore.</p> - * - * @return true if the given notification can be ignored as an optimization - */ - private boolean removeUnusedGroupedNotificationLocked(NotificationRecord r, - NotificationRecord old, int callingUid, int callingPid) { - if (!ENABLE_CHILD_NOTIFICATIONS) { - // No optimizations are possible if listeners want groups. - if (mListeners.notificationGroupsDesired()) { - return false; - } - - StatusBarNotification sbn = r.sbn; - String group = sbn.getGroupKey(); - boolean isSummary = sbn.getNotification().isGroupSummary(); - boolean isChild = !isSummary && sbn.isGroup(); - - NotificationRecord summary = mSummaryByGroupKey.get(group); - if (isChild && summary != null) { - // Child with an active summary -> ignore - if (DBG) { - Slog.d(TAG, "Ignoring group child " + sbn.getKey() + " due to existing summary " - + summary.getKey()); - } - // Make sure we don't leave an old version of the notification around. - if (old != null) { - if (DBG) { - Slog.d(TAG, "Canceling old version of ignored group child " + sbn.getKey()); - } - cancelNotificationLocked(old, false, REASON_GROUP_OPTIMIZATION); - } - return true; - } else if (isSummary) { - // Summary -> cancel children - cancelGroupChildrenLocked(r, callingUid, callingPid, null, - REASON_GROUP_OPTIMIZATION); - } - } - return false; - } - @VisibleForTesting void buzzBeepBlinkLocked(NotificationRecord record) { boolean buzz = false; @@ -3871,7 +3808,6 @@ public class NotificationManagerService extends SystemService { public class NotificationListeners extends ManagedServices { private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>(); - private boolean mNotificationGroupsDesired; public NotificationListeners() { super(getContext(), mHandler, mNotificationList, mUserProfiles); @@ -3904,7 +3840,6 @@ public class NotificationManagerService extends SystemService { final INotificationListener listener = (INotificationListener) info.service; final NotificationRankingUpdate update; synchronized (mNotificationList) { - updateNotificationGroupsDesiredLocked(); update = makeRankingUpdateLocked(info); } try { @@ -3921,7 +3856,6 @@ public class NotificationManagerService extends SystemService { updateEffectsSuppressorLocked(); } mLightTrimListeners.remove(removed); - updateNotificationGroupsDesiredLocked(); } public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) { @@ -4114,31 +4048,6 @@ public class NotificationManagerService extends SystemService { } return false; } - - /** - * Returns whether any of the currently registered listeners wants to receive notification - * groups. - * - * <p>Currently we assume groups are desired by non-SystemUI listeners.</p> - */ - public boolean notificationGroupsDesired() { - return mNotificationGroupsDesired; - } - - private void updateNotificationGroupsDesiredLocked() { - mNotificationGroupsDesired = true; - // No listeners, no groups. - if (mServices.isEmpty()) { - mNotificationGroupsDesired = false; - return; - } - // One listener: Check whether it's SysUI. - if (mServices.size() == 1 && - mServices.get(0).component.getPackageName().equals("com.android.systemui")) { - mNotificationGroupsDesired = false; - return; - } - } } public static final class DumpFilter { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index f4b60acba947..43a0b9174f8e 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -147,6 +147,7 @@ public class LauncherAppsService extends SystemService { @Override public void addOnAppsChangedListener(String callingPackage, IOnAppsChangedListener listener) throws RemoteException { + verifyCallingPackage(callingPackage); synchronized (mListeners) { if (DEBUG) { Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle()); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c12425575485..1641e1dce169 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -419,26 +419,26 @@ public class ShortcutService extends IShortcutService.Stub { result = false; } - mSaveDelayMillis = (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, - DEFAULT_SAVE_DELAY_MS); + mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, + DEFAULT_SAVE_DELAY_MS)); - mResetInterval = parser.getLong( + mResetInterval = Math.max(1, parser.getLong( ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) - * 1000L; + * 1000L); - mMaxDailyUpdates = (int) parser.getLong( - ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES); + mMaxDailyUpdates = Math.max(0, (int) parser.getLong( + ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES)); - mMaxDynamicShortcuts = (int) parser.getLong( - ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP); + mMaxDynamicShortcuts = Math.max(0, (int) parser.getLong( + ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP)); - final int iconDimensionDp = injectIsLowRamDevice() + final int iconDimensionDp = Math.max(1, injectIsLowRamDevice() ? (int) parser.getLong( ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP) : (int) parser.getLong( ConfigConstants.KEY_MAX_ICON_DIMENSION_DP, - DEFAULT_MAX_ICON_DIMENSION_DP); + DEFAULT_MAX_ICON_DIMENSION_DP)); mMaxIconDimension = injectDipToPixel(iconDimensionDp); @@ -1128,7 +1128,7 @@ public class ShortcutService extends IShortcutService.Stub { if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) { return; // Caller is valid. } - throw new SecurityException("Caller UID= doesn't own " + packageName); + throw new SecurityException("Calling package name mismatch"); } void postToHandler(Runnable r) { @@ -1425,6 +1425,8 @@ public class ShortcutService extends IShortcutService.Stub { @Override public int getIconMaxDimensions(String packageName, int userId) throws RemoteException { + verifyCaller(packageName, userId); + synchronized (mLock) { return mMaxIconDimension; } @@ -1445,7 +1447,15 @@ public class ShortcutService extends IShortcutService.Stub { getUserShortcutsLocked(userId).resetThrottling(); } scheduleSaveUser(userId); - Slog.i(TAG, "ShortcutManager: throttling counter reset"); + Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId); + } + + void resetAllThrottlingInner() { + synchronized (mLock) { + mRawLastResetTime = injectCurrentTimeMillis(); + } + scheduleSaveBaseState(); + Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); } // We override this method in unit tests to do a simpler check. @@ -1528,14 +1538,20 @@ public class ShortcutService extends IShortcutService.Stub { // === House keeping === + @VisibleForTesting + void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) { + cleanUpPackageLocked(packageName, owningUserId, packageUserId, + /* forceForCommandLine= */ false); + } + /** * Remove all the information associated with a package. This will really remove all the * information, including the restore information (i.e. it'll remove packages even if they're * shadow). */ - @VisibleForTesting - void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) { - if (isPackageInstalled(packageName, packageUserId)) { + private void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, + boolean forceForCommandLine) { + if (!forceForCommandLine && isPackageInstalled(packageName, packageUserId)) { wtf("Package " + packageName + " is still installed for user " + packageUserId); return; } @@ -1863,9 +1879,15 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, packageUserId)); } + handlePackageRemovedInner(packageName, packageUserId, /* forceForCommandLine =*/ false); + } + + private void handlePackageRemovedInner(String packageName, @UserIdInt int packageUserId, + boolean forceForCommandLine) { synchronized (mLock) { forEachLoadedUserLocked(user -> - cleanUpPackageLocked(packageName, user.getUserId(), packageUserId)); + cleanUpPackageLocked(packageName, user.getUserId(), packageUserId, + forceForCommandLine)); } } @@ -2046,17 +2068,26 @@ public class ShortcutService extends IShortcutService.Stub { pw.print(formatTime(next)); pw.println(); - pw.print(" Max icon dim: "); - pw.print(mMaxIconDimension); - pw.print(" Icon format: "); - pw.print(mIconPersistFormat); - pw.print(" Icon quality: "); + pw.print(" Config:"); + pw.print(" Max icon dim: "); + pw.println(mMaxIconDimension); + pw.print(" Icon format: "); + pw.println(mIconPersistFormat); + pw.print(" Icon quality: "); pw.println(mIconPersistQuality); + pw.print(" saveDelayMillis:"); + pw.println(mSaveDelayMillis); + pw.print(" resetInterval:"); + pw.println(mResetInterval); + pw.print(" maxDailyUpdates:"); + pw.println(mMaxDailyUpdates); + pw.print(" maxDynamicShortcuts:"); + pw.println(mMaxDynamicShortcuts); pw.println(); pw.println(" Stats:"); synchronized (mStatLock) { - final String p = " "; + final String p = " "; dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()"); dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check"); @@ -2142,6 +2173,9 @@ public class ShortcutService extends IShortcutService.Stub { case "reset-throttling": handleResetThrottling(); break; + case "reset-all-throttling": + handleResetAllThrottling(); + break; case "override-config": handleOverrideConfig(); break; @@ -2160,6 +2194,9 @@ public class ShortcutService extends IShortcutService.Stub { case "unload-user": handleUnloadUser(); break; + case "clear-shortcuts": + handleClearShortcuts(); + break; default: return handleDefaultCommands(cmd); } @@ -2179,9 +2216,12 @@ public class ShortcutService extends IShortcutService.Stub { pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE"); pw.println(" Reset throttling for a package"); pw.println(); - pw.println("cmd shortcut reset-throttling"); + pw.println("cmd shortcut reset-throttling [--user USER_ID]"); pw.println(" Reset throttling for all packages and users"); pw.println(); + pw.println("cmd shortcut reset-all-throttling"); + pw.println(" Reset the throttling state for all users"); + pw.println(); pw.println("cmd shortcut override-config CONFIG"); pw.println(" Override the configuration for testing (will last until reboot)"); pw.println(); @@ -2201,13 +2241,23 @@ public class ShortcutService extends IShortcutService.Stub { pw.println(" Unload a user from the memory"); pw.println(" (This should not affect any observable behavior)"); pw.println(); + pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE"); + pw.println(" Remove all shortcuts from a package, including pinned shortcuts"); + pw.println(); } - private int handleResetThrottling() throws CommandException { + private void handleResetThrottling() throws CommandException { parseOptions(/* takeUser =*/ true); + Slog.i(TAG, "cmd: handleResetThrottling"); + resetThrottlingInner(mUserId); - return 0; + } + + private void handleResetAllThrottling() { + Slog.i(TAG, "cmd: handleResetAllThrottling"); + + resetAllThrottlingInner(); } private void handleResetPackageThrottling() throws CommandException { @@ -2215,6 +2265,8 @@ public class ShortcutService extends IShortcutService.Stub { final String packageName = getNextArgRequired(); + Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName); + synchronized (mLock) { getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine(); saveUserLocked(mUserId); @@ -2224,6 +2276,8 @@ public class ShortcutService extends IShortcutService.Stub { private void handleOverrideConfig() throws CommandException { final String config = getNextArgRequired(); + Slog.i(TAG, "cmd: handleOverrideConfig: " + config); + synchronized (mLock) { if (!updateConfigurationLocked(config)) { throw new CommandException("override-config failed. See logcat for details."); @@ -2232,6 +2286,8 @@ public class ShortcutService extends IShortcutService.Stub { } private void handleResetConfig() { + Slog.i(TAG, "cmd: handleResetConfig"); + synchronized (mLock) { loadConfigurationLocked(); } @@ -2276,8 +2332,20 @@ public class ShortcutService extends IShortcutService.Stub { private void handleUnloadUser() throws CommandException { parseOptions(/* takeUser =*/ true); + Slog.i(TAG, "cmd: handleUnloadUser: " + mUserId); + ShortcutService.this.handleCleanupUser(mUserId); } + + private void handleClearShortcuts() throws CommandException { + parseOptions(/* takeUser =*/ true); + final String packageName = getNextArgRequired(); + + Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName); + + ShortcutService.this.handlePackageRemovedInner(packageName, mUserId, + /* forceForCommandLine= */ true); + } } // === Unit test support === diff --git a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java index 160d44c58bc9..27077f2f74f7 100644 --- a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java +++ b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java @@ -27,8 +27,11 @@ import android.graphics.PixelFormat; import android.graphics.drawable.ColorDrawable; import android.os.Handler; import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; +import android.service.vr.IVrManager; import android.util.DisplayMetrics; import android.util.Slog; import android.util.SparseBooleanArray; @@ -45,6 +48,7 @@ import android.widget.Button; import android.widget.FrameLayout; import com.android.internal.R; +import com.android.server.vr.VrManagerService; /** * Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden @@ -66,6 +70,7 @@ public class ImmersiveModeConfirmation { private long mPanicTime; private WindowManager mWindowManager; private int mCurrentUserId; + private IVrManager mVrManager; public ImmersiveModeConfirmation(Context context) { mContext = context; @@ -75,6 +80,8 @@ public class ImmersiveModeConfirmation { .getInteger(R.integer.config_immersive_mode_confirmation_panic); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mVrManager = (IVrManager) IVrManager.Stub.asInterface( + ServiceManager.getService(VrManagerService.VR_MANAGER_BINDER_SERVICE)); } private long getNavBarExitDuration() { @@ -112,6 +119,14 @@ public class ImmersiveModeConfirmation { } } + private boolean getVrMode() { + boolean vrMode = false; + try { + vrMode = mVrManager.getVrModeState(); + } catch (RemoteException ex) { } + return vrMode; + } + public void immersiveModeChanged(String pkg, boolean isImmersiveMode, boolean userSetupComplete) { mHandler.removeMessages(H.SHOW); @@ -119,7 +134,10 @@ public class ImmersiveModeConfirmation { final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg); if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s mConfirmed=%s", disabled, mConfirmed)); - if (!disabled && (DEBUG_SHOW_EVERY_TIME || !mConfirmed) && userSetupComplete) { + if (!disabled + && (DEBUG_SHOW_EVERY_TIME || !mConfirmed) + && userSetupComplete + && !getVrMode()) { mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs); } } else { diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java index a14c6145c548..ab404dbad910 100644 --- a/services/core/java/com/android/server/policy/ShortcutManager.java +++ b/services/core/java/com/android/server/policy/ShortcutManager.java @@ -27,7 +27,9 @@ import android.util.Log; import android.util.SparseArray; import android.view.KeyCharacterMap; import android.view.KeyEvent; + import com.android.internal.util.XmlUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index f004b4592413..49ff385ee27f 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -50,6 +50,8 @@ import com.android.server.vr.EnabledComponentsObserver.EnabledComponentChangeLis import com.android.server.utils.ManagedApplicationService; import com.android.server.utils.ManagedApplicationService.BinderChecker; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.lang.StringBuilder; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -103,6 +105,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC new RemoteCallbackList<>(); private final ArraySet<String> mPreviousToggledListenerSettings = new ArraySet<>(); private String mPreviousNotificationPolicyAccessPackage; + private String mPreviousCoarseLocationPackage; private String mPreviousManageOverlayPackage; private static final int MSG_VR_STATE_CHANGE = 0; @@ -186,6 +189,32 @@ public class VrManagerService extends SystemService implements EnabledComponentC return VrManagerService.this.getVrMode(); } + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("permission denied: can't dump VrManagerService from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + return; + } + pw.print("mVrModeEnabled="); + pw.println(mVrModeEnabled); + pw.print("mCurrentVrModeUser="); + pw.println(mCurrentVrModeUser); + pw.print("mRemoteCallbacks="); + int i=mRemoteCallbacks.beginBroadcast(); // create the broadcast item array + while(i-->0) { + pw.print(mRemoteCallbacks.getBroadcastItem(i)); + if (i>0) pw.print(", "); + } + mRemoteCallbacks.finishBroadcast(); + pw.println(); + pw.print("mCurrentVrService="); + pw.println(mCurrentVrService != null ? mCurrentVrService.getComponent() : "(none)"); + pw.print("mCurrentVrModeComponent="); + pw.println(mCurrentVrModeComponent); + } + }; private void enforceCallerPermission(String permission) { @@ -418,6 +447,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC mWasDefaultGranted = true; + grantCoarseLocationAccess(pName, userId); grantOverlayAccess(pName, userId); grantNotificationPolicyAccess(pName); grantNotificationListenerAccess(pName, userId); @@ -447,6 +477,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC String pName = component.getPackageName(); if (mWasDefaultGranted) { + revokeCoarseLocationAccess(userId); revokeOverlayAccess(userId); revokeNotificationPolicyAccess(pName); revokeNotificiationListenerAccess(); @@ -455,6 +486,27 @@ public class VrManagerService extends SystemService implements EnabledComponentC } + private void grantCoarseLocationAccess(String pkg, UserHandle userId) { + PackageManager pm = mContext.getPackageManager(); + boolean prev = (PackageManager.PERMISSION_GRANTED == + pm.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, pkg)); + mPreviousCoarseLocationPackage = null; + if (!prev) { + pm.grantRuntimePermission(pkg, android.Manifest.permission.ACCESS_COARSE_LOCATION, + userId); + mPreviousCoarseLocationPackage = pkg; + } + } + + private void revokeCoarseLocationAccess(UserHandle userId) { + PackageManager pm = mContext.getPackageManager(); + if (mPreviousCoarseLocationPackage != null) { + pm.revokeRuntimePermission(mPreviousCoarseLocationPackage, + android.Manifest.permission.ACCESS_COARSE_LOCATION, userId); + mPreviousCoarseLocationPackage = null; + } + } + private void grantOverlayAccess(String pkg, UserHandle userId) { PackageManager pm = mContext.getPackageManager(); boolean prev = (PackageManager.PERMISSION_GRANTED == @@ -476,7 +528,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC } } - private void grantNotificationPolicyAccess(String pkg) { NotificationManager nm = mContext.getSystemService(NotificationManager.class); boolean prev = nm.isNotificationPolicyAccessGrantedForPackage(pkg); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index fb3c6ec65b8a..fe3210491b90 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -49,6 +49,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; @@ -265,70 +266,134 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { */ private void generateCrop(WallpaperData wallpaper) { boolean success = false; - boolean needCrop = false; - boolean needScale = false; + + Rect cropHint = new Rect(wallpaper.cropHint); if (DEBUG) { Slog.v(TAG, "Generating crop for new wallpaper(s): 0x" + Integer.toHexString(wallpaper.whichPending) - + " to " + wallpaper.cropFile.getName()); + + " to " + wallpaper.cropFile.getName() + + " crop=(" + cropHint.width() + 'x' + cropHint.height() + + ") dim=(" + wallpaper.width + 'x' + wallpaper.height + ')'); } // Analyse the source; needed in multiple cases BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(wallpaper.wallpaperFile.getAbsolutePath(), options); + if (options.outWidth <= 0 || options.outHeight <= 0) { + Slog.e(TAG, "Invalid wallpaper data"); + success = false; + } else { + boolean needCrop = false; + boolean needScale = false; + + // Empty crop means use the full image + if (cropHint.isEmpty()) { + cropHint.left = cropHint.top = 0; + cropHint.right = options.outWidth; + cropHint.bottom = options.outHeight; + } else { + // force the crop rect to lie within the measured bounds + cropHint.offset( + (cropHint.right > options.outWidth ? options.outWidth - cropHint.right : 0), + (cropHint.bottom > options.outHeight ? options.outHeight - cropHint.bottom : 0)); - // We'll need to scale if the crop is sufficiently bigger than the display + // Don't bother cropping if what we're left with is identity + needCrop = (options.outHeight >= cropHint.height() + && options.outWidth >= cropHint.width()); + } - // Legacy case uses an empty crop rect here, so we just preserve the - // source image verbatim - if (!wallpaper.cropHint.isEmpty()) { - // ...clamp the crop rect to the measured bounds... - wallpaper.cropHint.right = Math.min(wallpaper.cropHint.right, options.outWidth); - wallpaper.cropHint.bottom = Math.min(wallpaper.cropHint.bottom, options.outHeight); - // ...and don't bother cropping if what we're left with is identity - needCrop = (options.outHeight >= wallpaper.cropHint.height() - && options.outWidth >= wallpaper.cropHint.width()); - } + // scale if the crop height winds up not matching the recommended metrics + needScale = (wallpaper.height != cropHint.height()); - if (!needCrop && !needScale) { - // Simple case: the nominal crop is at least as big as the source image, - // so we take the whole thing and just copy the image file directly. if (DEBUG) { - Slog.v(TAG, "Null crop of new wallpaper; copying"); - } - success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile); - if (!success) { - wallpaper.cropFile.delete(); - // TODO: fall back to default wallpaper in this case + Slog.v(TAG, "crop: w=" + cropHint.width() + " h=" + cropHint.height()); + Slog.v(TAG, "dims: w=" + wallpaper.width + " h=" + wallpaper.height); + Slog.v(TAG, "meas: w=" + options.outWidth + " h=" + options.outHeight); + Slog.v(TAG, "crop?=" + needCrop + " scale?=" + needScale); } - } else { - // Fancy case: crop and/or scale - FileOutputStream f = null; - BufferedOutputStream bos = null; - try { - BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance( - wallpaper.wallpaperFile.getAbsolutePath(), false); - Bitmap cropped = decoder.decodeRegion(wallpaper.cropHint, null); - decoder.recycle(); - if (cropped == null) { - Slog.e(TAG, "Could not decode new wallpaper"); - } else { - f = new FileOutputStream(wallpaper.cropFile); - bos = new BufferedOutputStream(f, 32*1024); - cropped.compress(Bitmap.CompressFormat.PNG, 90, bos); - bos.flush(); // don't rely on the implicit flush-at-close when noting success - success = true; - } - } catch (IOException e) { + if (!needCrop && !needScale) { + // Simple case: the nominal crop fits what we want, so we take + // the whole thing and just copy the image file directly. if (DEBUG) { - Slog.e(TAG, "I/O error decoding crop: " + e.getMessage()); + Slog.v(TAG, "Null crop of new wallpaper; copying"); + } + success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile); + if (!success) { + wallpaper.cropFile.delete(); + // TODO: fall back to default wallpaper in this case + } + } else { + // Fancy case: crop and scale. First, we decode and scale down if appropriate. + FileOutputStream f = null; + BufferedOutputStream bos = null; + try { + BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance( + wallpaper.wallpaperFile.getAbsolutePath(), false); + + // This actually downsamples only by powers of two, but that's okay; we do + // a proper scaling blit later. This is to minimize transient RAM use. + // We calculate the largest power-of-two under the actual ratio rather than + // just let the decode take care of it because we also want to remap where the + // cropHint rectangle lies in the decoded [super]rect. + final BitmapFactory.Options scaler; + final int actualScale = cropHint.height() / wallpaper.height; + int scale = 1; + while (2*scale < actualScale) { + scale *= 2; + } + if (scale > 1) { + scaler = new BitmapFactory.Options(); + scaler.inSampleSize = scale; + if (DEBUG) { + Slog.v(TAG, "Downsampling cropped rect with scale " + scale); + } + } else { + scaler = null; + } + Bitmap cropped = decoder.decodeRegion(cropHint, scaler); + decoder.recycle(); + + if (cropped == null) { + Slog.e(TAG, "Could not decode new wallpaper"); + } else { + // We've got the extracted crop; now we want to scale it properly to + // the desired rectangle. That's a height-biased operation: make it + // fit the hinted height, and accept whatever width we end up with. + cropHint.offsetTo(0, 0); + cropHint.right /= scale; // adjust by downsampling factor + cropHint.bottom /= scale; + final float heightR = ((float)wallpaper.height) / ((float)cropHint.height()); + if (DEBUG) { + Slog.v(TAG, "scale " + heightR + ", extracting " + cropHint); + } + final int destWidth = (int)(cropHint.width() * heightR); + final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped, + destWidth, wallpaper.height, true); + if (DEBUG) { + Slog.v(TAG, "Final extract:"); + Slog.v(TAG, " dims: w=" + wallpaper.width + + " h=" + wallpaper.height); + Slog.v(TAG, " out: w=" + finalCrop.getWidth() + + " h=" + finalCrop.getHeight()); + } + + f = new FileOutputStream(wallpaper.cropFile); + bos = new BufferedOutputStream(f, 32*1024); + finalCrop.compress(Bitmap.CompressFormat.PNG, 90, bos); + bos.flush(); // don't rely on the implicit flush-at-close when noting success + success = true; + } + } catch (Exception e) { + if (DEBUG) { + Slog.e(TAG, "Error decoding crop", e); + } + } finally { + IoUtils.closeQuietly(bos); + IoUtils.closeQuietly(f); } - } finally { - IoUtils.closeQuietly(bos); - IoUtils.closeQuietly(f); } } diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java index ed935ced0037..a5d68dabbdeb 100644 --- a/services/core/java/com/android/server/webkit/SystemImpl.java +++ b/services/core/java/com/android/server/webkit/SystemImpl.java @@ -34,7 +34,6 @@ import android.provider.Settings.Global; import android.provider.Settings; import android.util.AndroidRuntimeException; import android.util.Log; -import android.webkit.WebViewFactory.MissingWebViewPackageException; import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; @@ -68,6 +67,7 @@ public class SystemImpl implements SystemInterface { @Override public WebViewProviderInfo[] getWebViewPackages() { int numFallbackPackages = 0; + int numAvailableByDefaultPackages = 0; XmlResourceParser parser = null; List<WebViewProviderInfo> webViewProviders = new ArrayList<WebViewProviderInfo>(); try { @@ -83,12 +83,12 @@ public class SystemImpl implements SystemInterface { if (element.equals(TAG_WEBVIEW_PROVIDER)) { String packageName = parser.getAttributeValue(null, TAG_PACKAGE_NAME); if (packageName == null) { - throw new MissingWebViewPackageException( + throw new AndroidRuntimeException( "WebView provider in framework resources missing package name"); } String description = parser.getAttributeValue(null, TAG_DESCRIPTION); if (description == null) { - throw new MissingWebViewPackageException( + throw new AndroidRuntimeException( "WebView provider in framework resources missing description"); } boolean availableByDefault = "true".equals( @@ -100,22 +100,33 @@ public class SystemImpl implements SystemInterface { readSignatures(parser)); if (currentProvider.isFallback) { numFallbackPackages++; + if (!currentProvider.availableByDefault) { + throw new AndroidRuntimeException( + "Each WebView fallback package must be available by default."); + } if (numFallbackPackages > 1) { throw new AndroidRuntimeException( - "There can be at most one webview fallback package."); + "There can be at most one WebView fallback package."); } } + if (currentProvider.availableByDefault) { + numAvailableByDefaultPackages++; + } webViewProviders.add(currentProvider); } else { - Log.e(TAG, "Found an element that is not a webview provider"); + Log.e(TAG, "Found an element that is not a WebView provider"); } } } catch (XmlPullParserException | IOException e) { - throw new MissingWebViewPackageException("Error when parsing WebView meta data " + e); + throw new AndroidRuntimeException("Error when parsing WebView config " + e); } finally { if (parser != null) parser.close(); } + if (numAvailableByDefaultPackages == 0) { + throw new AndroidRuntimeException("There must be at least one WebView package " + + "that is available by default"); + } return webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index cd976e753a0a..df5d0274c20a 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -119,6 +119,8 @@ public class WebViewUpdateServiceImpl { } private void updateFallbackStateOnBoot() { + if (!mSystemInterface.isFallbackLogicEnabled()) return; + WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); updateFallbackState(webviewProviders, true); } @@ -497,8 +499,15 @@ public class WebViewUpdateServiceImpl { mWebViewPackageDirty = false; // If we have changed provider since we started the relro creation we need to // redo the whole process using the new package instead. - PackageInfo newPackage = findPreferredWebViewPackage(); - onWebViewProviderChanged(newPackage); + try { + PackageInfo newPackage = findPreferredWebViewPackage(); + onWebViewProviderChanged(newPackage); + } catch (WebViewFactory.MissingWebViewPackageException e) { + // If we can't find any valid WebView package we are now in a state where + // mAnyWebViewInstalled is false, so loading WebView will be blocked and we + // should simply wait until we receive an intent declaring a new package was + // installed. + } } else { mLock.notifyAll(); } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 446b74bc1933..1475686cca47 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -910,7 +910,7 @@ public class TaskStack implements DimLayer.DimLayerUser, // Calculate the content bounds excluding the area occupied by IME getDisplayContent().getContentRect(displayContentRect); contentBounds.set(displayContentRect); - int imeTop = Math.max(imeWin.getDisplayFrameLw().top, contentBounds.top); + int imeTop = Math.max(imeWin.getFrameLw().top, contentBounds.top); // if IME window is animating, get its actual vertical shown position (but no smaller than // the final target vertical position) diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 9c25f63dfa2a..140f3811ef53 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1288,7 +1288,7 @@ class WindowStateAnimator { } final WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw(); - if (w == winShowWhenLocked) { + if (w == winShowWhenLocked && mPolicy.isKeyguardShowingOrOccluded()) { return; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e7ae2b0fcfd5..6f45508d0d24 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -823,7 +823,7 @@ public final class SystemServer { mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS); mSystemServiceManager.startService(WIFI_SERVICE_CLASS); mSystemServiceManager.startService( - "com.android.server.wifi.WifiScanningService"); + "com.android.server.wifi.scanner.WifiScanningService"); if (!disableRtt) { mSystemServiceManager.startService("com.android.server.wifi.RttService"); diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index e6dd6a4205e7..7ffdb35bc277 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -19,7 +19,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ easymocklib \ guava \ android-support-test \ - mockito-target + mockito-target \ + ShortcutManagerTestUtils LOCAL_JAVA_LIBRARIES := android.test.runner diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java index dae84471d40e..7d28e3964f8e 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java @@ -87,6 +87,12 @@ public class LockSettingsStorageTests extends AndroidTestCase { return new File(mStorageDir, super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath(); } + + @Override + String getChildProfileLockFile(int userId) { + return new File(mStorageDir, + super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath(); + } }; } @@ -235,6 +241,27 @@ public class LockSettingsStorageTests extends AndroidTestCase { assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash); } + public void testLockType_WriteProfileWritesParent() { + mStorage.writePasswordHash("parentpassword".getBytes(), 10); + mStorage.writePatternHash("12345678".getBytes(), 20); + + assertEquals(2, mStorage.getStoredCredentialType(10)); + assertEquals(1, mStorage.getStoredCredentialType(20)); + mStorage.clearCache(); + assertEquals(2, mStorage.getStoredCredentialType(10)); + assertEquals(1, mStorage.getStoredCredentialType(20)); + } + + public void testProfileLock_ReadWriteChildProfileLock() { + assertFalse(mStorage.hasChildProfileLock(20)); + mStorage.writeChildProfileLock(20, "profilepassword".getBytes()); + assertArrayEquals("profilepassword".getBytes(), mStorage.readChildProfileLock(20)); + assertTrue(mStorage.hasChildProfileLock(20)); + mStorage.clearCache(); + assertArrayEquals("profilepassword".getBytes(), mStorage.readChildProfileLock(20)); + assertTrue(mStorage.hasChildProfileLock(20)); + } + public void testPassword_WriteParentWritesProfile() { mStorage.writePasswordHash("profilepassword".getBytes(), 2); mStorage.writePasswordHash("parentpasswordd".getBytes(), 1); diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 5b4803b58c0a..4468857ec024 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -16,23 +16,34 @@ package com.android.server.accounts; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.accounts.Account; import android.accounts.AuthenticatorDescription; +import android.app.AppOpsManager; import android.app.Notification; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.RegisteredServicesCache.ServiceInfo; import android.content.pm.RegisteredServicesCacheListener; +import android.content.pm.UserInfo; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; +import android.os.UserManager; import android.test.AndroidTestCase; -import android.test.IsolatedContext; -import android.test.RenamingDelegatingContext; -import android.test.mock.MockContentResolver; import android.test.mock.MockContext; import android.test.mock.MockPackageManager; +import android.util.Log; +import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -41,20 +52,28 @@ import java.util.Collection; import java.util.Comparator; public class AccountManagerServiceTest extends AndroidTestCase { + private static final String TAG = AccountManagerServiceTest.class.getSimpleName(); + + static final String PREN_DB = "pren.db"; + static final String DE_DB = "de.db"; + static final String CE_DB = "ce.db"; private AccountManagerService mAms; @Override protected void setUp() throws Exception { - final String filenamePrefix = "test."; - MockContentResolver resolver = new MockContentResolver(); - RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( - new MyMockContext(), // The context that most methods are delegated to - getContext(), // The context that file methods are delegated to - filenamePrefix); - Context context = new IsolatedContext(resolver, targetContextWrapper); - setContext(context); + Context realTestContext = getContext(); + Context mockContext = new MyMockContext(realTestContext); + setContext(mockContext); mAms = new MyAccountManagerService(getContext(), - new MyMockPackageManager(), new MockAccountAuthenticatorCache()); + new MyMockPackageManager(), new MockAccountAuthenticatorCache(), realTestContext); + } + + @Override + protected void tearDown() throws Exception { + new File(mAms.getCeDatabaseName(UserHandle.USER_SYSTEM)).delete(); + new File(mAms.getDeDatabaseName(UserHandle.USER_SYSTEM)).delete(); + new File(mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM)).delete(); + super.tearDown(); } public class AccountSorter implements Comparator<Account> { @@ -69,6 +88,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { } public void testCheckAddAccount() throws Exception { + unlockUser(UserHandle.USER_SYSTEM); Account a11 = new Account("account1", "type1"); Account a21 = new Account("account2", "type1"); Account a31 = new Account("account3", "type1"); @@ -109,6 +129,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { } public void testPasswords() throws Exception { + unlockUser(UserHandle.USER_SYSTEM); Account a11 = new Account("account1", "type1"); Account a12 = new Account("account1", "type2"); mAms.addAccountExplicitly(a11, "p11", null); @@ -124,6 +145,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { } public void testUserdata() throws Exception { + unlockUser(UserHandle.USER_SYSTEM); Account a11 = new Account("account1", "type1"); Bundle u11 = new Bundle(); u11.putString("a", "a_a11"); @@ -156,6 +178,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { } public void testAuthtokens() throws Exception { + unlockUser(UserHandle.USER_SYSTEM); Account a11 = new Account("account1", "type1"); Account a12 = new Account("account1", "type2"); mAms.addAccountExplicitly(a11, "p11", null); @@ -188,15 +211,21 @@ public class AccountManagerServiceTest extends AndroidTestCase { assertNull(mAms.peekAuthToken(a12, "att2")); } + private void unlockUser(int userId) { + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + mAms.onUserUnlocked(intent); + } + static public class MockAccountAuthenticatorCache implements IAccountAuthenticatorCache { private ArrayList<ServiceInfo<AuthenticatorDescription>> mServices; public MockAccountAuthenticatorCache() { - mServices = new ArrayList<ServiceInfo<AuthenticatorDescription>>(); + mServices = new ArrayList<>(); AuthenticatorDescription d1 = new AuthenticatorDescription("type1", "p1", 0, 0, 0, 0); AuthenticatorDescription d2 = new AuthenticatorDescription("type2", "p2", 0, 0, 0, 0); - mServices.add(new ServiceInfo<AuthenticatorDescription>(d1, null, null)); - mServices.add(new ServiceInfo<AuthenticatorDescription>(d2, null, null)); + mServices.add(new ServiceInfo<>(d1, null, null)); + mServices.add(new ServiceInfo<>(d2, null, null)); } @Override @@ -232,10 +261,68 @@ public class AccountManagerServiceTest extends AndroidTestCase { } static public class MyMockContext extends MockContext { + private Context mTestContext; + private AppOpsManager mAppOpsManager; + private UserManager mUserManager; + + public MyMockContext(Context testContext) { + this.mTestContext = testContext; + this.mAppOpsManager = mock(AppOpsManager.class); + this.mUserManager = mock(UserManager.class); + final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0); + when(mUserManager.getUserInfo(eq(ui.id))).thenReturn(ui); + } + @Override public int checkCallingOrSelfPermission(final String permission) { return PackageManager.PERMISSION_GRANTED; } + + @Override + public Object getSystemService(String name) { + if (Context.APP_OPS_SERVICE.equals(name)) { + return mAppOpsManager; + } else if( Context.USER_SERVICE.equals(name)) { + return mUserManager; + } + return null; + } + + @Override + public String getSystemServiceName(Class<?> serviceClass) { + if (AppOpsManager.class.equals(serviceClass)) { + return Context.APP_OPS_SERVICE; + } + return null; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return null; + } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + return null; + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String file, int mode, + SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { + Log.i(TAG, "openOrCreateDatabase " + file + " mode " + mode); + return mTestContext.openOrCreateDatabase(file, mode, factory,errorHandler); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user) { + Log.i(TAG, "sendBroadcastAsUser " + intent + " " + user); + } + + @Override + public String getOpPackageName() { + return null; + } } static public class MyMockPackageManager extends MockPackageManager { @@ -246,9 +333,11 @@ public class AccountManagerServiceTest extends AndroidTestCase { } static public class MyAccountManagerService extends AccountManagerService { + private Context mRealTestContext; public MyAccountManagerService(Context context, PackageManager packageManager, - IAccountAuthenticatorCache authenticatorCache) { + IAccountAuthenticatorCache authenticatorCache, Context realTestContext) { super(context, packageManager, authenticatorCache); + this.mRealTestContext = realTestContext; } @Override @@ -258,5 +347,20 @@ public class AccountManagerServiceTest extends AndroidTestCase { @Override protected void cancelNotification(final int id, UserHandle user) { } + + @Override + protected String getCeDatabaseName(int userId) { + return new File(mRealTestContext.getCacheDir(), CE_DB).getPath(); + } + + @Override + protected String getDeDatabaseName(int userId) { + return new File(mRealTestContext.getCacheDir(), DE_DB).getPath(); + } + + @Override + String getPreNDatabaseName(int userId) { + return new File(mRealTestContext.getCacheDir(), PREN_DB).getPath(); + } } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index b08bd724a985..2fd11da3adf3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.*; import android.annotation.NonNull; import android.annotation.Nullable; @@ -608,14 +609,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { addPackage(packageName, uid, version, packageName); } - private <T> List<T> list(T... array) { - return Arrays.asList(array); - } - - private <T> Set<T> set(Set<T> in) { - return new ArraySet<T>(in); - } - private Signature[] genSignatures(String... signatures) { final Signature[] sigs = new Signature[signatures.length]; for (int i = 0; i < signatures.length; i++){ @@ -799,33 +792,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { runTestOnUiThread(() -> {}); } - public static Bundle makeBundle(Object... keysAndValues) { - Preconditions.checkState((keysAndValues.length % 2) == 0); - - if (keysAndValues.length == 0) { - return null; - } - final Bundle ret = new Bundle(); - - for (int i = keysAndValues.length - 2; i >= 0; i -= 2) { - final String key = keysAndValues[i].toString(); - final Object value = keysAndValues[i + 1]; - - if (value == null) { - ret.putString(key, null); - } else if (value instanceof Integer) { - ret.putInt(key, (Integer) value); - } else if (value instanceof String) { - ret.putString(key, (String) value); - } else if (value instanceof Bundle) { - ret.putBundle(key, (Bundle) value); - } else { - fail("Type not supported yet: " + value.getClass().getName()); - } - } - return ret; - } - /** * Make a shortcut with an ID. */ @@ -923,20 +889,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return new ComponentName(mClientContext, clazz); } - private <T> Set<T> makeSet(T... values) { - final HashSet<T> ret = new HashSet<>(); - for (T s : values) { - ret.add(s); - } - return ret; - } - - private static void resetAll(Collection<?> mocks) { - for (Object o : mocks) { - reset(o); - } - } - @NonNull private ShortcutInfo findById(List<ShortcutInfo> list, String id) { for (ShortcutInfo s : list) { @@ -957,59 +909,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked()); } - @NonNull - private List<ShortcutInfo> assertShortcutIds(@NonNull List<ShortcutInfo> actualShortcuts, - String... expectedIds) { - final HashSet<String> expected = new HashSet<>(list(expectedIds)); - final HashSet<String> actual = new HashSet<>(); - for (ShortcutInfo s : actualShortcuts) { - actual.add(s.getId()); - } - - // Compare the sets. - assertEquals(expected, actual); - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllHaveIntents( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertNotNull("ID " + s.getId(), s.getIntent()); - } - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllNotHaveIntents( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertNull("ID " + s.getId(), s.getIntent()); - } - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllHaveTitle( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertNotNull("ID " + s.getId(), s.getTitle()); - } - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllNotHaveTitle( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertNull("ID " + s.getId(), s.getTitle()); - } - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllNotHaveIcon( - @NonNull List<ShortcutInfo> actualShortcuts) { + public static List<ShortcutInfo> assertAllNotHaveIcon( + List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { assertNull("ID " + s.getId(), s.getIcon()); } @@ -1017,35 +918,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { } @NonNull - private List<ShortcutInfo> assertAllHaveIconResId( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource()); - assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile()); - } - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllHaveIconFile( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource()); - assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile()); - } - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllHaveIcon( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource()); - } - return actualShortcuts; - } - - @NonNull private List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts, int shortcutFlags) { for (ShortcutInfo s : actualShortcuts) { @@ -1055,87 +927,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return actualShortcuts; } - @NonNull - private List<ShortcutInfo> assertAllKeyFieldsOnly( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly()); - } - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllNotKeyFieldsOnly( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly()); - } - return actualShortcuts; - } - - @NonNull - private List<ShortcutInfo> assertAllDynamic(@NonNull List<ShortcutInfo> actualShortcuts) { - return assertAllHaveFlags(actualShortcuts, ShortcutInfo.FLAG_DYNAMIC); - } - - @NonNull - private List<ShortcutInfo> assertAllPinned(@NonNull List<ShortcutInfo> actualShortcuts) { - return assertAllHaveFlags(actualShortcuts, ShortcutInfo.FLAG_PINNED); - } - - @NonNull - private List<ShortcutInfo> assertAllDynamicOrPinned( - @NonNull List<ShortcutInfo> actualShortcuts) { - for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned()); - } - return actualShortcuts; - } - - private void assertDynamicOnly(ShortcutInfo si) { - assertTrue(si.isDynamic()); - assertFalse(si.isPinned()); - } - - private void assertPinnedOnly(ShortcutInfo si) { - assertFalse(si.isDynamic()); - assertTrue(si.isPinned()); - } - - private void assertDynamicAndPinned(ShortcutInfo si) { - assertTrue(si.isDynamic()); - assertTrue(si.isPinned()); - } - - private void assertBitmapSize(int expectedWidth, int expectedHeight, @NonNull Bitmap bitmap) { - assertEquals("width", expectedWidth, bitmap.getWidth()); - assertEquals("height", expectedHeight, bitmap.getHeight()); - } - - private <T> void assertAllUnique(Collection<T> list) { - final Set<Object> set = new HashSet<>(); - for (T item : list) { - if (set.contains(item)) { - fail("Duplicate item found: " + item + " (in the list: " + list + ")"); - } - set.add(item); - } - } - - @NonNull - private Bitmap pfdToBitmap(@NonNull ParcelFileDescriptor pfd) { - Preconditions.checkNotNull(pfd); - try { - return BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); - } finally { - IoUtils.closeQuietly(pfd); - } - } - - private void assertBundleEmpty(BaseBundle b) { - assertTrue(b == null || b.size() == 0); - } - private ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) { return mService.getPackageShortcutForTest(packageName, shortcutId, userId); } @@ -1718,7 +1509,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertFalse(mManager.setDynamicShortcuts(list(si2))); } - public void testIcons() { + public void testIcons() throws IOException { final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64); final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512); @@ -2242,7 +2033,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { getCallingUser())), "s1", "s3"); - TestUtils.assertExpectException( + assertExpectException( IllegalArgumentException.class, "package name must also be set", () -> { mLauncherApps.getShortcuts(buildQuery( /* time =*/ 0, /* package= */ null, list("id"), @@ -3341,20 +3132,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(0, shortcuts.getValue().size()); } - private void assertCallbackNotReceived(LauncherApps.Callback mock) { - verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(), - any(UserHandle.class)); - } - - private void assertCallbackReceived(LauncherApps.Callback mock, - UserHandle user, String packageName, String... ids) { - ArgumentCaptor<List> shortcutsCaptor = ArgumentCaptor.forClass(List.class); - - verify(mock, times(1)).onShortcutsChanged(eq(packageName), shortcutsCaptor.capture(), - eq(user)); - assertShortcutIds(shortcutsCaptor.getValue(), ids); - } - public void testLauncherCallback_crossProfile() throws Throwable { prepareCrossProfileDataSet(); @@ -3724,18 +3501,18 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Check the registered packages. dumpsysOnLogcat(); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_1), + set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_1", "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3756,18 +3533,18 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.cleanUpPackageLocked("abc", USER_0, USER_0); // No changes. - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_1), + set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_1", "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3787,18 +3564,18 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_0, CALLING_PACKAGE_1); mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_1), + set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3818,17 +3595,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_10, LAUNCHER_1); mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + set(PackageWithUser.of(USER_10, LAUNCHER_2)), + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3846,17 +3623,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_10, CALLING_PACKAGE_2); mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_10, LAUNCHER_2)), - set(user10.getAllLaunchers().keySet())); + set(PackageWithUser.of(USER_10, LAUNCHER_2)), + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3874,17 +3651,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_10, LAUNCHER_2); mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(CALLING_PACKAGE_1), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_1), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); assertEquals( - makeSet(), - set(user10.getAllLaunchers().keySet())); + set(), + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3902,16 +3679,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { uninstallPackage(USER_10, CALLING_PACKAGE_1); mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10); - assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getAllPackages().keySet())); - assertEquals(makeSet(), - set(user10.getAllPackages().keySet())); + assertEquals(set(CALLING_PACKAGE_2), + hashSet(user0.getAllPackages().keySet())); + assertEquals(set(), + hashSet(user10.getAllPackages().keySet())); assertEquals( - makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), + set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - set(user0.getAllLaunchers().keySet())); - assertEquals(makeSet(), - set(user10.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchers().keySet())); + assertEquals(set(), + hashSet(user10.getAllLaunchers().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -5052,7 +4829,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutIds( mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0) /* empty */); - TestUtils.assertExpectException( + assertExpectException( SecurityException.class, "", () -> { mLauncherApps.getShortcuts( buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10); @@ -5131,7 +4908,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutIds( mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0), "s1", "s4"); - TestUtils.assertExpectException( + assertExpectException( SecurityException.class, "unrelated profile", () -> { mLauncherApps.getShortcuts( buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10); @@ -5147,12 +4924,12 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutIds( mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_10) /* empty */); - TestUtils.assertExpectException( + assertExpectException( SecurityException.class, "unrelated profile", () -> { mLauncherApps.getShortcuts( buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0); }); - TestUtils.assertExpectException( + assertExpectException( SecurityException.class, "unrelated profile", () -> { mLauncherApps.getShortcuts( buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0); @@ -5163,16 +4940,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // ShortcutInfo tests public void testShortcutInfoMissingMandatoryFields() { - TestUtils.assertExpectException( + assertExpectException( IllegalArgumentException.class, "ID must be provided", () -> new ShortcutInfo.Builder(getTestContext()).build()); - TestUtils.assertExpectException( + assertExpectException( IllegalArgumentException.class, "title must be provided", () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build() .enforceMandatoryFields()); - TestUtils.assertExpectException( + assertExpectException( NullPointerException.class, "Intent must be provided", () -> new ShortcutInfo.Builder(getTestContext()).setId("id").setTitle("x").build() @@ -5493,7 +5270,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { dumpsysOnLogcat("test1", /* force= */ true); } - public void testDumpsys_withIcons() { + public void testDumpsys_withIcons() throws IOException { testIcons(); // Dump after having some icons. dumpsysOnLogcat("test1", /* force= */ true); diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java new file mode 100644 index 000000000000..26b87c5d9283 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.webkit; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.webkit.WebViewProviderInfo; + +import java.util.HashMap; + +public class TestSystemImpl implements SystemInterface { + private String mUserProvider = ""; + private final WebViewProviderInfo[] mPackageConfigs; + HashMap<String, PackageInfo> mPackages = new HashMap(); + private boolean mFallbackLogicEnabled; + private final int mNumRelros; + private final boolean mIsDebuggable; + + public TestSystemImpl(WebViewProviderInfo[] packageConfigs, boolean fallbackLogicEnabled, + int numRelros, boolean isDebuggable) { + mPackageConfigs = packageConfigs; + mFallbackLogicEnabled = fallbackLogicEnabled; + mNumRelros = numRelros; + mIsDebuggable = isDebuggable; + } + + @Override + public WebViewProviderInfo[] getWebViewPackages() { + return mPackageConfigs; + } + + @Override + public int onWebViewProviderChanged(PackageInfo packageInfo) { + return mNumRelros; + } + + @Override + public String getUserChosenWebViewProvider(Context context) { return mUserProvider; } + + @Override + public void updateUserSetting(Context context, String newProviderName) { + mUserProvider = newProviderName; + } + + @Override + public void killPackageDependents(String packageName) {} + + @Override + public boolean isFallbackLogicEnabled() { + return mFallbackLogicEnabled; + } + + @Override + public void enableFallbackLogic(boolean enable) { + mFallbackLogicEnabled = enable; + } + + @Override + public void uninstallAndDisablePackageForAllUsers(Context context, String packageName) { + enablePackageForAllUsers(context, packageName, false); + } + + @Override + public void enablePackageForAllUsers(Context context, String packageName, boolean enable) { + enablePackageForUser(packageName, enable, 0); + } + + @Override + public void enablePackageForUser(String packageName, boolean enable, int userId) { + PackageInfo packageInfo = mPackages.get(packageName); + if (packageInfo == null) { + throw new IllegalArgumentException("There is no package called " + packageName); + } + packageInfo.applicationInfo.enabled = enable; + setPackageInfo(packageInfo); + } + + @Override + public boolean systemIsDebuggable() { return mIsDebuggable; } + + @Override + public PackageInfo getPackageInfoForProvider(WebViewProviderInfo info) throws + NameNotFoundException { + PackageInfo ret = mPackages.get(info.packageName); + if (ret == null) throw new NameNotFoundException(info.packageName); + return ret; + } + + public void setPackageInfo(PackageInfo pi) { + mPackages.put(pi.packageName, pi); + } + + @Override + public int getFactoryPackageVersion(String packageName) { + return 0; + } +} diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java new file mode 100644 index 000000000000..c00520dc3552 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -0,0 +1,613 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.webkit; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.Signature; +import android.os.Bundle; +import android.util.Base64; +import android.test.AndroidTestCase; + +import android.webkit.WebViewFactory; +import android.webkit.WebViewProviderInfo; +import android.webkit.WebViewProviderResponse; + +import java.util.concurrent.CountDownLatch; + +import org.hamcrest.Description; + +import org.mockito.Mockito; +import org.mockito.Matchers; +import org.mockito.ArgumentMatcher; + + +/** + * Tests for WebViewUpdateService + */ +public class WebViewUpdateServiceTest extends AndroidTestCase { + private final static String TAG = WebViewUpdateServiceTest.class.getSimpleName(); + + private WebViewUpdateServiceImpl mWebViewUpdateServiceImpl; + private TestSystemImpl mTestSystemImpl; + + private static final String WEBVIEW_LIBRARY_FLAG = "com.android.webview.WebViewLibrary"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + /** + * Creates a new instance. + */ + public WebViewUpdateServiceTest() { + } + + private void setupWithPackages(WebViewProviderInfo[] packages) { + setupWithPackages(packages, true); + } + + private void setupWithPackages(WebViewProviderInfo[] packages, + boolean fallbackLogicEnabled) { + setupWithPackages(packages, fallbackLogicEnabled, 1); + } + + private void setupWithPackages(WebViewProviderInfo[] packages, + boolean fallbackLogicEnabled, int numRelros) { + setupWithPackages(packages, fallbackLogicEnabled, numRelros, + true /* isDebuggable == true -> don't check package signatures */); + } + + private void setupWithPackages(WebViewProviderInfo[] packages, + boolean fallbackLogicEnabled, int numRelros, boolean isDebuggable) { + TestSystemImpl testing = new TestSystemImpl(packages, fallbackLogicEnabled, numRelros, + isDebuggable); + mTestSystemImpl = Mockito.spy(testing); + mWebViewUpdateServiceImpl = + new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl); + } + + private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) { + for(WebViewProviderInfo wpi : providers) { + mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true /* enabled */, + true /* valid */)); + } + } + + private void checkCertainPackageUsedAfterWebViewPreparation(String expectedProviderName, + WebViewProviderInfo[] webviewPackages) { + checkCertainPackageUsedAfterWebViewPreparation(expectedProviderName, webviewPackages, 1); + } + + private void checkCertainPackageUsedAfterWebViewPreparation(String expectedProviderName, + WebViewProviderInfo[] webviewPackages, int numRelros) { + setupWithPackages(webviewPackages, true, numRelros); + // Add (enabled and valid) package infos for each provider + setEnabledAndValidPackageInfos(webviewPackages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(expectedProviderName))); + + for (int n = 0; n < numRelros; n++) { + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + } + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(expectedProviderName, response.packageInfo.packageName); + } + + // For matching the package name of a PackageInfo + private class IsPackageInfoWithName extends ArgumentMatcher<PackageInfo> { + private final String mPackageName; + + IsPackageInfoWithName(String name) { + mPackageName = name; + } + + @Override + public boolean matches(Object p) { + return ((PackageInfo) p).packageName.equals(mPackageName); + } + + // Provide a more useful description in case of mismatch + @Override + public void describeTo (Description description) { + description.appendText(String.format("PackageInfo with name '%s'", mPackageName)); + } + } + + private static PackageInfo createPackageInfo( + String packageName, boolean enabled, boolean valid) { + PackageInfo p = new PackageInfo(); + p.packageName = packageName; + p.applicationInfo = new ApplicationInfo(); + p.applicationInfo.enabled = enabled; + p.applicationInfo.metaData = new Bundle(); + if (valid) { + // no flag means invalid + p.applicationInfo.metaData.putString(WEBVIEW_LIBRARY_FLAG, "blah"); + } + return p; + } + + private static PackageInfo createPackageInfo( + String packageName, boolean enabled, boolean valid, Signature[] signatures) { + PackageInfo p = createPackageInfo(packageName, enabled, valid); + p.signatures = signatures; + return p; + } + + + // **************** + // Tests + // **************** + + + public void testWithSinglePackage() { + String testPackageName = "test.package.name"; + checkCertainPackageUsedAfterWebViewPreparation(testPackageName, + new WebViewProviderInfo[] { + new WebViewProviderInfo(testPackageName, "", + true /*default available*/, false /* fallback */, null)}); + } + + public void testDefaultPackageUsedOverNonDefault() { + String defaultPackage = "defaultPackage"; + String nonDefaultPackage = "nonDefaultPackage"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo(nonDefaultPackage, "", false, false, null), + new WebViewProviderInfo(defaultPackage, "", true, false, null)}; + checkCertainPackageUsedAfterWebViewPreparation(defaultPackage, packages); + } + + public void testSeveralRelros() { + String singlePackage = "singlePackage"; + checkCertainPackageUsedAfterWebViewPreparation( + singlePackage, + new WebViewProviderInfo[] { + new WebViewProviderInfo(singlePackage, "", true /*def av*/, false, null)}, + 2); + } + + // Ensure that package with valid signatures is chosen rather than package with invalid + // signatures. + public void testWithSignatures() { + String validPackage = "valid package"; + String invalidPackage = "invalid package"; + + Signature validSignature = new Signature("11"); + Signature invalidExpectedSignature = new Signature("22"); + Signature invalidPackageSignature = new Signature("33"); + + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo(invalidPackage, "", true, false, new String[]{ + Base64.encodeToString( + invalidExpectedSignature.toByteArray(), Base64.DEFAULT)}), + new WebViewProviderInfo(validPackage, "", true, false, new String[]{ + Base64.encodeToString( + validSignature.toByteArray(), Base64.DEFAULT)}) + }; + setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */, + false /* isDebuggable */); + mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */, + true /* valid */, new Signature[]{invalidPackageSignature})); + mTestSystemImpl.setPackageInfo(createPackageInfo(validPackage, true /* enabled */, + true /* valid */, new Signature[]{validSignature})); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(validPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(validPackage, response.packageInfo.packageName); + + WebViewProviderInfo[] validPackages = mWebViewUpdateServiceImpl.getValidWebViewPackages(); + assertEquals(1, validPackages.length); + assertEquals(validPackage, validPackages[0].packageName); + } + + public void testFailWaitingForRelro() { + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo("packagename", "", true, true, null)}; + setupWithPackages(packages); + setEnabledAndValidPackageInfos(packages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(packages[0].packageName))); + + // Never call notifyRelroCreation() + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO, response.status); + } + + public void testFailListingEmptyWebviewPackages() { + WebViewProviderInfo[] packages = new WebViewProviderInfo[0]; + setupWithPackages(packages); + setEnabledAndValidPackageInfos(packages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( + Matchers.anyObject()); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); + } + + public void testFailListingInvalidWebviewPackage() { + WebViewProviderInfo wpi = new WebViewProviderInfo("", "", true, true, null); + WebViewProviderInfo[] packages = new WebViewProviderInfo[] {wpi}; + setupWithPackages(packages); + mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true, false)); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); + } + + // Test that switching provider using changeProviderAndSetting works. + public void testSwitchingProvider() { + String firstPackage = "first"; + String secondPackage = "second"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo(firstPackage, "", true, false, null), + new WebViewProviderInfo(secondPackage, "", true, false, null)}; + checkSwitchingProvider(packages, firstPackage, secondPackage); + } + + public void testSwitchingProviderToNonDefault() { + String defaultPackage = "defaultPackage"; + String nonDefaultPackage = "nonDefaultPackage"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo(defaultPackage, "", true, false, null), + new WebViewProviderInfo(nonDefaultPackage, "", false, false, null)}; + checkSwitchingProvider(packages, defaultPackage, nonDefaultPackage); + } + + private void checkSwitchingProvider(WebViewProviderInfo[] packages, String initialPackage, + String finalPackage) { + checkCertainPackageUsedAfterWebViewPreparation(initialPackage, packages); + + mWebViewUpdateServiceImpl.changeProviderAndSetting(finalPackage); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(finalPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + WebViewProviderResponse secondResponse = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, secondResponse.status); + assertEquals(finalPackage, secondResponse.packageInfo.packageName); + + Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(initialPackage)); + } + + // Change provider during relro creation by using changeProviderAndSetting + public void testSwitchingProviderDuringRelroCreation() { + checkChangingProviderDuringRelroCreation(true); + } + + // Change provider during relro creation by enabling a provider + public void testChangingProviderThroughEnablingDuringRelroCreation() { + checkChangingProviderDuringRelroCreation(false); + } + + private void checkChangingProviderDuringRelroCreation(boolean settingsChange) { + String firstPackage = "first"; + String secondPackage = "second"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo(firstPackage, "", true, false, null), + new WebViewProviderInfo(secondPackage, "", true, false, null)}; + setupWithPackages(packages); + if (settingsChange) { + // Have all packages be enabled, so that we can change provider however we want to + setEnabledAndValidPackageInfos(packages); + } else { + // Have all packages be disabled so that we can change one to enabled later + for(WebViewProviderInfo wpi : packages) { + mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, + false /* enabled */, true /* valid */)); + } + } + + CountDownLatch countdown = new CountDownLatch(1); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(firstPackage))); + + assertEquals(firstPackage, mWebViewUpdateServiceImpl.getCurrentWebViewPackageName()); + + new Thread(new Runnable() { + @Override + public void run() { + WebViewProviderResponse threadResponse = + mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, threadResponse.status); + assertEquals(secondPackage, threadResponse.packageInfo.packageName); + // Verify that we killed the first package + Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage)); + countdown.countDown(); + } + }).start(); + try { + Thread.sleep(1000); // Let the new thread run / be blocked + } catch (InterruptedException e) { + } + + if (settingsChange) { + mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); + } else { + // Switch provider by enabling the second one + mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, + true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged( + secondPackage, WebViewUpdateService.PACKAGE_CHANGED); + } + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + // first package done, should start on second + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(secondPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + // second package done, the other thread should now be unblocked + try { + countdown.await(); + } catch (InterruptedException e) { + } + } + + public void testRunFallbackLogicIfEnabled() { + checkFallbackLogicBeingRun(true); + } + + public void testDontRunFallbackLogicIfDisabled() { + checkFallbackLogicBeingRun(false); + } + + private void checkFallbackLogicBeingRun(boolean fallbackLogicEnabled) { + String primaryPackage = "primary"; + String fallbackPackage = "fallback"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo( + primaryPackage, "", true /* default available */, false /* fallback */, null), + new WebViewProviderInfo( + fallbackPackage, "", true /* default available */, true /* fallback */, null)}; + setupWithPackages(packages, fallbackLogicEnabled); + setEnabledAndValidPackageInfos(packages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + // Verify that we disable the fallback package if fallback logic enabled, and don't disable + // the fallback package if that logic is disabled + if (fallbackLogicEnabled) { + Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers( + Matchers.anyObject(), Mockito.eq(fallbackPackage)); + } else { + Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers( + Matchers.anyObject(), Matchers.anyObject()); + } + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(primaryPackage))); + + // Enable fallback package + mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */, + true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged( + fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED); + + if (fallbackLogicEnabled) { + // Check that we have now disabled the fallback package twice + Mockito.verify(mTestSystemImpl, Mockito.times(2)).uninstallAndDisablePackageForAllUsers( + Matchers.anyObject(), Mockito.eq(fallbackPackage)); + } else { + // Check that we still haven't disabled any package + Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers( + Matchers.anyObject(), Matchers.anyObject()); + } + } + + /** + * Scenario for installing primary package when fallback enabled. + * 1. Start with only fallback installed + * 2. Install non-fallback + * 3. Fallback should be disabled + */ + public void testInstallingNonFallbackPackage() { + String primaryPackage = "primary"; + String fallbackPackage = "fallback"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo( + primaryPackage, "", true /* default available */, false /* fallback */, null), + new WebViewProviderInfo( + fallbackPackage, "", true /* default available */, true /* fallback */, null)}; + setupWithPackages(packages, true /* isFallbackLogicEnabled */); + mTestSystemImpl.setPackageInfo( + createPackageInfo(fallbackPackage, true /* enabled */ , true /* valid */)); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers( + Matchers.anyObject(), Matchers.anyObject()); + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(fallbackPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(fallbackPackage, response.packageInfo.packageName); + + // Install primary package + mTestSystemImpl.setPackageInfo( + createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_ADDED); + + // Verify fallback disabled and primary package used as provider + Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers( + Matchers.anyObject(), Mockito.eq(fallbackPackage)); + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(primaryPackage))); + + // Finish the webview preparation and ensure primary package used and fallback killed + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(primaryPackage, response.packageInfo.packageName); + Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(fallbackPackage)); + } + + public void testFallbackChangesEnabledState() { + String primaryPackage = "primary"; + String fallbackPackage = "fallback"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo( + primaryPackage, "", true /* default available */, false /* fallback */, null), + new WebViewProviderInfo( + fallbackPackage, "", true /* default available */, true /* fallback */, null)}; + setupWithPackages(packages, true /* fallbackLogicEnabled */); + setEnabledAndValidPackageInfos(packages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + // Verify fallback disabled at boot when primary package enabled + Mockito.verify(mTestSystemImpl).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, + Matchers.anyInt()); + + mTestSystemImpl.setPackageInfo( + createPackageInfo(primaryPackage, false /* enabled */, true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_CHANGED); + + // Verify fallback becomes enabled when primary package becomes disabled + Mockito.verify(mTestSystemImpl).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */, + Matchers.anyInt()); + + mTestSystemImpl.setPackageInfo( + createPackageInfo(primaryPackage, true /* enabled */, true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_CHANGED); + + // Verify fallback is disabled a second time when primary package becomes enabled + Mockito.verify(mTestSystemImpl, Mockito.times(2)).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, + Matchers.anyInt()); + } + + public void testAddUserWhenFallbackLogicEnabled() { + checkAddingNewUser(true); + } + + public void testAddUserWhenFallbackLogicDisabled() { + checkAddingNewUser(false); + } + + public void checkAddingNewUser(boolean fallbackLogicEnabled) { + String primaryPackage = "primary"; + String fallbackPackage = "fallback"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo( + primaryPackage, "", true /* default available */, false /* fallback */, null), + new WebViewProviderInfo( + fallbackPackage, "", true /* default available */, true /* fallback */, null)}; + setupWithPackages(packages, fallbackLogicEnabled); + setEnabledAndValidPackageInfos(packages); + int newUser = 100; + mWebViewUpdateServiceImpl.handleNewUser(newUser); + if (fallbackLogicEnabled) { + // Verify fallback package becomes disabled for new user + Mockito.verify(mTestSystemImpl).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, + Mockito.eq(newUser)); + } else { + // Verify that we don't disable fallback for new user + Mockito.verify(mTestSystemImpl, Mockito.never()).enablePackageForUser( + Mockito.anyObject(), Matchers.anyBoolean() /* enable */, + Matchers.anyInt() /* user */); + } + } + + /** + * Timing dependent test where we verify that the list of valid webview packages becoming empty + * at a certain point doesn't crash us or break our state. + */ + public void testNotifyRelroDoesntCrashIfNoPackages() { + String firstPackage = "first"; + String secondPackage = "second"; + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo(firstPackage, "", true /* default available */, + false /* fallback */, null), + new WebViewProviderInfo(secondPackage, "", true /* default available */, + false /* fallback */, null)}; + setupWithPackages(packages); + // Add (enabled and valid) package infos for each provider + setEnabledAndValidPackageInfos(packages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(firstPackage))); + + mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); + + // Make packages invalid to cause exception to be thrown + mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, + false /* valid */)); + mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, + false /* valid */)); + + // This shouldn't throw an exception! + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); + + // Now make a package valid again and verify that we can switch back to that + mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, + true /* valid */)); + + mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, + WebViewUpdateService.PACKAGE_ADDED); + + // Second time we call onWebViewProviderChanged for firstPackage + Mockito.verify(mTestSystemImpl, Mockito.times(2)).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(firstPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(firstPackage, response.packageInfo.packageName); + } + + // TODO (gsennton) add more tests for ensuring killPackageDependents is called / not called +} diff --git a/services/tests/shortcutmanagerutils/Android.mk b/services/tests/shortcutmanagerutils/Android.mk new file mode 100644 index 000000000000..701e05857142 --- /dev/null +++ b/services/tests/shortcutmanagerutils/Android.mk @@ -0,0 +1,31 @@ +# Copyright (C) 2016 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + mockito-target + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE := ShortcutManagerTestUtils + +LOCAL_SDK_VERSION := current + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java new file mode 100644 index 000000000000..d09b62c2dec6 --- /dev/null +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm.shortcutmanagertest; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyList; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.Instrumentation; +import android.content.Context; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.BaseBundle; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.test.MoreAsserts; +import android.util.Log; + +import junit.framework.Assert; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.mockito.Mockito; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Predicate; + +public class ShortcutManagerTestUtils { + private static final String TAG = "ShortcutManagerUtils"; + + private static final boolean ENABLE_DUMPSYS = true; // DO NOT SUBMIT WITH true + + private static final int STANDARD_TIMEOUT_SEC = 5; + + private ShortcutManagerTestUtils() { + } + + private static List<String> readAll(ParcelFileDescriptor pfd) { + try { + try { + final ArrayList<String> ret = new ArrayList<>(); + try (BufferedReader r = new BufferedReader( + new FileReader(pfd.getFileDescriptor()))) { + String line; + while ((line = r.readLine()) != null) { + ret.add(line); + } + r.readLine(); + } + return ret; + } finally { + pfd.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String concatResult(List<String> result) { + final StringBuilder sb = new StringBuilder(); + for (String s : result) { + sb.append(s); + sb.append("\n"); + } + return sb.toString(); + } + + private static List<String> runCommand(Instrumentation instrumentation, String command) { + return runCommand(instrumentation, command, null); + } + private static List<String> runCommand(Instrumentation instrumentation, String command, + Predicate<List<String>> resultAsserter) { + Log.d(TAG, "Running command: " + command); + final List<String> result; + try { + result = readAll( + instrumentation.getUiAutomation().executeShellCommand(command)); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (resultAsserter != null && !resultAsserter.test(result)) { + fail("Command '" + command + "' failed, output was:\n" + concatResult(result)); + } + return result; + } + + private static void runCommandForNoOutput(Instrumentation instrumentation, String command) { + runCommand(instrumentation, command, result -> result.size() == 0); + } + + private static List<String> runShortcutCommand(Instrumentation instrumentation, String command, + Predicate<List<String>> resultAsserter) { + return runCommand(instrumentation, "cmd shortcut " + command, resultAsserter); + } + + public static List<String> runShortcutCommandForSuccess(Instrumentation instrumentation, + String command) { + return runShortcutCommand(instrumentation, command, result -> result.contains("Success")); + } + + public static String getDefaultLauncher(Instrumentation instrumentation) { + final String PREFIX = "Launcher: ComponentInfo{"; + final String POSTFIX = "}"; + final List<String> result = runShortcutCommandForSuccess( + instrumentation, "get-default-launcher"); + for (String s : result) { + if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) { + return s.substring(PREFIX.length(), s.length() - POSTFIX.length()); + } + } + fail("Default launcher not found"); + return null; + } + + public static void setDefaultLauncher(Instrumentation instrumentation, String component) { + runCommandForNoOutput(instrumentation, "cmd package set-home-activity " + component); + } + + public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) { + setDefaultLauncher(instrumentation, packageContext.getPackageName() + + "/android.content.pm.cts.shortcutmanager.packages.Launcher"); + } + + public static void overrideConfig(Instrumentation instrumentation, String config) { + runShortcutCommandForSuccess(instrumentation, "override-config " + config); + } + + public static void resetConfig(Instrumentation instrumentation) { + runShortcutCommandForSuccess(instrumentation, "reset-config"); + } + + public static void resetThrottling(Instrumentation instrumentation) { + runShortcutCommandForSuccess(instrumentation, "reset-throttling"); + } + + public static void resetAllThrottling(Instrumentation instrumentation) { + runShortcutCommandForSuccess(instrumentation, "reset-all-throttling"); + } + + public static void clearShortcuts(Instrumentation instrumentation, int userId, + String packageName) { + runShortcutCommandForSuccess(instrumentation, "clear-shortcuts " + + " --user " + userId + " " + packageName); + } + + public static void dumpsysShortcut(Instrumentation instrumentation) { + if (!ENABLE_DUMPSYS) { + return; + } + for (String s : runCommand(instrumentation, "dumpsys shortcut")) { + Log.e(TAG, s); + } + } + + public static Bundle makeBundle(Object... keysAndValues) { + assertTrue((keysAndValues.length % 2) == 0); + + if (keysAndValues.length == 0) { + return null; + } + final Bundle ret = new Bundle(); + + for (int i = keysAndValues.length - 2; i >= 0; i -= 2) { + final String key = keysAndValues[i].toString(); + final Object value = keysAndValues[i + 1]; + + if (value == null) { + ret.putString(key, null); + } else if (value instanceof Integer) { + ret.putInt(key, (Integer) value); + } else if (value instanceof String) { + ret.putString(key, (String) value); + } else if (value instanceof Bundle) { + ret.putBundle(key, (Bundle) value); + } else { + fail("Type not supported yet: " + value.getClass().getName()); + } + } + return ret; + } + + public static <T> List<T> list(T... array) { + return Arrays.asList(array); + } + + public static <T> Set<T> hashSet(Set<T> in) { + return new HashSet<T>(in); + } + + public static <T> Set<T> set(T... values) { + return set(v -> v, values); + } + + public static <T, V> Set<T> set(Function<V, T> converter, V... values) { + return set(converter, Arrays.asList(values)); + } + + public static <T, V> Set<T> set(Function<V, T> converter, List<V> values) { + final HashSet<T> ret = new HashSet<>(); + for (V v : values) { + ret.add(converter.apply(v)); + } + return ret; + } + + public static void resetAll(Collection<?> mocks) { + for (Object o : mocks) { + reset(o); + } + } + public static void assertExpectException(Class<? extends Throwable> expectedExceptionType, + String expectedExceptionMessageRegex, Runnable r) { + assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r); + } + + public static void assertDynamicShortcutCountExceeded(Runnable r) { + assertExpectException(IllegalArgumentException.class, + "Max number of dynamic shortcuts exceeded", r); + } + + public static void assertExpectException(String message, + Class<? extends Throwable> expectedExceptionType, + String expectedExceptionMessageRegex, Runnable r) { + try { + r.run(); + Assert.fail("Expected exception type " + expectedExceptionType.getName() + + " was not thrown (message=" + message + ")"); + } catch (Throwable e) { + Assert.assertTrue( + "Expected exception type was " + expectedExceptionType.getName() + + " but caught " + e + " (message=" + message + ")", + expectedExceptionType.isAssignableFrom(e.getClass())); + if (expectedExceptionMessageRegex != null) { + MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage()); + } + } + } + + public static List<ShortcutInfo> assertShortcutIds(List<ShortcutInfo> actualShortcuts, + String... expectedIds) { + final HashSet<String> expected = new HashSet<>(list(expectedIds)); + final HashSet<String> actual = new HashSet<>(); + for (ShortcutInfo s : actualShortcuts) { + actual.add(s.getId()); + } + + // Compare the sets. + assertEquals(expected, actual); + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllHaveIntents( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNotNull("ID " + s.getId(), s.getIntent()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllNotHaveIntents( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNull("ID " + s.getId(), s.getIntent()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllHaveTitle( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNotNull("ID " + s.getId(), s.getTitle()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllNotHaveTitle( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNull("ID " + s.getId(), s.getTitle()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllHaveIconResId( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource()); + assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllHaveIconFile( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource()); + assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllHaveIcon( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllKeyFieldsOnly( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllNotKeyFieldsOnly( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllDynamic(List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.isDynamic()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllPinned(List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.isPinned()); + } + return actualShortcuts; + } + + public static List<ShortcutInfo> assertAllDynamicOrPinned( + List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned()); + } + return actualShortcuts; + } + + public static void assertDynamicOnly(ShortcutInfo si) { + assertTrue(si.isDynamic()); + assertFalse(si.isPinned()); + } + + public static void assertPinnedOnly(ShortcutInfo si) { + assertFalse(si.isDynamic()); + assertTrue(si.isPinned()); + } + + public static void assertDynamicAndPinned(ShortcutInfo si) { + assertTrue(si.isDynamic()); + assertTrue(si.isPinned()); + } + + public static void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) { + assertEquals("width", expectedWidth, bitmap.getWidth()); + assertEquals("height", expectedHeight, bitmap.getHeight()); + } + + public static <T> void assertAllUnique(Collection<T> list) { + final Set<Object> set = new HashSet<>(); + for (T item : list) { + if (set.contains(item)) { + fail("Duplicate item found: " + item + " (in the list: " + list + ")"); + } + set.add(item); + } + } + + public static Bitmap pfdToBitmap(ParcelFileDescriptor pfd) { + assertNotNull(pfd); + try { + try { + return BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); + } finally { + pfd.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void assertBundleEmpty(BaseBundle b) { + assertTrue(b == null || b.size() == 0); + } + + public static void assertCallbackNotReceived(LauncherApps.Callback mock) { + verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(), + any(UserHandle.class)); + } + + public static void assertCallbackReceived(LauncherApps.Callback mock, + UserHandle user, String packageName, String... ids) { + verify(mock).onShortcutsChanged(eq(packageName), checkShortcutIds(ids), + eq(user)); + } + + public static boolean checkAssertSuccess(Runnable r) { + try { + r.run(); + return true; + } catch (AssertionError e) { + return false; + } + } + + public static <T> T checkArgument(Predicate<T> checker, String description, + List<T> matchedCaptor) { + final Matcher<T> m = new BaseMatcher<T>() { + @Override + public boolean matches(Object item) { + if (item == null) { + return false; + } + final T value = (T) item; + if (!checker.test(value)) { + return false; + } + + if (matchedCaptor != null) { + matchedCaptor.add(value); + } + return true; + } + + @Override + public void describeTo(Description d) { + d.appendText(description); + } + }; + return Mockito.argThat(m); + } + + public static List<ShortcutInfo> checkShortcutIds(String... ids) { + return checkArgument((List<ShortcutInfo> list) -> { + final Set<String> actualSet = set(si -> si.getId(), list); + return actualSet.equals(set(ids)); + + }, "Shortcut IDs=[" + Arrays.toString(ids) + "]", null); + } + + public static void waitUntil(String message, BooleanSupplier condition) { + waitUntil(message, condition, STANDARD_TIMEOUT_SEC); + } + + public static void waitUntil(String message, BooleanSupplier condition, int timeoutSeconds) { + final long timeout = System.currentTimeMillis() + (timeoutSeconds * 1000L); + while (System.currentTimeMillis() < timeout) { + if (condition.getAsBoolean()) { + return; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + fail("Timed out for: " + message); + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 0aeb96f52181..ecfeff92ac9f 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -152,6 +152,7 @@ public class UsageStatsService extends SystemService implements private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener> mPackageAccessListeners = new ArrayList<>(); + private boolean mHaveCarrierPrivilegedApps; private List<String> mCarrierPrivilegedApps; public UsageStatsService(Context context) { @@ -931,11 +932,14 @@ public class UsageStatsService extends SystemService implements private boolean isCarrierApp(String packageName) { synchronized (mLock) { - if (mCarrierPrivilegedApps == null) { + if (!mHaveCarrierPrivilegedApps) { fetchCarrierPrivilegedAppsLocked(); } + if (mCarrierPrivilegedApps != null) { + return mCarrierPrivilegedApps.contains(packageName); + } + return false; } - return mCarrierPrivilegedApps.contains(packageName); } void clearCarrierPrivilegedApps() { @@ -943,6 +947,7 @@ public class UsageStatsService extends SystemService implements Slog.i(TAG, "Clearing carrier privileged apps list"); } synchronized (mLock) { + mHaveCarrierPrivilegedApps = false; mCarrierPrivilegedApps = null; // Need to be refetched. } } @@ -951,6 +956,7 @@ public class UsageStatsService extends SystemService implements TelephonyManager telephonyManager = getContext().getSystemService(TelephonyManager.class); mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges(); + mHaveCarrierPrivilegedApps = true; if (DEBUG) { Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps); } @@ -1024,7 +1030,8 @@ public class UsageStatsService extends SystemService implements } pw.println(); - pw.println("Carrier privileged apps: " + mCarrierPrivilegedApps); + pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps + + "): " + mCarrierPrivilegedApps); pw.println(); pw.println("Settings:"); diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 18fd98528855..b9e9ac856185 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -374,6 +374,15 @@ </activity> <activity + android:name="GetBitmapSurfaceViewActivity" + android:label="SurfaceView/GetBitmap with Camera source"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.android.test.hwui.TEST" /> + </intent-filter> + </activity> + + <activity android:name="GLTextureViewActivity" android:label="TextureView/OpenGL"> <intent-filter> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapSurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapSurfaceViewActivity.java new file mode 100644 index 000000000000..d3cd7db7d46a --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapSurfaceViewActivity.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 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.test.hwui; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.PixelCopy; +import android.graphics.PixelCopy.OnPixelCopyFinished; +import android.graphics.PixelCopy.Response; +import android.hardware.Camera; +import android.os.Bundle; +import android.os.Environment; +import android.view.Gravity; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.Toast; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +public class GetBitmapSurfaceViewActivity extends Activity implements SurfaceHolder.Callback { + private Camera mCamera; + private SurfaceView mSurfaceView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + FrameLayout content = new FrameLayout(this); + + mSurfaceView = new SurfaceView(this); + mSurfaceView.getHolder().addCallback(this); + + Button button = new Button(this); + button.setText("Copy bitmap to /sdcard/surfaceview.png"); + button.setOnClickListener((View v) -> { + Bitmap b = Bitmap.createBitmap( + mSurfaceView.getWidth(), + mSurfaceView.getHeight(), + Bitmap.Config.ARGB_8888); + PixelCopy.request(mSurfaceView, b, + mOnCopyFinished, mSurfaceView.getHandler()); + }); + + content.addView(mSurfaceView, new FrameLayout.LayoutParams(500, 400, Gravity.CENTER)); + content.addView(button, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)); + setContentView(content); + } + + private final OnPixelCopyFinished mOnCopyFinished = new OnPixelCopyFinished() { + @Override + public void onPixelCopyFinished(Response response) { + if (!response.success) { + Toast.makeText(GetBitmapSurfaceViewActivity.this, + "Failed to copy", Toast.LENGTH_SHORT).show(); + return; + } + try { + try (FileOutputStream out = new FileOutputStream( + Environment.getExternalStorageDirectory() + "/surfaceview.png");) { + response.bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + } + } catch (Exception e) { + // Ignore + } + } + }; + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mCamera = Camera.open(); + + try { + mCamera.setPreviewSurface(holder.getSurface()); + } catch (IOException t) { + android.util.Log.e("TextureView", "Cannot set preview texture target!", t); + } + + mCamera.startPreview(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mCamera.stopPreview(); + mCamera.release(); + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java index b1431c586631..5c30faba3d18 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java @@ -17,18 +17,29 @@ package com.android.test.hwui; import android.app.Activity; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.PixelCopy; +import android.graphics.PixelCopy.OnPixelCopyFinished; +import android.graphics.PixelCopy.Response; import android.graphics.PorterDuff; import android.os.Bundle; -import android.view.Gravity; +import android.os.Environment; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; +import android.view.View; +import android.widget.Button; import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.Toast; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; -@SuppressWarnings({"UnusedDeclaration"}) public class HardwareCanvasSurfaceViewActivity extends Activity implements Callback { private SurfaceView mSurfaceView; private HardwareCanvasSurfaceViewActivity.RenderingThread mThread; @@ -42,13 +53,49 @@ public class HardwareCanvasSurfaceViewActivity extends Activity implements Callb mSurfaceView = new SurfaceView(this); mSurfaceView.getHolder().addCallback(this); - content.addView(mSurfaceView, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, + Button button = new Button(this); + button.setText("Copy bitmap to /sdcard/surfaceview.png"); + button.setOnClickListener((View v) -> { + Bitmap b = Bitmap.createBitmap( + mSurfaceView.getWidth(), + mSurfaceView.getHeight(), + Bitmap.Config.ARGB_8888); + PixelCopy.request(mSurfaceView, b, + mOnCopyFinished, mSurfaceView.getHandler()); + }); + + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(button, LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + layout.addView(mSurfaceView, LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT); + + content.addView(layout, new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, - Gravity.CENTER)); + FrameLayout.LayoutParams.MATCH_PARENT)); setContentView(content); } + private final OnPixelCopyFinished mOnCopyFinished = new OnPixelCopyFinished() { + @Override + public void onPixelCopyFinished(Response response) { + if (!response.success) { + Toast.makeText(HardwareCanvasSurfaceViewActivity.this, + "Failed to copy", Toast.LENGTH_SHORT).show(); + return; + } + try { + try (FileOutputStream out = new FileOutputStream( + Environment.getExternalStorageDirectory() + "/surfaceview.png");) { + response.bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + } + } catch (Exception e) { + // Ignore + } + } + }; + @Override public void surfaceCreated(SurfaceHolder holder) { mThread = new RenderingThread(holder.getSurface()); diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index 7412bc269c28..50efc7f7db86 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -245,6 +245,13 @@ public class FontFamily_Delegate { return sFontLocation; } + // ---- delegate methods ---- + @LayoutlibDelegate + /*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) { + final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr); + return delegate != null && delegate.addFont(path, ttcIndex); + } + // ---- native methods ---- @LayoutlibDelegate @@ -270,16 +277,8 @@ public class FontFamily_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nAddFont(long nativeFamily, final String path, int ttcIndex) { - // FIXME: support ttc fonts. Hack JRE?? - final FontFamily_Delegate delegate = getDelegate(nativeFamily); - if (delegate != null) { - if (sFontLocation == null) { - delegate.mPostInitRunnables.add(() -> delegate.addFont(path)); - return true; - } - return delegate.addFont(path); - } + /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) { + assert false : "The only client of this method has been overriden."; return false; } @@ -390,6 +389,15 @@ public class FontFamily_Delegate { mPostInitRunnables = null; } + private boolean addFont(final String path, int ttcIndex) { + // FIXME: support ttc fonts. Hack JRE?? + if (sFontLocation == null) { + mPostInitRunnables.add(() -> addFont(path)); + return true; + } + return addFont(path); + } + private boolean addFont(@NonNull String path) { return addFont(path, DEFAULT_FONT_WEIGHT, path.endsWith(FONT_SUFFIX_ITALIC)); } diff --git a/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java index 6c34c70b7609..6d3bb4ca9115 100644 --- a/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java +++ b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java @@ -64,15 +64,14 @@ public class PathParser_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nParseStringForPath(long pathPtr, @NonNull String pathString, int + /*package*/ static void nParseStringForPath(long pathPtr, @NonNull String pathString, int stringLength) { Path_Delegate path_delegate = Path_Delegate.getDelegate(pathPtr); if (path_delegate == null) { - return false; + return; } assert pathString.length() == stringLength; PathDataNode.nodesToPath(createNodesFromPathData(pathString), path_delegate); - return true; } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index fea633e7036d..308488a39ec7 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -70,23 +70,6 @@ public class BridgeRenderSession extends RenderSession { } @Override - public Map<String, String> getDefaultProperties(Object viewObject) { - return mSession.getDefaultProperties(viewObject); - } - - @Override - public Result getProperty(Object objectView, String propertyName) { - // pass - return super.getProperty(objectView, propertyName); - } - - @Override - public Result setProperty(Object objectView, String propertyName, String propertyValue) { - // pass - return super.setProperty(objectView, propertyName, propertyValue); - } - - @Override public Result render(long timeout, boolean forceMeasure) { try { Bridge.prepareThread(); @@ -213,6 +196,10 @@ public class BridgeRenderSession extends RenderSession { } } + public RenderSessionImpl getSessionImpl() { + return mSession; + } + /*package*/ BridgeRenderSession(RenderSessionImpl scene, Result lastResult) { mSession = scene; if (scene != null) { 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 89272fa916c1..fd95bd5a0204 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 @@ -27,6 +27,7 @@ import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.android.PropertiesMap.Property; import com.android.layoutlib.bridge.android.view.WindowManagerImpl; import com.android.layoutlib.bridge.impl.ParserFactory; import com.android.layoutlib.bridge.impl.Stack; @@ -275,7 +276,7 @@ public final class BridgeContext extends Context { return mRenderResources; } - public Map<String, String> getDefaultPropMap(Object key) { + public PropertiesMap getDefaultPropMap(Object key) { return mDefaultPropMaps.get(key); } @@ -731,16 +732,10 @@ public final class BridgeContext extends Context { Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE, "Failed to find the style corresponding to the id " + defStyleAttr, null); } else { - if (defaultPropMap != null) { - String defStyleName = defStyleAttribute.getFirst(); - if (defStyleAttribute.getSecond()) { - defStyleName = "android:" + defStyleName; - } - defaultPropMap.put("style", defStyleName); - } + String defStyleName = defStyleAttribute.getFirst(); // look for the style in the current theme, and its parent: - ResourceValue item = mRenderResources.findItemInTheme(defStyleAttribute.getFirst(), + ResourceValue item = mRenderResources.findItemInTheme(defStyleName, defStyleAttribute.getSecond()); if (item != null) { @@ -750,6 +745,12 @@ public final class BridgeContext extends Context { if (item instanceof StyleResourceValue) { defStyleValues = (StyleResourceValue) item; } + if (defaultPropMap != null) { + if (defStyleAttribute.getSecond()) { + defStyleName = "android:" + defStyleName; + } + defaultPropMap.put("style", new Property(defStyleName, item.getValue())); + } } else { Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR, String.format( @@ -776,7 +777,8 @@ public final class BridgeContext extends Context { item = mRenderResources.getStyle(value.getSecond(), isFrameworkRes); if (item != null) { if (defaultPropMap != null) { - defaultPropMap.put("style", item.getName()); + String name = item.getName(); + defaultPropMap.put("style", new Property(name, name)); } defStyleValues = item; @@ -855,13 +857,14 @@ public final class BridgeContext extends Context { // if we found a value, we make sure this doesn't reference another value. // So we resolve it. if (resValue != null) { - // put the first default value, before the resolution. + String preResolve = resValue.getValue(); + resValue = mRenderResources.resolveResValue(resValue); + if (defaultPropMap != null) { - defaultPropMap.put(attrName, resValue.getValue()); + defaultPropMap.put(attrName, + new Property(preResolve, resValue.getValue())); } - resValue = mRenderResources.resolveResValue(resValue); - // If the value is a reference to another theme attribute that doesn't // exist, we should log a warning and omit it. String val = resValue.getValue(); @@ -949,10 +952,11 @@ public final class BridgeContext extends Context { if (resValue != null) { // Add it to defaultPropMap before resolving - defaultPropMap.put(attrName, resValue.getValue()); + String preResolve = resValue.getValue(); // resolve it to make sure there are no references left. - ta.bridgeSetValue(i, attrName, attribute.getSecond(), - mRenderResources.resolveResValue(resValue)); + resValue = mRenderResources.resolveResValue(resValue); + ta.bridgeSetValue(i, attrName, attribute.getSecond(), resValue); + defaultPropMap.put(attrName, new Property(preResolve, resValue.getValue())); } } } @@ -1915,11 +1919,4 @@ public final class BridgeContext extends Context { } } - - /** - * An alias used for the value in {@code {@link #mDefaultPropMaps}} - */ - private static class PropertiesMap extends HashMap<String, String> { - } - } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/PropertiesMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/PropertiesMap.java new file mode 100644 index 000000000000..a38d579d2b4b --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/PropertiesMap.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 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.layoutlib.bridge.android; + +import com.android.layoutlib.bridge.android.PropertiesMap.Property; + +import java.util.HashMap; + +/** + * An alias used for the value in {@link BridgeContext#mDefaultPropMaps} + */ +public class PropertiesMap extends HashMap<String, Property> { + + public static class Property { + public final String resource; + public final String value; + + public Property(String resource, String value) { + this.resource = resource; + this.value = value; + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index 0c537533479e..2d388312330b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -286,7 +286,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso return mParams; } - protected BridgeContext getContext() { + public BridgeContext getContext() { return mContext; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 866b2480b828..11fabc6a59a9 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -1415,10 +1415,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return mSystemViewInfoList; } - public Map<String, String> getDefaultProperties(Object viewObject) { - return getContext().getDefaultPropMap(viewObject); - } - public void setScene(RenderSession session) { mScene = session; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 483bddc21859..061bed7b7740 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -192,6 +192,7 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.BitmapFactory#setDensityFromOptions", "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget", "android.graphics.drawable.GradientDrawable#buildRing", + "android.graphics.FontFamily#addFont", "android.graphics.Typeface#getSystemFontConfigLocation", "android.graphics.Typeface#makeFamilyFromParsed", "android.os.Handler#sendMessageAtTime", |