Merge "Remove config_wifi_hal_pno_enable and config_wifi_ssid_white_list_enable" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 85428ab..429674c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5031,13 +5031,13 @@
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);
}
@@ -19857,8 +19857,8 @@
public class AudioRecord implements android.media.AudioRouting {
ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
- method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int getAudioFormat();
method public int getAudioSessionId();
method public int getAudioSource();
@@ -19883,8 +19883,8 @@
method public int read(java.nio.ByteBuffer, int);
method public int read(java.nio.ByteBuffer, int, int);
method public void release();
+ method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
- method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setNotificationMarkerPosition(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -19918,8 +19918,9 @@
method public abstract void onPeriodicNotification(android.media.AudioRecord);
}
- public static abstract interface AudioRecord.OnRoutingChangedListener {
+ public static abstract deprecated interface AudioRecord.OnRoutingChangedListener implements android.media.AudioRouting.OnRoutingChangedListener {
method public abstract void onRoutingChanged(android.media.AudioRecord);
+ method public default void onRoutingChanged(android.media.AudioRouting);
}
public final class AudioRecordingConfiguration implements android.os.Parcelable {
@@ -19934,10 +19935,10 @@
}
public abstract interface AudioRouting {
- method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+ method public abstract void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public abstract android.media.AudioDeviceInfo getPreferredDevice();
method public abstract android.media.AudioDeviceInfo getRoutedDevice();
- method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+ method public abstract void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
}
@@ -19957,8 +19958,8 @@
ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
- method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int attachAuxEffect(int);
method public void flush();
method public int getAudioFormat();
@@ -19990,8 +19991,8 @@
method public void play() throws java.lang.IllegalStateException;
method public void release();
method public int reloadStaticData();
+ method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
- method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setAuxEffectSendLevel(float);
method public int setBufferSizeInFrames(int);
method public int setLoopPoints(int, int, int);
@@ -20045,8 +20046,9 @@
method public abstract void onPeriodicNotification(android.media.AudioTrack);
}
- public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
- method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
+ public static abstract deprecated interface AudioTrack.OnRoutingChangedListener implements android.media.AudioRouting.OnRoutingChangedListener {
+ method public abstract void onRoutingChanged(android.media.AudioTrack);
+ method public default void onRoutingChanged(android.media.AudioRouting);
}
public class CamcorderProfile {
@@ -49059,6 +49061,7 @@
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;
}
@@ -49929,6 +49932,11 @@
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);
@@ -57585,6 +57593,7 @@
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[]);
@@ -58040,6 +58049,7 @@
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 {
@@ -58083,6 +58093,7 @@
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();
}
@@ -58623,6 +58634,18 @@
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[]);
@@ -59064,6 +59087,7 @@
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);
@@ -59165,6 +59189,7 @@
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>);
}
}
@@ -60138,18 +60163,6 @@
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);
@@ -61522,6 +61535,7 @@
}
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();
@@ -61531,6 +61545,7 @@
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 d2125a3..befff23 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5165,13 +5165,13 @@
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);
}
@@ -21336,8 +21336,8 @@
public class AudioRecord implements android.media.AudioRouting {
ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
+ method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
- method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int getAudioFormat();
method public int getAudioSessionId();
method public int getAudioSource();
@@ -21362,8 +21362,8 @@
method public int read(java.nio.ByteBuffer, int);
method public int read(java.nio.ByteBuffer, int, int);
method public void release();
+ method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
- method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setNotificationMarkerPosition(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -21399,8 +21399,9 @@
method public abstract void onPeriodicNotification(android.media.AudioRecord);
}
- public static abstract interface AudioRecord.OnRoutingChangedListener {
+ public static abstract deprecated interface AudioRecord.OnRoutingChangedListener implements android.media.AudioRouting.OnRoutingChangedListener {
method public abstract void onRoutingChanged(android.media.AudioRecord);
+ method public default void onRoutingChanged(android.media.AudioRouting);
}
public final class AudioRecordingConfiguration implements android.os.Parcelable {
@@ -21415,10 +21416,10 @@
}
public abstract interface AudioRouting {
- method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+ method public abstract void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public abstract android.media.AudioDeviceInfo getPreferredDevice();
method public abstract android.media.AudioDeviceInfo getRoutedDevice();
- method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+ method public abstract void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
}
@@ -21438,8 +21439,8 @@
ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
- method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int attachAuxEffect(int);
method public void flush();
method public int getAudioFormat();
@@ -21471,8 +21472,8 @@
method public void play() throws java.lang.IllegalStateException;
method public void release();
method public int reloadStaticData();
+ method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
- method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setAuxEffectSendLevel(float);
method public int setBufferSizeInFrames(int);
method public int setLoopPoints(int, int, int);
@@ -21526,8 +21527,9 @@
method public abstract void onPeriodicNotification(android.media.AudioTrack);
}
- public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
- method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
+ public static abstract deprecated interface AudioTrack.OnRoutingChangedListener implements android.media.AudioRouting.OnRoutingChangedListener {
+ method public abstract void onRoutingChanged(android.media.AudioTrack);
+ method public default void onRoutingChanged(android.media.AudioRouting);
}
public class CamcorderProfile {
@@ -52172,6 +52174,7 @@
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;
}
@@ -53042,6 +53045,11 @@
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);
@@ -60698,6 +60706,7 @@
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[]);
@@ -61153,6 +61162,7 @@
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 {
@@ -61196,6 +61206,7 @@
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();
}
@@ -61736,6 +61747,18 @@
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[]);
@@ -62177,6 +62200,7 @@
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);
@@ -62278,6 +62302,7 @@
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>);
}
}
@@ -63251,18 +63276,6 @@
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);
@@ -64635,6 +64648,7 @@
}
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();
@@ -64644,6 +64658,7 @@
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 f6ea9c0..d39e0b7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5031,13 +5031,13 @@
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 @@
}
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.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo);
@@ -10092,6 +10093,7 @@
}
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);
@@ -19923,8 +19925,8 @@
public class AudioRecord implements android.media.AudioRouting {
ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
- method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int getAudioFormat();
method public int getAudioSessionId();
method public int getAudioSource();
@@ -19949,8 +19951,8 @@
method public int read(java.nio.ByteBuffer, int);
method public int read(java.nio.ByteBuffer, int, int);
method public void release();
+ method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
- method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setNotificationMarkerPosition(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -19984,8 +19986,9 @@
method public abstract void onPeriodicNotification(android.media.AudioRecord);
}
- public static abstract interface AudioRecord.OnRoutingChangedListener {
+ public static abstract deprecated interface AudioRecord.OnRoutingChangedListener implements android.media.AudioRouting.OnRoutingChangedListener {
method public abstract void onRoutingChanged(android.media.AudioRecord);
+ method public default void onRoutingChanged(android.media.AudioRouting);
}
public final class AudioRecordingConfiguration implements android.os.Parcelable {
@@ -20000,10 +20003,10 @@
}
public abstract interface AudioRouting {
- method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+ method public abstract void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public abstract android.media.AudioDeviceInfo getPreferredDevice();
method public abstract android.media.AudioDeviceInfo getRoutedDevice();
- method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+ method public abstract void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
}
@@ -20023,8 +20026,8 @@
ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
- method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int attachAuxEffect(int);
method public void flush();
method public int getAudioFormat();
@@ -20056,8 +20059,8 @@
method public void play() throws java.lang.IllegalStateException;
method public void release();
method public int reloadStaticData();
+ method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
- method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setAuxEffectSendLevel(float);
method public int setBufferSizeInFrames(int);
method public int setLoopPoints(int, int, int);
@@ -20111,8 +20114,9 @@
method public abstract void onPeriodicNotification(android.media.AudioTrack);
}
- public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
- method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
+ public static abstract deprecated interface AudioTrack.OnRoutingChangedListener implements android.media.AudioRouting.OnRoutingChangedListener {
+ method public abstract void onRoutingChanged(android.media.AudioTrack);
+ method public default void onRoutingChanged(android.media.AudioRouting);
}
public class CamcorderProfile {
@@ -49135,6 +49139,7 @@
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;
}
@@ -50005,6 +50010,11 @@
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);
@@ -57661,6 +57671,7 @@
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[]);
@@ -58116,6 +58127,7 @@
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 {
@@ -58159,6 +58171,7 @@
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();
}
@@ -58699,6 +58712,18 @@
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[]);
@@ -59140,6 +59165,7 @@
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);
@@ -59241,6 +59267,7 @@
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>);
}
}
@@ -60214,18 +60241,6 @@
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);
@@ -61598,6 +61613,7 @@
}
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();
@@ -61607,6 +61623,7 @@
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 f0e35c9..f203f46 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -318,7 +318,7 @@
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 4bf1aa3..a759719 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1419,7 +1419,7 @@
* 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 +1432,7 @@
* 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 +1955,16 @@
* @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
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index bdc4404..d5d4ca7 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -589,9 +589,7 @@
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 @@
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/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index abe1aaf..a0d2339 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -19,6 +19,7 @@
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.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 @@
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 e4a98b5..919ccda 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 @@
}
/**
+ * @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/hardware/location/NanoApp.java b/core/java/android/hardware/location/NanoApp.java
index 8d50be6..03ac4a2 100644
--- a/core/java/android/hardware/location/NanoApp.java
+++ b/core/java/android/hardware/location/NanoApp.java
@@ -192,7 +192,7 @@
* @return publisher name
*/
public String getPublisher() {
- return mPublisher;
+ return mPublisher;
}
/**
@@ -201,7 +201,7 @@
* @return app name
*/
public String getName() {
- return mName;
+ return mName;
}
/**
@@ -210,7 +210,7 @@
* @return identifier for this app
*/
public int getAppId() {
- return mAppId;
+ return mAppId;
}
/**
@@ -219,7 +219,7 @@
* @return app version
*/
public int getAppVersion() {
- return mAppVersion;
+ return mAppVersion;
}
/**
@@ -228,7 +228,7 @@
* @return readable memory needed in bytes
*/
public int getNeededReadMemBytes() {
- return mNeededReadMemBytes;
+ return mNeededReadMemBytes;
}
/**
@@ -237,7 +237,7 @@
* @return writable memory needed in bytes
*/
public int getNeededWriteMemBytes() {
- return mNeededWriteMemBytes;
+ return mNeededWriteMemBytes;
}
/**
@@ -246,7 +246,7 @@
* @return executable memory needed in bytes
*/
public int getNeededExecMemBytes() {
- return mNeededExecMemBytes;
+ return mNeededExecMemBytes;
}
/**
@@ -255,7 +255,7 @@
* @return sensors needed
*/
public int[] getNeededSensors() {
- return mNeededSensors;
+ return mNeededSensors;
}
/**
@@ -264,7 +264,7 @@
* @return generated events
*/
public int[] getOutputEvents() {
- return mOutputEvents;
+ return mOutputEvents;
}
/**
@@ -273,7 +273,7 @@
* @return app binary
*/
public byte[] getAppBinary() {
- return mAppBinary;
+ return mAppBinary;
}
private NanoApp(Parcel in) {
diff --git a/core/java/android/inputmethodservice/CompactExtractEditLayout.java b/core/java/android/inputmethodservice/CompactExtractEditLayout.java
new file mode 100644
index 0000000..35c54b2
--- /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 cc201bc..085b97c 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -68,9 +68,10 @@
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 @@
boolean mExtractViewHidden;
ExtractEditText mExtractEditText;
ViewGroup mExtractAccessories;
- Button mExtractAction;
+ View mExtractAction;
ExtractedText mExtractedText;
int mExtractedToken;
@@ -1344,7 +1345,7 @@
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 @@
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 @@
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/provider/Settings.java b/core/java/android/provider/Settings.java
index 2a3c3fe..fdb1cef 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1581,6 +1581,8 @@
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 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)));
@@ -6666,6 +6668,17 @@
public static final String DEVICE_PROVISIONED = "device_provisioned";
/**
+ * Whether mobile data should be allowed while the device is being provisioned.
+ * This allows the provisioning process to turn off mobile data before the user
+ * has an opportunity to set things up, preventing other processes from burning
+ * precious bytes before wifi is setup.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String DEVICE_PROVISIONING_MOBILE_DATA_ENABLED =
+ "device_provisioning_mobile_data";
+
+ /**
* The saved value for WindowManagerService.setForcedDisplaySize().
* Two integers separated by a comma. If unset, then use the real display size.
* @hide
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 9ed4850..0a452db 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -251,9 +251,9 @@
+ "|[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 @@
"\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 f44d4c1a..c4ed94f 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -485,25 +485,15 @@
}
/**
- * Halts any current rendering into the surface. Use this if it is unclear whether
+ * Stops any rendering into the surface. Use this if it is unclear whether
* or not the surface used by the HardwareRenderer will be changing. It
- * Suspends any rendering into the surface, but will not do any destruction.
- *
- * Any subsequent draws will override the pause, resuming normal operation.
+ * Suspends any rendering into the surface, but will not do any destruction
*/
boolean pauseSurface(Surface surface) {
return nPauseSurface(mNativeProxy, surface);
}
/**
- * Hard stops or resumes rendering into the surface. This flag is used to
- * determine whether or not it is safe to use the given surface *at all*
- */
- void setStopped(boolean stopped) {
- nSetStopped(mNativeProxy, stopped);
- }
-
- /**
* Destroys all hardware rendering resources associated with the specified
* view hierarchy.
*
@@ -903,6 +893,10 @@
nSerializeDisplayListTree(mNativeProxy);
}
+ public static boolean copySurfaceInto(Surface surface, Bitmap bitmap) {
+ return nCopySurfaceInto(surface, bitmap);
+ }
+
@Override
protected void finalize() throws Throwable {
try {
@@ -998,7 +992,6 @@
private static native void nInitialize(long nativeProxy, Surface window);
private static native void nUpdateSurface(long nativeProxy, Surface window);
private static native boolean nPauseSurface(long nativeProxy, Surface window);
- private static native void nSetStopped(long nativeProxy, boolean stopped);
private static native void nSetup(long nativeProxy, int width, int height,
float lightRadius, int ambientShadowAlpha, int spotShadowAlpha);
private static native void nSetLightCenter(long nativeProxy,
@@ -1040,4 +1033,6 @@
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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 94c4cef..a324767 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1079,16 +1079,13 @@
void setWindowStopped(boolean stopped) {
if (mStopped != stopped) {
mStopped = stopped;
- final ThreadedRenderer renderer = mAttachInfo.mHardwareRenderer;
- if (renderer != null) {
- if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
- renderer.setStopped(mStopped);
- }
if (!mStopped) {
scheduleTraversals();
} else {
- if (renderer != null) {
- renderer.destroyHardwareResources(mView);
+ if (mAttachInfo.mHardwareRenderer != null) {
+ if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle());
+ mAttachInfo.mHardwareRenderer.updateSurface(null);
+ mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView);
}
}
}
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index ff10287..a585d75 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -555,7 +555,15 @@
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 a9d5113..7e9587a 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -90,6 +90,15 @@
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 @@
* @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 @@
* @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 f2bf9e1..1f2acc9 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -760,7 +760,7 @@
} 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 46b49de..8d11783 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 @@
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 @@
}
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 @@
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 @@
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/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp
index 91a3b4f..90d69d2 100644
--- a/core/jni/android_hardware_location_ContextHubService.cpp
+++ b/core/jni/android_hardware_location_ContextHubService.cpp
@@ -21,8 +21,8 @@
#include <inttypes.h>
#include <jni.h>
-#include <map>
#include <queue>
+#include <unordered_map>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
@@ -105,7 +105,7 @@
context_hub_info_s hubInfo;
jniInfo_s jniInfo;
std::queue<int> freeIds;
- std::map<int, app_instance_info_s *> appInstances;
+ std::unordered_map<int, app_instance_info_s> appInstances;
};
} // unnamed namespace
@@ -137,7 +137,7 @@
const context_hub_t *info = get_hub_info(hubHandle);
if (info) {
- msg->app = info->os_app_name;
+ msg->app_name = info->os_app_name;
return 0;
} else {
ALOGD("%s: Hub information is null for hubHandle %d", __FUNCTION__, hubHandle);
@@ -154,23 +154,23 @@
}
static int get_hub_id_for_app_instance(int id) {
- if (db.appInstances.find(id) == db.appInstances.end()) {
+ if (!db.appInstances.count(id)) {
ALOGD("%s: Cannot find app for app instance %d", __FUNCTION__, id);
return -1;
}
- int hubHandle = db.appInstances[id]->hubHandle;
+ int hubHandle = db.appInstances[id].hubHandle;
return db.hubInfo.hubs[hubHandle].hub_id;
}
static int set_dest_app(hub_message_t *msg, int id) {
- if (db.appInstances.find(id) == db.appInstances.end()) {
+ if (!db.appInstances.count(id)) {
ALOGD("%s: Cannod find app for app instance %d", __FUNCTION__, id);
return -1;
}
- msg->app = db.appInstances[id]->appInfo.name;
+ msg->app_name = db.appInstances[id].appInfo.app_name;
return 0;
}
@@ -210,76 +210,43 @@
int add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle, JNIEnv *env) {
// Not checking if the apps are indeed distinct
-
- app_instance_info_s *entry;
- void *appName;
- hub_app_name_t *name;
-
- assert(appInfo && appInfo->name && appInfo->name->app_name);
-
- entry = (app_instance_info_s *) malloc(sizeof(app_instance_info_s));
- appName = malloc(appInfo->name->app_name_len);
- name = (hub_app_name_t *) malloc(sizeof(hub_app_name_t));
-
+ app_instance_info_s entry;
int appInstanceHandle = generate_id();
- if (appInstanceHandle < 0 || !appName || !entry || !name) {
- ALOGE("Cannot find resources to add app instance %d, %p, %p",
- appInstanceHandle, appName, entry);
+ assert(appInfo);
- free(appName);
- free(entry);
- free(name);
-
- if (appInstanceHandle >= 0) {
- return_id(appInstanceHandle);
- }
-
+ if (appInstanceHandle < 0) {
+ ALOGE("Cannot find resources to add app instance %d",
+ appInstanceHandle);
return -1;
}
- memcpy(&(entry->appInfo), appInfo, sizeof(entry->appInfo));
- memcpy(appName, appInfo->name->app_name, appInfo->name->app_name_len);
- name->app_name = appName;
- name->app_name_len = appInfo->name->app_name_len;
- entry->appInfo.name = name;
- entry->truncName = 0;
- memcpy(&(entry->truncName), name->app_name,
- sizeof(entry->truncName) < name->app_name_len ?
- sizeof(entry->truncName) : name->app_name_len);
-
- // Not checking for sanity of hubId
- entry->hubHandle = hubHandle;
- entry->instanceId = appInstanceHandle;
+ entry.appInfo = *appInfo;
+ entry.instanceId = appInstanceHandle;
+ entry.truncName = appInfo->app_name.id;
+ entry.hubHandle = hubHandle;
db.appInstances[appInstanceHandle] = entry;
// Finally - let the service know of this app instance
env->CallIntMethod(db.jniInfo.jContextHubService,
db.jniInfo.contextHubServiceAddAppInstance,
- hubHandle, entry->instanceId, entry->truncName,
- entry->appInfo.version);
+ hubHandle, entry.instanceId, entry.truncName,
+ entry.appInfo.version);
ALOGW("Added App 0x%" PRIx64 " on hub Handle %" PRId32
- " as appInstance %d, original name_length %" PRId32, entry->truncName,
- entry->hubHandle, appInstanceHandle, name->app_name_len);
+ " as appInstance %d", entry.truncName,
+ entry.hubHandle, appInstanceHandle);
return appInstanceHandle;
}
int delete_app_instance(int id) {
- if (db.appInstances.find(id) == db.appInstances.end()) {
+ if (!db.appInstances.count(id)) {
return -1;
}
return_id(id);
-
- if (db.appInstances[id]) {
- // Losing the const cast below. This is intentional.
- free((void *)db.appInstances[id]->appInfo.name->app_name);
- free((void *)db.appInstances[id]->appInfo.name);
- free(db.appInstances[id]);
- db.appInstances.erase(id);
- }
+ db.appInstances.erase(id);
return 0;
}
@@ -353,27 +320,20 @@
}
int handle_query_apps_response(char *msg, int msgLen, uint32_t hubHandle) {
- int i;
JNIEnv *env;
if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
return -1;
}
int numApps = msgLen/sizeof(hub_app_info);
- hub_app_info *info = (hub_app_info *)malloc(msgLen); // handle possible alignment
+ hub_app_info info;
+ hub_app_info *unalignedInfoAddr = (hub_app_info*)msg;
- if (!info) {
- return -1;
+ for (int i = 0; i < numApps; i++, unalignedInfoAddr++) {
+ memcpy(&info, unalignedInfoAddr, sizeof(info));
+ add_app_instance(&info, hubHandle, env);
}
- memcpy(info, msg, msgLen);
- for (i = 0; i < numApps; i++) {
- add_app_instance(info, hubHandle, env);
- info++;
- }
-
- free(info);
-
return 0;
}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index ef45c87..faa4192 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -479,12 +479,6 @@
return proxy->pauseSurface(surface);
}
-static void android_view_ThreadedRenderer_setStopped(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jboolean stopped) {
- RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setStopped(stopped);
-}
-
static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlong proxyPtr,
jint width, jint height, jfloat lightRadius, jint ambientShadowAlpha, jint spotShadowAlpha) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -669,6 +663,14 @@
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
// ----------------------------------------------------------------------------
@@ -738,7 +740,6 @@
{ "nInitialize", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_initialize },
{ "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
{ "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
- { "nSetStopped", "(JZ)V", (void*) android_view_ThreadedRenderer_setStopped },
{ "nSetup", "(JIIFII)V", (void*) android_view_ThreadedRenderer_setup },
{ "nSetLightCenter", "(JFFF)V", (void*) android_view_ThreadedRenderer_setLightCenter },
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
@@ -775,6 +776,8 @@
{ "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 9a2e39c7..778f797 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
index cce5187..2e9b196 100644
--- a/core/res/res/drawable-hdpi/ic_launcher_android.png
+++ b/core/res/res/drawable-hdpi/ic_launcher_android.png
Binary files differ
diff --git a/core/res/res/drawable-ldpi/ic_launcher_android.png b/core/res/res/drawable-ldpi/ic_launcher_android.png
index 628a8de..245e4b7 100644
--- a/core/res/res/drawable-ldpi/ic_launcher_android.png
+++ b/core/res/res/drawable-ldpi/ic_launcher_android.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_launcher_android.png b/core/res/res/drawable-mdpi/ic_launcher_android.png
index 6a97d5b..baacd4f 100644
--- a/core/res/res/drawable-mdpi/ic_launcher_android.png
+++ b/core/res/res/drawable-mdpi/ic_launcher_android.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_launcher_android.png b/core/res/res/drawable-xhdpi/ic_launcher_android.png
index b1097d6..00b69a5 100644
--- a/core/res/res/drawable-xhdpi/ic_launcher_android.png
+++ b/core/res/res/drawable-xhdpi/ic_launcher_android.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_launcher_android.png b/core/res/res/drawable-xxhdpi/ic_launcher_android.png
new file mode 100644
index 0000000..ad05cd5
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_launcher_android.png
Binary files differ
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 0000000..f6e872e
--- /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 0000000..edbc826
--- /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 0000000..ffef346
--- /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 0000000..89777b0
--- /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 0000000..cb2de5a
--- /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 0000000..dcbcdbf
--- /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 0000000..6494bee5
--- /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 0000000..9c6a6c3
--- /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 0000000..8449978
--- /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 0000000..adade104
--- /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 0000000..e3cd2ce
--- /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/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 4b8640c..5850e50 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 0000000..a12f499
--- /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 0000000..c91cbc1
--- /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 0000000..f103aa9
--- /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 756a94b..6d6065f 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 61753b1..66509fb 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/colors_material.xml b/core/res/res/values/colors_material.xml
index 7399fa9..c8ca116 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/symbols.xml b/core/res/res/values/symbols.xml
index 60dbcd0..7eaff7b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2530,4 +2530,23 @@
<java-symbol type="id" name="titleDividerNoCustom" />
<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 478d66c..25a6e00 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 348f8fd..edb3082 100644
--- a/core/tests/coretests/src/android/util/PatternsTest.java
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -419,6 +419,36 @@
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/graphics/java/android/graphics/PixelCopy.java b/graphics/java/android/graphics/PixelCopy.java
new file mode 100644
index 0000000..c599126
--- /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/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 0606b0b..717a1e6 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -84,6 +84,7 @@
Properties.cpp \
PropertyValuesHolder.cpp \
PropertyValuesAnimatorSet.cpp \
+ Readback.cpp \
RenderBufferCache.cpp \
RenderNode.cpp \
RenderProperties.cpp \
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index e209e2a..aba5d4b 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -137,6 +137,9 @@
// whether children with non-zero Z in the chunk should be reordered
bool reorderChildren;
+#if HWUI_NEW_OPS
+ const ClipBase* reorderClip;
+#endif
};
DisplayList();
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index f12e523..6fc74a5 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -269,7 +269,8 @@
}
template <typename V>
-void FrameBuilder::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
+void FrameBuilder::defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode,
+ const V& zTranslatedNodes) {
const int size = zTranslatedNodes.size();
if (size == 0
|| (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
@@ -305,7 +306,7 @@
// attempt to render the shadow if the caster about to be drawn is its caster,
// OR if its caster's Z value is similar to the previous potential caster
if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) {
- deferShadow(*casterNodeOp);
+ deferShadow(reorderClip, *casterNodeOp);
lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
shadowIndex++;
@@ -319,7 +320,7 @@
}
}
-void FrameBuilder::deferShadow(const RenderNodeOp& casterNodeOp) {
+void FrameBuilder::deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterNodeOp) {
auto& node = *casterNodeOp.renderNode;
auto& properties = node.properties();
@@ -365,6 +366,10 @@
casterPath = frameAllocatedPath;
}
+ // apply reorder clip to shadow, so it respects clip at beginning of reorderable chunk
+ int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
+ mCanvasState.writableSnapshot()->applyClip(reorderClip,
+ *mCanvasState.currentSnapshot()->transform);
if (CC_LIKELY(!mCanvasState.getRenderTargetClipBounds().isEmpty())) {
Matrix4 shadowMatrixXY(casterNodeOp.localMatrix);
Matrix4 shadowMatrixZ(casterNodeOp.localMatrix);
@@ -386,6 +391,7 @@
currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
}
}
+ mCanvasState.restoreToCount(restoreTo);
}
void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
@@ -438,11 +444,11 @@
// can't be null, since DL=null node rejection happens before deferNodePropsAndOps
const DisplayList& displayList = *(renderNode.getDisplayList());
- for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
+ for (auto& chunk : displayList.getChunks()) {
FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
- defer3dChildren(ChildrenSelectMode::Negative, zTranslatedNodes);
+ defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Negative, zTranslatedNodes);
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
const RecordedOp* op = displayList.getOps()[opIndex];
receivers[op->opId](*this, *op);
@@ -453,7 +459,7 @@
deferProjectedChildren(renderNode);
}
}
- defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
+ defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes);
}
}
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index 039ab6b..a6fd761 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -193,9 +193,10 @@
void deferNodePropsAndOps(RenderNode& node);
template <typename V>
- void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+ void defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode,
+ const V& zTranslatedNodes);
- void deferShadow(const RenderNodeOp& casterOp);
+ void deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterOp);
void deferProjectedChildren(const RenderNode& renderNode);
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 8f914ac..a8ace8c 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 @@
, 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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::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::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::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::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 d2633aa..6368ddd 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 @@
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 @@
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 0000000..d7df77c
--- /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 0000000..ea03c82
--- /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/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index b78497d..ab733f1 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -57,6 +57,16 @@
return displayList;
}
+void RecordingCanvas::insertReorderBarrier(bool enableReorder) {
+ if (enableReorder) {
+ mDeferredBarrierType = DeferredBarrierType::OutOfOrder;
+ mDeferredBarrierClip = getRecordedClip();
+ } else {
+ mDeferredBarrierType = DeferredBarrierType::InOrder;
+ mDeferredBarrierClip = nullptr;
+ }
+}
+
SkCanvas* RecordingCanvas::asSkCanvas() {
LOG_ALWAYS_FATAL_IF(!mDisplayList,
"attempting to get an SkCanvas when we are not recording!");
@@ -610,6 +620,7 @@
newChunk.beginOpIndex = insertIndex;
newChunk.endOpIndex = insertIndex + 1;
newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
+ newChunk.reorderClip = mDeferredBarrierClip;
int nextChildIndex = mDisplayList->children.size();
newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index acb88e2..219296c 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -55,10 +55,7 @@
// ----------------------------------------------------------------------------
// MISC HWUI OPERATIONS - TODO: CATEGORIZE
// ----------------------------------------------------------------------------
- virtual void insertReorderBarrier(bool enableReorder) override {
- mDeferredBarrierType = enableReorder
- ? DeferredBarrierType::OutOfOrder : DeferredBarrierType::InOrder;
- }
+ virtual void insertReorderBarrier(bool enableReorder) override;
virtual void drawLayer(DeferredLayerUpdater* layerHandle) override;
virtual void drawRenderNode(RenderNode* renderNode) override;
@@ -312,6 +309,7 @@
std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
ResourceCache& mResourceCache;
DeferredBarrierType mDeferredBarrierType = DeferredBarrierType::None;
+ const ClipBase* mDeferredBarrierClip = nullptr;
DisplayList* mDisplayList = nullptr;
bool mHighContrastText = false;
SkAutoTUnref<SkDrawFilter> mDrawFilter;
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index 14c8f392..cfdc084 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -35,13 +35,14 @@
///////////////////////////////////////////////////////////////////////////////
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 @@
, 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 @@
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 @@
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 0bd6365..6141b4e 100644
--- a/libs/hwui/TessellationCache.h
+++ b/libs/hwui/TessellationCache.h
@@ -52,10 +52,10 @@
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 @@
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/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index b39de26..a455f57 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -24,7 +24,8 @@
MinikinFontSkia::MinikinFontSkia(SkTypeface* typeface, const void* fontData, size_t fontSize,
int ttcIndex) :
- mTypeface(typeface), mFontData(fontData), mFontSize(fontSize), mTtcIndex(ttcIndex) {
+ MinikinFont(typeface->uniqueID()), mTypeface(typeface), mFontData(fontData),
+ mFontSize(fontSize), mTtcIndex(ttcIndex) {
}
MinikinFontSkia::~MinikinFontSkia() {
@@ -99,10 +100,6 @@
return mTtcIndex;
}
-int32_t MinikinFontSkia::GetUniqueId() const {
- return mTypeface->uniqueID();
-}
-
uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
uint32_t flags = paint->getFlags();
SkPaint::Hinting hinting = paint->getHinting();
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index dbc65f9..a7c9fb0 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -41,8 +41,6 @@
const void* GetTable(uint32_t tag, size_t* size, MinikinDestroyFunc* destroy);
- int32_t GetUniqueId() const;
-
SkTypeface* GetSkTypeface() const;
// Access to underlying raw font bytes
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 890d4a1..ab66b2a 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -113,11 +113,18 @@
mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
mHaveNewSurface = true;
mSwapHistory.clear();
+ makeCurrent();
} else {
mRenderThread.removeFrameCallback(this);
}
}
+void CanvasContext::requireSurface() {
+ LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+ "requireSurface() called but no surface set!");
+ makeCurrent();
+}
+
void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) {
mSwapBehavior = swapBehavior;
}
@@ -139,18 +146,6 @@
return mRenderThread.removeFrameCallback(this);
}
-void CanvasContext::setStopped(bool stopped) {
- if (mStopped != stopped) {
- mStopped = stopped;
- if (mStopped) {
- mRenderThread.removeFrameCallback(this);
- if (mEglManager.isCurrent(mEglSurface)) {
- mEglManager.makeCurrent(EGL_NO_SURFACE);
- }
- }
- }
-}
-
// TODO: don't pass viewport size, it's automatic via EGL
void CanvasContext::setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
@@ -177,9 +172,7 @@
mOpaque = opaque;
}
-bool CanvasContext::makeCurrent() {
- if (mStopped) return false;
-
+void CanvasContext::makeCurrent() {
// TODO: Figure out why this workaround is needed, see b/13913604
// In the meantime this matches the behavior of GLRenderer, so it is not a regression
EGLint error = 0;
@@ -187,7 +180,6 @@
if (error) {
setSurface(nullptr);
}
- return !error;
}
static bool wasSkipped(FrameInfo* info) {
@@ -679,7 +671,7 @@
}
Layer* CanvasContext::createTextureLayer() {
- mEglManager.initialize();
+ requireSurface();
return LayerRenderer::createTextureLayer(mRenderThread.renderState());
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 52df3abe..9350114 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -82,14 +82,13 @@
void initialize(Surface* surface);
void updateSurface(Surface* surface);
bool pauseSurface(Surface* surface);
- void setStopped(bool stopped);
bool hasSurface() { return mNativeSurface.get(); }
void setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
void setLightCenter(const Vector3& lightCenter);
void setOpaque(bool opaque);
- bool makeCurrent();
+ void makeCurrent();
void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
int64_t syncQueued, RenderNode* target);
void draw();
@@ -173,6 +172,7 @@
friend class android::uirenderer::RenderState;
void setSurface(Surface* window);
+ void requireSurface();
void freePrefetchedLayers(TreeObserver* observer);
@@ -185,7 +185,6 @@
EglManager& mEglManager;
sp<Surface> mNativeSurface;
EGLSurface mEglSurface = EGL_NO_SURFACE;
- bool mStopped = false;
bool mBufferPreserved = false;
SwapBehavior mSwapBehavior = kSwap_default;
struct SwapHistory {
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index ed472ac..651aaa2 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -115,7 +115,7 @@
ATRACE_CALL();
int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
mRenderThread->timeLord().vsyncReceived(vsync);
- bool canDraw = mContext->makeCurrent();
+ mContext->makeCurrent();
Caches::getInstance().textureCache.resetMarkInUse(mContext);
for (size_t i = 0; i < mLayers.size(); i++) {
@@ -126,9 +126,8 @@
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
- if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) {
+ if (CC_UNLIKELY(!mContext->hasSurface())) {
mSyncResult |= kSync_LostSurfaceRewardIfFound;
- info.out.canDrawThisFrame = false;
}
if (info.out.hasAnimations) {
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index ac6a28f..8def7ad 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -270,6 +270,12 @@
// Ensure we always have a valid surface & context
surface = mPBufferSurface;
}
+ // TODO: Temporary to help diagnose b/27286867
+ if (mCurrentSurface == mPBufferSurface || surface == mPBufferSurface) {
+ ALOGD("Switching from surface %p%s to %p%s", mCurrentSurface,
+ mCurrentSurface == mPBufferSurface ? " (pbuffer)" : "",
+ surface, surface == mPBufferSurface ? " (pbuffer)" : "");
+ }
if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
if (errOut) {
*errOut = eglGetError();
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index c5a2dc7..096093c 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"
@@ -167,18 +168,6 @@
return (bool) postAndWait(task);
}
-CREATE_BRIDGE2(setStopped, CanvasContext* context, bool stopped) {
- args->context->setStopped(args->stopped);
- return nullptr;
-}
-
-void RenderProxy::setStopped(bool stopped) {
- SETUP_TASK(setStopped);
- args->context = mContext;
- args->stopped = stopped;
- postAndWait(task);
-}
-
CREATE_BRIDGE6(setup, CanvasContext* context, int width, int height,
float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
args->context->setup(args->width, args->height, args->lightRadius,
@@ -616,6 +605,20 @@
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 32d3283b..98aace0 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -79,7 +79,6 @@
ANDROID_API void initialize(const sp<Surface>& surface);
ANDROID_API void updateSurface(const sp<Surface>& surface);
ANDROID_API bool pauseSurface(const sp<Surface>& surface);
- ANDROID_API void setStopped(bool stopped);
ANDROID_API void setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
ANDROID_API void setLightCenter(const Vector3& lightCenter);
@@ -127,6 +126,8 @@
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/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 9877439..209a104 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -1722,6 +1722,35 @@
EXPECT_EQ(4, renderer.getIndex());
}
+RENDERTHREAD_TEST(FrameBuilder, shadowClipping) {
+ class ShadowClippingTestRenderer : public TestRendererBase {
+ public:
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipState->rect)
+ << "Shadow must respect pre-barrier canvas clip value.";
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ }
+ };
+ auto parent = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ // Apply a clip before the reorder barrier/shadow casting child is drawn.
+ // This clip must be applied to the shadow cast by the child.
+ canvas.clipRect(25, 25, 75, 75, SkRegion::kIntersect_Op);
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ });
+
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+ TestUtils::createSyncedNodeList(parent),
+ (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
+ ShadowClippingTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
static void testProperty(std::function<void(RenderProperties&)> propSetupCallback,
std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
class PropertyTestRenderer : public TestRendererBase {
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index c49ff71..18171de 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -603,6 +603,36 @@
EXPECT_TRUE(chunks[1].reorderChildren);
}
+TEST(RecordingCanvas, insertReorderBarrier_clip) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ // first chunk: no recorded clip
+ canvas.insertReorderBarrier(true);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+
+ // second chunk: no recorded clip, since inorder region
+ canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
+ canvas.insertReorderBarrier(false);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+
+ // third chunk: recorded clip
+ canvas.insertReorderBarrier(true);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ });
+
+ auto chunks = dl->getChunks();
+ ASSERT_EQ(3u, chunks.size());
+
+ EXPECT_TRUE(chunks[0].reorderChildren);
+ EXPECT_EQ(nullptr, chunks[0].reorderClip);
+
+ EXPECT_FALSE(chunks[1].reorderChildren);
+ EXPECT_EQ(nullptr, chunks[1].reorderClip);
+
+ EXPECT_TRUE(chunks[2].reorderChildren);
+ ASSERT_NE(nullptr, chunks[2].reorderClip);
+ EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect);
+}
+
TEST(RecordingCanvas, refPaint) {
SkPaint paint;
diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h
index ccf2287..7212897b 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/AudioRecord.java b/media/java/android/media/AudioRecord.java
index ca306cc..a5550ec 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -37,6 +37,8 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
/**
* The AudioRecord class manages the audio resources for Java applications
* to record audio from the audio input hardware of the platform. This is
@@ -1320,6 +1322,7 @@
* Note: The query is only valid if the AudioRecord is currently recording. If it is not,
* <code>getRoutedDevice()</code> will return null.
*/
+ @Override
public AudioDeviceInfo getRoutedDevice() {
int deviceId = native_getRoutedDeviceId();
if (deviceId == 0) {
@@ -1338,8 +1341,8 @@
/*
* Call BEFORE adding a routing callback handler.
*/
- private void testEnableNativeRoutingCallbacks() {
- if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+ private void testEnableNativeRoutingCallbacksLocked() {
+ if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback();
}
}
@@ -1347,24 +1350,23 @@
/*
* Call AFTER removing a routing callback handler.
*/
- private void testDisableNativeRoutingCallbacks() {
- if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+ private void testDisableNativeRoutingCallbacksLocked() {
+ if (mRoutingChangeListeners.size() == 0) {
native_disableDeviceCallback();
}
}
//--------------------------------------------------------------------------
- // >= "N" (Re)Routing Info
+ // (Re)Routing Info
//--------------------
/**
* The list of AudioRouting.OnRoutingChangedListener interfaces added (with
- * {@link AudioRecord#addOnRoutingListener(AudioRouting.OnRoutingChangedListener,
- * android.os.Handler)}
- * by an app to receive (re)routing notifications.
+ * {@link AudioRecord#addOnRoutingChangedListener} by an app to receive
+ * (re)routing notifications.
*/
- private ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>
- mNewRoutingChangeListeners =
- new ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>();
+ @GuardedBy("mRoutingChangeListeners")
+ private ArrayMap<AudioRouting.OnRoutingChangedListener,
+ NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
/**
* Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of
@@ -1375,14 +1377,15 @@
* the callback. If <code>null</code>, the {@link Handler} associated with the main
* {@link Looper} will be used.
*/
- public void addOnRoutingListener(AudioRouting.OnRoutingChangedListener listener,
+ @Override
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
android.os.Handler handler) {
- if (listener != null && !mNewRoutingChangeListeners.containsKey(listener)) {
- synchronized (mNewRoutingChangeListeners) {
- testEnableNativeRoutingCallbacks();
- mNewRoutingChangeListeners.put(
- listener, new NativeNewRoutingEventHandlerDelegate(this, listener,
- handler != null ? handler : new Handler(mInitializationLooper)));
+ synchronized (mRoutingChangeListeners) {
+ if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+ testEnableNativeRoutingCallbacksLocked();
+ mRoutingChangeListeners.put(
+ listener, new NativeRoutingEventHandlerDelegate(this, listener,
+ handler != null ? handler : new Handler(mInitializationLooper)));
}
}
}
@@ -1393,39 +1396,42 @@
* @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
* to remove.
*/
- public void removeOnRoutingListener(AudioRouting.OnRoutingChangedListener listener) {
- synchronized (mNewRoutingChangeListeners) {
- if (mNewRoutingChangeListeners.containsKey(listener)) {
- mNewRoutingChangeListeners.remove(listener);
- testDisableNativeRoutingCallbacks();
+ @Override
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mRoutingChangeListeners) {
+ if (mRoutingChangeListeners.containsKey(listener)) {
+ mRoutingChangeListeners.remove(listener);
+ testDisableNativeRoutingCallbacksLocked();
}
}
}
//--------------------------------------------------------------------------
- // Marshmallow (Re)Routing Info
+ // (Re)Routing Info
//--------------------
/**
- * Defines the interface by which applications can receive notifications of routing
- * changes for the associated {@link AudioRecord}.
+ * Defines the interface by which applications can receive notifications of
+ * routing changes for the associated {@link AudioRecord}.
+ *
+ * @deprecated users should switch to the general purpose
+ * {@link AudioRouting.OnRoutingChangedListener} class instead.
*/
- public interface OnRoutingChangedListener {
+ @Deprecated
+ public interface OnRoutingChangedListener extends AudioRouting.OnRoutingChangedListener {
/**
- * Called when the routing of an AudioRecord changes from either and explicit or
- * policy rerouting. Use {@link #getRoutedDevice()} to retrieve the newly routed-from
- * device.
+ * Called when the routing of an AudioRecord changes from either and
+ * explicit or policy rerouting. Use {@link #getRoutedDevice()} to
+ * retrieve the newly routed-from device.
*/
public void onRoutingChanged(AudioRecord audioRecord);
- }
- /**
- * The list of AudioRecord.OnRoutingChangedListener interface added (with
- * {@link AudioRecord#addOnRoutingChangedListener(OnRoutingChangedListener,android.os.Handler)}
- * by an app to receive (re)routing notifications.
- */
- private ArrayMap<OnRoutingChangedListener, NativeRoutingEventHandlerDelegate>
- mRoutingChangeListeners =
- new ArrayMap<OnRoutingChangedListener, NativeRoutingEventHandlerDelegate>();
+ @Override
+ default public void onRoutingChanged(AudioRouting router) {
+ if (router instanceof AudioRecord) {
+ onRoutingChanged((AudioRecord) router);
+ }
+ }
+ }
/**
* Adds an {@link OnRoutingChangedListener} to receive notifications of routing changes
@@ -1435,88 +1441,28 @@
* @param handler Specifies the {@link Handler} object for the thread on which to execute
* the callback. If <code>null</code>, the {@link Handler} associated with the main
* {@link Looper} will be used.
+ * @deprecated users should switch to the general purpose
+ * {@link AudioRouting.OnRoutingChangedListener} class instead.
*/
@Deprecated
public void addOnRoutingChangedListener(OnRoutingChangedListener listener,
android.os.Handler handler) {
- if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
- synchronized (mRoutingChangeListeners) {
- testEnableNativeRoutingCallbacks();
- mRoutingChangeListeners.put(
- listener, new NativeRoutingEventHandlerDelegate(this, listener,
- handler != null ? handler : new Handler(mInitializationLooper)));
- }
- }
+ addOnRoutingChangedListener((AudioRouting.OnRoutingChangedListener) listener, handler);
}
/**
* Removes an {@link OnRoutingChangedListener} which has been previously added
* to receive rerouting notifications.
* @param listener The previously added {@link OnRoutingChangedListener} interface to remove.
+ * @deprecated users should switch to the general purpose
+ * {@link AudioRouting.OnRoutingChangedListener} class instead.
*/
@Deprecated
public void removeOnRoutingChangedListener(OnRoutingChangedListener listener) {
- synchronized (mRoutingChangeListeners) {
- if (mRoutingChangeListeners.containsKey(listener)) {
- mRoutingChangeListeners.remove(listener);
- testDisableNativeRoutingCallbacks();
- }
- }
+ removeOnRoutingChangedListener((AudioRouting.OnRoutingChangedListener) listener);
}
/**
- * >= "N" Routing
- * Helper class to handle the forwarding of native events to the appropriate listener
- * (potentially) handled in a different thread
- */
- private class NativeNewRoutingEventHandlerDelegate {
- private final Handler mHandler;
-
- NativeNewRoutingEventHandlerDelegate(final AudioRecord record,
- final AudioRouting.OnRoutingChangedListener listener,
- Handler handler) {
- // find the looper for our new event handler
- Looper looper;
- if (handler != null) {
- looper = handler.getLooper();
- } else {
- // no given handler, use the looper the AudioRecord was created in
- looper = mInitializationLooper;
- }
-
- // construct the event handler with this looper
- if (looper != null) {
- // implement the event handler delegate
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- if (record == null) {
- return;
- }
- switch(msg.what) {
- case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE:
- if (listener != null) {
- listener.onRoutingChanged(record);
- }
- break;
- default:
- loge("Unknown native event type: " + msg.what);
- break;
- }
- }
- };
- } else {
- mHandler = null;
- }
- }
-
- Handler getHandler() {
- return mHandler;
- }
- }
-
- /**
- * Marshmallow Routing
* Helper class to handle the forwarding of native events to the appropriate listener
* (potentially) handled in a different thread
*/
@@ -1524,7 +1470,7 @@
private final Handler mHandler;
NativeRoutingEventHandlerDelegate(final AudioRecord record,
- final OnRoutingChangedListener listener,
+ final AudioRouting.OnRoutingChangedListener listener,
Handler handler) {
// find the looper for our new event handler
Looper looper;
@@ -1571,26 +1517,12 @@
*/
private void broadcastRoutingChange() {
AudioManager.resetAudioPortGeneration();
- // Marshmallow Routing
- Collection<NativeRoutingEventHandlerDelegate> values;
synchronized (mRoutingChangeListeners) {
- values = mRoutingChangeListeners.values();
- }
- for(NativeRoutingEventHandlerDelegate delegate : values) {
- Handler handler = delegate.getHandler();
- if (handler != null) {
- handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
- }
- }
- // >= "N" Routing
- Collection<NativeNewRoutingEventHandlerDelegate> newValues;
- synchronized (mNewRoutingChangeListeners) {
- newValues = mNewRoutingChangeListeners.values();
- }
- for(NativeNewRoutingEventHandlerDelegate delegate : newValues) {
- Handler handler = delegate.getHandler();
- if (handler != null) {
- handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
+ for (NativeRoutingEventHandlerDelegate delegate : mRoutingChangeListeners.values()) {
+ Handler handler = delegate.getHandler();
+ if (handler != null) {
+ handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
+ }
}
}
}
@@ -1623,6 +1555,7 @@
* @return true if successful, false if the specified {@link AudioDeviceInfo} is non-null and
* does not correspond to a valid audio input device.
*/
+ @Override
public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
// Do some validation....
if (deviceInfo != null && !deviceInfo.isSource()) {
@@ -1643,6 +1576,7 @@
* Returns the selected input specified by {@link #setPreferredDevice}. Note that this
* is not guarenteed to correspond to the actual device being used for recording.
*/
+ @Override
public AudioDeviceInfo getPreferredDevice() {
synchronized (this) {
return mPreferredDevice;
@@ -1683,7 +1617,6 @@
* (potentially) handled in a different thread
*/
private class NativeEventHandler extends Handler {
-
private final AudioRecord mAudioRecord;
NativeEventHandler(AudioRecord recorder, Looper looper) {
@@ -1714,8 +1647,7 @@
break;
}
}
- };
-
+ }
//---------------------------------------------------------
// Java methods called from the native side
diff --git a/media/java/android/media/AudioRouting.java b/media/java/android/media/AudioRouting.java
index 41f92d4..26fa631 100644
--- a/media/java/android/media/AudioRouting.java
+++ b/media/java/android/media/AudioRouting.java
@@ -57,7 +57,7 @@
* the callback. If <code>null</code>, the {@link Handler} associated with the main
* {@link Looper} will be used.
*/
- public void addOnRoutingListener(OnRoutingChangedListener listener,
+ public void addOnRoutingChangedListener(OnRoutingChangedListener listener,
Handler handler);
/**
@@ -66,7 +66,7 @@
* @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
* to remove.
*/
- public void removeOnRoutingListener(OnRoutingChangedListener listener);
+ public void removeOnRoutingChangedListener(OnRoutingChangedListener listener);
/**
* Defines the interface by which applications can receive notifications of routing
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 621129d..9d360db 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -40,6 +40,7 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
/**
@@ -1489,6 +1490,7 @@
* @deprecated Applications should use {@link #setVolume} instead, as it
* more gracefully scales down to mono, and up to multi-channel content beyond stereo.
*/
+ @Deprecated
public int setStereoVolume(float leftGain, float rightGain) {
if (isRestricted()) {
return SUCCESS;
@@ -2397,6 +2399,7 @@
* @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
* does not correspond to a valid audio output device.
*/
+ @Override
public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
// Do some validation....
if (deviceInfo != null && !deviceInfo.isSink()) {
@@ -2416,6 +2419,7 @@
* Returns the selected output specified by {@link #setPreferredDevice}. Note that this
* is not guaranteed to correspond to the actual device being used for playback.
*/
+ @Override
public AudioDeviceInfo getPreferredDevice() {
synchronized (this) {
return mPreferredDevice;
@@ -2427,6 +2431,7 @@
* Note: The query is only valid if the AudioTrack is currently playing. If it is not,
* <code>getRoutedDevice()</code> will return null.
*/
+ @Override
public AudioDeviceInfo getRoutedDevice() {
int deviceId = native_getRoutedDeviceId();
if (deviceId == 0) {
@@ -2445,8 +2450,8 @@
/*
* Call BEFORE adding a routing callback handler.
*/
- private void testEnableNativeRoutingCallbacks() {
- if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+ private void testEnableNativeRoutingCallbacksLocked() {
+ if (mRoutingChangeListeners.size() == 0) {
native_enableDeviceCallback();
}
}
@@ -2454,24 +2459,23 @@
/*
* Call AFTER removing a routing callback handler.
*/
- private void testDisableNativeRoutingCallbacks() {
- if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+ private void testDisableNativeRoutingCallbacksLocked() {
+ if (mRoutingChangeListeners.size() == 0) {
native_disableDeviceCallback();
}
}
//--------------------------------------------------------------------------
- // >= "N" (Re)Routing Info
+ // (Re)Routing Info
//--------------------
/**
* The list of AudioRouting.OnRoutingChangedListener interfaces added (with
- * {@link AudioTrack#addOnRoutingListener(AudioRouting.OnRoutingChangedListener,
- * android.os.Handler)}
- * by an app to receive (re)routing notifications.
+ * {@link AudioRecord#addOnRoutingChangedListener} by an app to receive
+ * (re)routing notifications.
*/
- private ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>
- mNewRoutingChangeListeners =
- new ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>();
+ @GuardedBy("mRoutingChangeListeners")
+ private ArrayMap<AudioRouting.OnRoutingChangedListener,
+ NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
/**
* Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
@@ -2482,14 +2486,15 @@
* the callback. If <code>null</code>, the {@link Handler} associated with the main
* {@link Looper} will be used.
*/
- public void addOnRoutingListener(AudioRouting.OnRoutingChangedListener listener,
+ @Override
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
Handler handler) {
- if (listener != null && !mNewRoutingChangeListeners.containsKey(listener)) {
- synchronized (mNewRoutingChangeListeners) {
- testEnableNativeRoutingCallbacks();
- mNewRoutingChangeListeners.put(
- listener, new NativeNewRoutingEventHandlerDelegate(this, listener,
- handler != null ? handler : new Handler(mInitializationLooper)));
+ synchronized (mRoutingChangeListeners) {
+ if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+ testEnableNativeRoutingCallbacksLocked();
+ mRoutingChangeListeners.put(
+ listener, new NativeRoutingEventHandlerDelegate(this, listener,
+ handler != null ? handler : new Handler(mInitializationLooper)));
}
}
}
@@ -2500,39 +2505,42 @@
* @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
* to remove.
*/
- public void removeOnRoutingListener(AudioRouting.OnRoutingChangedListener listener) {
- if (mNewRoutingChangeListeners.containsKey(listener)) {
- mNewRoutingChangeListeners.remove(listener);
+ @Override
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mRoutingChangeListeners) {
+ if (mRoutingChangeListeners.containsKey(listener)) {
+ mRoutingChangeListeners.remove(listener);
+ }
+ testDisableNativeRoutingCallbacksLocked();
}
- testDisableNativeRoutingCallbacks();
}
//--------------------------------------------------------------------------
- // Marshmallow (Re)Routing Info
+ // (Re)Routing Info
//--------------------
/**
- * Defines the interface by which applications can receive notifications of routing
- * changes for the associated {@link AudioTrack}.
+ * Defines the interface by which applications can receive notifications of
+ * routing changes for the associated {@link AudioTrack}.
+ *
+ * @deprecated users should switch to the general purpose
+ * {@link AudioRouting.OnRoutingChangedListener} class instead.
*/
@Deprecated
- public interface OnRoutingChangedListener {
+ public interface OnRoutingChangedListener extends AudioRouting.OnRoutingChangedListener {
/**
- * Called when the routing of an AudioTrack changes from either and explicit or
- * policy rerouting. Use {@link #getRoutedDevice()} to retrieve the newly routed-to
- * device.
+ * Called when the routing of an AudioTrack changes from either and
+ * explicit or policy rerouting. Use {@link #getRoutedDevice()} to
+ * retrieve the newly routed-to device.
*/
- @Deprecated
public void onRoutingChanged(AudioTrack audioTrack);
- }
- /**
- * The list of AudioTrack.OnRoutingChangedListener interfaces added (with
- * {@link AudioTrack#addOnRoutingChangedListener(OnRoutingChangedListener, android.os.Handler)}
- * by an app to receive (re)routing notifications.
- */
- private ArrayMap<OnRoutingChangedListener, NativeRoutingEventHandlerDelegate>
- mRoutingChangeListeners =
- new ArrayMap<OnRoutingChangedListener, NativeRoutingEventHandlerDelegate>();
+ @Override
+ default public void onRoutingChanged(AudioRouting router) {
+ if (router instanceof AudioTrack) {
+ onRoutingChanged((AudioTrack) router);
+ }
+ }
+ }
/**
* Adds an {@link OnRoutingChangedListener} to receive notifications of routing changes
@@ -2542,33 +2550,25 @@
* @param handler Specifies the {@link Handler} object for the thread on which to execute
* the callback. If <code>null</code>, the {@link Handler} associated with the main
* {@link Looper} will be used.
+ * @deprecated users should switch to the general purpose
+ * {@link AudioRouting.OnRoutingChangedListener} class instead.
*/
@Deprecated
public void addOnRoutingChangedListener(OnRoutingChangedListener listener,
android.os.Handler handler) {
- if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
- synchronized (mRoutingChangeListeners) {
- testEnableNativeRoutingCallbacks();
- mRoutingChangeListeners.put(
- listener, new NativeRoutingEventHandlerDelegate(this, listener,
- handler != null ? handler : new Handler(mInitializationLooper)));
- }
- }
+ addOnRoutingChangedListener((AudioRouting.OnRoutingChangedListener) listener, handler);
}
/**
* Removes an {@link OnRoutingChangedListener} which has been previously added
* to receive rerouting notifications.
* @param listener The previously added {@link OnRoutingChangedListener} interface to remove.
+ * @deprecated users should switch to the general purpose
+ * {@link AudioRouting.OnRoutingChangedListener} class instead.
*/
@Deprecated
public void removeOnRoutingChangedListener(OnRoutingChangedListener listener) {
- synchronized (mRoutingChangeListeners) {
- if (mRoutingChangeListeners.containsKey(listener)) {
- mRoutingChangeListeners.remove(listener);
- }
- testDisableNativeRoutingCallbacks();
- }
+ removeOnRoutingChangedListener((AudioRouting.OnRoutingChangedListener) listener);
}
/**
@@ -2576,27 +2576,12 @@
*/
private void broadcastRoutingChange() {
AudioManager.resetAudioPortGeneration();
-
- // Marshmallow Routing
- Collection<NativeRoutingEventHandlerDelegate> values;
synchronized (mRoutingChangeListeners) {
- values = mRoutingChangeListeners.values();
- }
- for(NativeRoutingEventHandlerDelegate delegate : values) {
- Handler handler = delegate.getHandler();
- if (handler != null) {
- handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
- }
- }
- // >= "N" Routing
- Collection<NativeNewRoutingEventHandlerDelegate> newValues;
- synchronized (mNewRoutingChangeListeners) {
- newValues = mNewRoutingChangeListeners.values();
- }
- for(NativeNewRoutingEventHandlerDelegate delegate : newValues) {
- Handler handler = delegate.getHandler();
- if (handler != null) {
- handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
+ for (NativeRoutingEventHandlerDelegate delegate : mRoutingChangeListeners.values()) {
+ Handler handler = delegate.getHandler();
+ if (handler != null) {
+ handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
+ }
}
}
}
@@ -2681,7 +2666,6 @@
}
/**
- * Marshmallow Routing API.
* Helper class to handle the forwarding of native events to the appropriate listener
* (potentially) handled in a different thread
*/
@@ -2689,57 +2673,6 @@
private final Handler mHandler;
NativeRoutingEventHandlerDelegate(final AudioTrack track,
- final OnRoutingChangedListener listener,
- Handler handler) {
- // find the looper for our new event handler
- Looper looper;
- if (handler != null) {
- looper = handler.getLooper();
- } else {
- // no given handler, use the looper the AudioTrack was created in
- looper = mInitializationLooper;
- }
-
- // construct the event handler with this looper
- if (looper != null) {
- // implement the event handler delegate
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- if (track == null) {
- return;
- }
- switch(msg.what) {
- case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE:
- if (listener != null) {
- listener.onRoutingChanged(track);
- }
- break;
- default:
- loge("Unknown native event type: " + msg.what);
- break;
- }
- }
- };
- } else {
- mHandler = null;
- }
- }
-
- Handler getHandler() {
- return mHandler;
- }
- }
-
- /**
- * Marshmallow Routing API.
- * Helper class to handle the forwarding of native events to the appropriate listener
- * (potentially) handled in a different thread
- */
- private class NativeNewRoutingEventHandlerDelegate {
- private final Handler mHandler;
-
- NativeNewRoutingEventHandlerDelegate(final AudioTrack track,
final AudioRouting.OnRoutingChangedListener listener,
Handler handler) {
// find the looper for our new event handler
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index b78869e..55d5f42 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1107,6 +1107,9 @@
* 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 @@
*
* @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 @@
* @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 @@
*
* @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 0aa2c13..e1d7e61 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/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 40b54d3..7a7d3a1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -232,12 +232,13 @@
final MenuItem list = menu.findItem(R.id.menu_list);
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
- boolean recents = cwd == null;
- createDir.setVisible(picking && !recents && cwd.isCreateSupported());
+ createDir.setVisible(picking);
+ createDir.setEnabled(canCreateDirectory());
// No display options in recent directories
- if (picking && recents) {
+ boolean inRecents = cwd == null;
+ if (picking && inRecents) {
grid.setVisible(false);
list.setVisible(false);
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index bc2133e..aa9f356 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -833,6 +833,15 @@
getActivity(),
DocumentsActivity.class);
+
+ // Relay any config overrides bits present in the original intent.
+ Intent original = getActivity().getIntent();
+ if (original != null && original.hasExtra(Shared.EXTRA_PRODUCTIVITY_MODE)) {
+ intent.putExtra(
+ Shared.EXTRA_PRODUCTIVITY_MODE,
+ original.getBooleanExtra(Shared.EXTRA_PRODUCTIVITY_MODE, false));
+ }
+
// Set an appropriate title on the drawer when it is shown in the picker.
// Coupled with the fact that we auto-open the drawer for copy/move operations
// it should basically be the thing people see first.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
index 450341f..2288fe74 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 @@
}
// 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 329afdd..6ed4ea1 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -196,9 +196,10 @@
}
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 60dd7e1..a3c6bd7 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 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 @@
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/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index d353f31..8881034 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 @@
Comparator<AppEntry> mRebuildComparator;
ArrayList<AppEntry> mRebuildResult;
ArrayList<AppEntry> mLastAppList;
+ boolean mRebuildForeground;
Session(Callbacks callbacks) {
mCallbacks = callbacks;
@@ -572,6 +573,11 @@
// 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 @@
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 @@
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 @@
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 @@
}
}
- 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 ff70190..bcbc6ac 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 @@
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/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f5854f5..bd3c6ba 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"
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 0000000..33bceaa
--- /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 0000000..a12ddff
--- /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 b1a2242..555a69a 100644
--- a/packages/SystemUI/res/drawable/recents_info_dark.xml
+++ b/packages/SystemUI/res/drawable/recents_info_dark.xml
@@ -14,8 +14,8 @@
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 bc58c3b..65e7bf5 100644
--- a/packages/SystemUI/res/drawable/recents_info_light.xml
+++ b/packages/SystemUI/res/drawable/recents_info_light.xml
@@ -14,8 +14,8 @@
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 186aaf6..ae89631 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 0000000..2b49dd3
--- /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 c813818..b1b2f1e 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 2b3c5df..2df57bf 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 9f41dff..dc9ffa9 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/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 287bb22..82daaa6 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 @@
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 b1d9555..6b476ee 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 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.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.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 @@
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 @@
// 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 @@
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 0000000..d6ef636
--- /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 0000000..3a4350e
--- /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 69d98af..44f220b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -19,6 +19,7 @@
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;
@@ -30,6 +31,7 @@
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 +222,20 @@
}
/**
+ * 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) {
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 a867bde..22acb88 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.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 @@
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 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/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 6e585ae..6be8a4a 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.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 @@
@ViewDebug.ExportedProperty(deepExport=true, prefix="header_")
TaskViewHeader mHeaderView;
View mActionButtonView;
+ View mIncompatibleAppToastView;
TaskViewCallbacks mCb;
@ViewDebug.ExportedProperty(category="recents")
@@ -345,6 +347,9 @@
mActionButtonView.setScaleY(1f);
mActionButtonView.setAlpha(0f);
mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
+ if (mIncompatibleAppToastView != null) {
+ mIncompatibleAppToastView.setVisibility(View.INVISIBLE);
+ }
}
/**
@@ -536,6 +541,10 @@
// 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 @@
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 @@
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 570ff8a..16d8e53 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.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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 3005535..98e0dd9 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.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.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;
@@ -866,6 +866,9 @@
mEntranceAnimationRunning = true;
resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
mSnapAlgorithm.getMiddleTarget());
+
+ // Vibrate after docking
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
@@ -894,6 +897,9 @@
// Don't start immediately - give a little bit time to settle the drag resize change.
stopDragging(getCurrentPosition(), 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 1b2393a..3ac7b26 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.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 @@
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 @@
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 @@
}
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 86c1fca..c2521b3 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 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 @@
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/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
index a5ebbba..9ed5022 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 @@
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/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 75430ff..c8a2ec0 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 @@
}
private boolean shouldSuppressFullScreenIntent(String key) {
+ if (isDeviceInVrMode()) {
+ return true;
+ }
+
if (mPowerManager.isInteractive()) {
return mNotificationData.shouldSuppressScreenOn(key);
} else {
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 6dd196b..c4c64e7 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 @@
}
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/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index e205ff5..011e159 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 @@
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 @@
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 180d918..2c53e29 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 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 @@
}
}
+ /**
+ * 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/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index aa98648..5a90488 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -58,7 +58,6 @@
* 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 @@
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 22cc066..544e645 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -2966,16 +2966,6 @@
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 @@
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 d136f1a..9ab6300 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -401,7 +401,8 @@
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 fd9a94d..229a3f4 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -1010,7 +1010,7 @@
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 9bc6bff..05e4245 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.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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
}
}
- /* For testing */
+ @VisibleForTesting
protected void removeAccountInternal(Account account) {
removeAccountInternal(getUserAccountsForCaller(), account, getCallingUid());
}
@@ -4044,7 +4050,8 @@
}
}
- 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 @@
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 @@
}
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 @@
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 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 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 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 @@
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 @@
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 @@
* @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 @@
}
}
+ @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 @@
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 0f48d21..3ec51e3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13164,6 +13164,9 @@
}
}
+ private volatile long mWtfClusterStart;
+ private volatile int mWtfClusterCount;
+
/**
* Write a description of an error (crash, WTF, ANR) to the drop box.
* @param eventType to include in the drop box tag ("crash", "wtf", etc.)
@@ -13190,6 +13193,16 @@
// Exit early if the dropbox isn't configured to accept this report type.
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
+ // Rate-limit how often we're willing to do the heavy lifting below to
+ // collect and record logs; currently 5 logs per 10 second period.
+ final long now = SystemClock.elapsedRealtime();
+ if (now - mWtfClusterStart > 10 * DateUtils.SECOND_IN_MILLIS) {
+ mWtfClusterStart = now;
+ mWtfClusterCount = 1;
+ } else {
+ if (mWtfClusterCount++ >= 5) return;
+ }
+
final StringBuilder sb = new StringBuilder(1024);
appendDropBoxProcessHeaders(process, processName, sb);
if (process != null) {
@@ -17131,7 +17144,7 @@
// 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);
@@ -17140,17 +17153,17 @@
// 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/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index 1825c88..0e192ea 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 @@
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 3cd194b..a0f0bee 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 @@
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.
@@ -2459,12 +2459,10 @@
// 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 @@
// 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 @@
enqueueStatus);
}
- if (ignoreNotification) {
- return;
- }
-
mRankingHelper.extractSignals(r);
final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
@@ -2683,58 +2672,6 @@
}
}
- /**
- * 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 NotificationListeners extends ManagedServices {
private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
- private boolean mNotificationGroupsDesired;
public NotificationListeners() {
super(getContext(), mHandler, mNotificationList, mUserProfiles);
@@ -3904,7 +3840,6 @@
final INotificationListener listener = (INotificationListener) info.service;
final NotificationRankingUpdate update;
synchronized (mNotificationList) {
- updateNotificationGroupsDesiredLocked();
update = makeRankingUpdateLocked(info);
}
try {
@@ -3921,7 +3856,6 @@
updateEffectsSuppressorLocked();
}
mLightTrimListeners.remove(removed);
- updateNotificationGroupsDesiredLocked();
}
public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
@@ -4114,31 +4048,6 @@
}
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 79d9c86..5ee7cc9 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 @@
@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 c124255..1641e1d 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 @@
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 @@
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 @@
@Override
public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
+ verifyCaller(packageName, userId);
+
synchronized (mLock) {
return mMaxIconDimension;
}
@@ -1445,7 +1447,15 @@
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 @@
// === 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 @@
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 @@
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 @@
case "reset-throttling":
handleResetThrottling();
break;
+ case "reset-all-throttling":
+ handleResetAllThrottling();
+ break;
case "override-config":
handleOverrideConfig();
break;
@@ -2160,6 +2194,9 @@
case "unload-user":
handleUnloadUser();
break;
+ case "clear-shortcuts":
+ handleClearShortcuts();
+ break;
default:
return handleDefaultCommands(cmd);
}
@@ -2179,9 +2216,12 @@
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 @@
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 @@
final String packageName = getNextArgRequired();
+ Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName);
+
synchronized (mLock) {
getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine();
saveUserLocked(mUserId);
@@ -2224,6 +2276,8 @@
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 @@
}
private void handleResetConfig() {
+ Slog.i(TAG, "cmd: handleResetConfig");
+
synchronized (mLock) {
loadConfigurationLocked();
}
@@ -2276,8 +2332,20 @@
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 160d44c..27077f2 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.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.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 @@
private long mPanicTime;
private WindowManager mWindowManager;
private int mCurrentUserId;
+ private IVrManager mVrManager;
public ImmersiveModeConfirmation(Context context) {
mContext = context;
@@ -75,6 +80,8 @@
.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 @@
}
}
+ 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 @@
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 a14c614..ab404db 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.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 f004b45..07e017f 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.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;
@@ -186,6 +188,32 @@
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) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index fb3c6ec..fe32104 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.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 @@
*/
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);
-
- // We'll need to scale if the crop is sufficiently bigger than the display
-
- // 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());
- }
-
- 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
- }
+ if (options.outWidth <= 0 || options.outHeight <= 0) {
+ Slog.e(TAG, "Invalid wallpaper data");
+ success = false;
} 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();
+ boolean needCrop = false;
+ boolean needScale = false;
- 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) {
+ // 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));
+
+ // Don't bother cropping if what we're left with is identity
+ needCrop = (options.outHeight >= cropHint.height()
+ && options.outWidth >= cropHint.width());
+ }
+
+ // scale if the crop height winds up not matching the recommended metrics
+ needScale = (wallpaper.height != cropHint.height());
+
+ if (DEBUG) {
+ 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);
+ }
+
+ 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");
}
- } finally {
- IoUtils.closeQuietly(bos);
- IoUtils.closeQuietly(f);
+ 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);
+ }
}
}
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index ed935ce..a5d68da 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;
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 @@
@Override
public WebViewProviderInfo[] getWebViewPackages() {
int numFallbackPackages = 0;
+ int numAvailableByDefaultPackages = 0;
XmlResourceParser parser = null;
List<WebViewProviderInfo> webViewProviders = new ArrayList<WebViewProviderInfo>();
try {
@@ -83,12 +83,12 @@
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 @@
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 cd976e7..df5d027 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 @@
}
private void updateFallbackStateOnBoot() {
+ if (!mSystemInterface.isFallbackLogicEnabled()) return;
+
WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
updateFallbackState(webviewProviders, true);
}
@@ -497,8 +499,15 @@
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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e7ae2b0..6f45508 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -823,7 +823,7 @@
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 e6dd6a4..7ffdb35 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -19,7 +19,8 @@
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 dae8447..7d28e39 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 @@
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 @@
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 5b4803b..4468857 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.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 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 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 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 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 @@
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 @@
}
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 @@
}
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 @@
@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 b08bd72..2fd11da 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.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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 0000000..26b87c5
--- /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 0000000..c00520d
--- /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 0000000..701e058
--- /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 0000000..d09b62c
--- /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 0aeb96f..ecfeff9 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 @@
private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
mPackageAccessListeners = new ArrayList<>();
+ private boolean mHaveCarrierPrivilegedApps;
private List<String> mCarrierPrivilegedApps;
public UsageStatsService(Context context) {
@@ -931,11 +932,14 @@
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 @@
Slog.i(TAG, "Clearing carrier privileged apps list");
}
synchronized (mLock) {
+ mHaveCarrierPrivilegedApps = false;
mCarrierPrivilegedApps = null; // Need to be refetched.
}
}
@@ -951,6 +956,7 @@
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 @@
}
pw.println();
- pw.println("Carrier privileged apps: " + mCarrierPrivilegedApps);
+ pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
+ + "): " + mCarrierPrivilegedApps);
pw.println();
pw.println("Settings:");
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index fcb967f..ccabace 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -102,6 +102,7 @@
public static final int CMD_NET_STAT_POLL = BASE + 40;
public static final int EVENT_DATA_RAT_CHANGED = BASE + 41;
public static final int CMD_CLEAR_PROVISIONING_SPINNER = BASE + 42;
+ public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 43;
/***** Constants *****/
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 18fd985..b9e9ac8 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 0000000..d3cd7db
--- /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 b1431c5..5c30fab 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;
-@SuppressWarnings({"UnusedDeclaration"})
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
public class HardwareCanvasSurfaceViewActivity extends Activity implements Callback {
private SurfaceView mSurfaceView;
private HardwareCanvasSurfaceViewActivity.RenderingThread mThread;
@@ -42,13 +53,49 @@
mSurfaceView = new SurfaceView(this);
mSurfaceView.getHolder().addCallback(this);
- content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+ 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,
- 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/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
index 702be49..06949a0 100644
--- a/tests/SoundTriggerTestApp/res/layout/main.xml
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -60,29 +60,22 @@
android:onClick="onUnEnrollButtonClicked"
android:padding="20dp" />
+ <Button
+ android:id="@+id/play_trigger_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/play_trigger"
+ android:onClick="onPlayTriggerButtonClicked"
+ android:padding="20dp" />
+
</LinearLayout>
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/model_group_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="20dp"
- android:checkedButton="@+id/model_one"
android:orientation="vertical">
- <RadioButton android:id="@+id/model_one"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/model_one"
- android:onClick="onRadioButtonClicked"/>
- <RadioButton android:id="@+id/model_two"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/model_two"
- android:onClick="onRadioButtonClicked"/>
- <RadioButton android:id="@+id/model_three"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/model_three"
- android:onClick="onRadioButtonClicked"/>
</RadioGroup>
<ScrollView
@@ -93,7 +86,7 @@
android:fillViewport="true">
<TextView
- android:id="@+id/console"
+ android:id="@+id/console"
android:paddingTop="20pt"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
index b4ca71b..7c1f649 100644
--- a/tests/SoundTriggerTestApp/res/values/strings.xml
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -21,8 +21,6 @@
<string name="unenroll">Un-load</string>
<string name="start_recog">Start</string>
<string name="stop_recog">Stop</string>
- <string name="model_one">Model One</string>
- <string name="model_two">Model Two</string>
- <string name="model_three">Model Three</string>
+ <string name="play_trigger">Play Trigger Audio</string>
<string name="none">Debug messages appear here:\n</string>
</resources>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
index 3ca96d2..5fd38e9 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
@@ -16,24 +16,36 @@
package com.android.test.soundtrigger;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
import java.util.Random;
import java.util.UUID;
import android.app.Activity;
-import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
import android.media.soundtrigger.SoundTriggerDetector;
import android.media.soundtrigger.SoundTriggerManager;
-import android.text.Editable;
-import android.text.method.ScrollingMovementMethod;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserManager;
+import android.text.Editable;
+import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
+import android.widget.Button;
import android.widget.RadioButton;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
@@ -44,20 +56,17 @@
private SoundTriggerUtil mSoundTriggerUtil;
private Random mRandom;
- private UUID mModelUuid1 = UUID.randomUUID();
- private UUID mModelUuid2 = UUID.randomUUID();
- private UUID mModelUuid3 = UUID.randomUUID();
- private UUID mVendorUuid = UUID.randomUUID();
- private SoundTriggerDetector mDetector1 = null;
- private SoundTriggerDetector mDetector2 = null;
- private SoundTriggerDetector mDetector3 = null;
+ private Map<Integer, ModelInfo> mModelInfoMap;
+ private Map<View, Integer> mModelIdMap;
private TextView mDebugView = null;
- private int mSelectedModelId = 1;
+ private int mSelectedModelId = -1;
private ScrollView mScrollView = null;
+ private Button mPlayTriggerButton = null;
private PowerManager.WakeLock mScreenWakelock;
private Handler mHandler;
+ private RadioGroup mRadioGroup;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -66,11 +75,108 @@
setContentView(R.layout.main);
mDebugView = (TextView) findViewById(R.id.console);
mScrollView = (ScrollView) findViewById(R.id.scroller_id);
+ mRadioGroup = (RadioGroup) findViewById(R.id.model_group_id);
+ mPlayTriggerButton = (Button) findViewById(R.id.play_trigger_id);
mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE);
mDebugView.setMovementMethod(new ScrollingMovementMethod());
mSoundTriggerUtil = new SoundTriggerUtil(this);
mRandom = new Random();
mHandler = new Handler();
+
+ mModelInfoMap = new HashMap();
+ mModelIdMap = new HashMap();
+
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ // Load all the models in the data dir.
+ for (File file : getFilesDir().listFiles()) {
+ // Find meta-data in .properties files, ignore everything else.
+ if (!file.getName().endsWith(".properties")) {
+ continue;
+ }
+ try {
+ Properties properties = new Properties();
+ properties.load(new FileInputStream(file));
+ createModelInfoAndWidget(properties);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to load properties file " + file.getName());
+ }
+ }
+
+ // Create a few dummy models if we didn't load anything.
+ if (mModelIdMap.isEmpty()) {
+ Properties dummyModelProperties = new Properties();
+ for (String name : new String[]{"One", "Two", "Three"}) {
+ dummyModelProperties.setProperty("name", "Model " + name);
+ createModelInfoAndWidget(dummyModelProperties);
+ }
+ }
+ }
+
+ private void createModelInfoAndWidget(Properties properties) {
+ try {
+ ModelInfo modelInfo = new ModelInfo();
+
+ if (!properties.containsKey("name")) {
+ throw new RuntimeException("must have a 'name' property");
+ }
+ modelInfo.name = properties.getProperty("name");
+
+ if (properties.containsKey("modelUuid")) {
+ modelInfo.modelUuid = UUID.fromString(properties.getProperty("modelUuid"));
+ } else {
+ modelInfo.modelUuid = UUID.randomUUID();
+ }
+
+ if (properties.containsKey("vendorUuid")) {
+ modelInfo.vendorUuid = UUID.fromString(properties.getProperty("vendorUuid"));
+ } else {
+ modelInfo.vendorUuid = UUID.randomUUID();
+ }
+
+ if (properties.containsKey("triggerAudio")) {
+ modelInfo.triggerAudioPlayer = MediaPlayer.create(this, Uri.parse(
+ getFilesDir().getPath() + "/" + properties.getProperty("triggerAudio")));
+ }
+
+ if (properties.containsKey("dataFile")) {
+ File modelDataFile = new File(
+ getFilesDir().getPath() + "/" + properties.getProperty("dataFile"));
+ modelInfo.modelData = new byte[(int) modelDataFile.length()];
+ FileInputStream input = new FileInputStream(modelDataFile);
+ input.read(modelInfo.modelData, 0, modelInfo.modelData.length);
+ } else {
+ modelInfo.modelData = new byte[1024];
+ mRandom.nextBytes(modelInfo.modelData);
+ }
+
+ // TODO: Add property support for keyphrase models when they're exposed by the
+ // service. Also things like how much audio they should record with the capture session
+ // provided in the callback.
+
+ // Add a widget into the radio group.
+ RadioButton button = new RadioButton(this);
+ mRadioGroup.addView(button);
+ button.setText(modelInfo.name);
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ onRadioButtonClicked(v);
+ }
+ });
+
+ // Update our maps containing the button -> id and id -> modelInfo.
+ int newModelId = mModelIdMap.size() + 1;
+ mModelIdMap.put(button, newModelId);
+ mModelInfoMap.put(newModelId, modelInfo);
+
+ // If we don't have something selected, select this first thing.
+ if (mSelectedModelId < 0) {
+ button.setChecked(true);
+ onRadioButtonClicked(button);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error parsing properties for " + properties.getProperty("name"), e);
+ }
}
private void postMessage(String msg) {
@@ -91,27 +197,15 @@
}
private synchronized UUID getSelectedUuid() {
- if (mSelectedModelId == 2) return mModelUuid2;
- if (mSelectedModelId == 3) return mModelUuid3;
- return mModelUuid1; // Default.
+ return mModelInfoMap.get(mSelectedModelId).modelUuid;
}
private synchronized void setDetector(SoundTriggerDetector detector) {
- if (mSelectedModelId == 2) {
- mDetector2 = detector;
- return;
- }
- if (mSelectedModelId == 3) {
- mDetector3 = detector;
- return;
- }
- mDetector1 = detector;
+ mModelInfoMap.get(mSelectedModelId).detector = detector;
}
private synchronized SoundTriggerDetector getDetector() {
- if (mSelectedModelId == 2) return mDetector2;
- if (mSelectedModelId == 3) return mDetector3;
- return mDetector1;
+ return mModelInfoMap.get(mSelectedModelId).detector;
}
private void screenWakeup() {
@@ -127,25 +221,29 @@
mScreenWakelock.release();
}
+ /** TODO: Should return the abstract sound model that can be then sent to the service. */
+ private GenericSoundModel createNewSoundModel() {
+ ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
+ return new GenericSoundModel(modelInfo.modelUuid, modelInfo.vendorUuid,
+ modelInfo.modelData);
+ }
+
/**
* Called when the user clicks the enroll button.
* Performs a fresh enrollment.
*/
public void onEnrollButtonClicked(View v) {
postMessage("Loading model: " + mSelectedModelId);
- // Generate a fake model to push.
- byte[] data = new byte[1024];
- mRandom.nextBytes(data);
- UUID modelUuid = getSelectedUuid();
- GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data);
+
+ GenericSoundModel model = createNewSoundModel();
boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
if (status) {
Toast.makeText(
- this, "Successfully created sound trigger model UUID=" + modelUuid,
+ this, "Successfully created sound trigger model UUID=" + model.uuid,
Toast.LENGTH_SHORT).show();
} else {
- Toast.makeText(this, "Failed to enroll!!!" + modelUuid, Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, "Failed to enroll!!!" + model.uuid, Toast.LENGTH_SHORT).show();
}
// Test the SoundManager API.
@@ -185,11 +283,7 @@
Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
return;
}
- // Generate a fake model to push.
- byte[] data = new byte[2048];
- mRandom.nextBytes(data);
- GenericSoundModel updated = new GenericSoundModel(soundModel.uuid,
- soundModel.vendorUuid, data);
+ GenericSoundModel updated = createNewSoundModel();
boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated);
if (status) {
Toast.makeText(this, "Successfully re-enrolled, model UUID=" + updated.uuid,
@@ -237,29 +331,32 @@
public synchronized void onRadioButtonClicked(View view) {
// Is the button now checked?
boolean checked = ((RadioButton) view).isChecked();
- // Check which radio button was clicked
- switch(view.getId()) {
- case R.id.model_one:
- if (checked) {
- mSelectedModelId = 1;
- postMessage("Selected model one.");
- }
- break;
- case R.id.model_two:
- if (checked) {
- mSelectedModelId = 2;
- postMessage("Selected model two.");
- }
- break;
- case R.id.model_three:
- if (checked) {
- mSelectedModelId = 3;
- postMessage("Selected model three.");
- }
- break;
+ if (checked) {
+ mSelectedModelId = mModelIdMap.get(view);
+ ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
+ postMessage("Selected " + modelInfo.name);
+
+ // Set the play trigger button to be enabled only if we actually have some audio.
+ mPlayTriggerButton.setEnabled(modelInfo.triggerAudioPlayer != null);
}
}
+ public synchronized void onPlayTriggerButtonClicked(View v) {
+ ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
+ modelInfo.triggerAudioPlayer.start();
+ postMessage("Playing trigger audio for " + modelInfo.name);
+ }
+
+ // Helper struct for holding information about a model.
+ private static class ModelInfo {
+ public String name;
+ public UUID modelUuid;
+ public UUID vendorUuid;
+ public MediaPlayer triggerAudioPlayer;
+ public SoundTriggerDetector detector;
+ public byte modelData[];
+ };
+
// Implementation of SoundTriggerDetector.Callback.
public class DetectorCallback extends SoundTriggerDetector.Callback {
public void onAvailabilityChanged(int status) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index 7412bc2..50efc7f 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 @@
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 @@
}
@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 @@
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 6c34c70..6d3bb4c 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 @@
}
@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/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 483bddc..061bed7 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 @@
"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",