Merge "AudioService: modify logic for independent a11y volume"
diff --git a/api/current.txt b/api/current.txt
index b9cd7d0..cc8e709 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -989,6 +989,7 @@
field public static final int preferenceStyle = 16842894; // 0x101008e
field public static final int presentationTheme = 16843712; // 0x10103c0
field public static final int previewImage = 16843482; // 0x10102da
+ field public static final int primaryContentAlpha = 16843367; // 0x1010267
field public static final int priority = 16842780; // 0x101001c
field public static final int privateImeOptions = 16843299; // 0x1010223
field public static final int process = 16842769; // 0x1010011
@@ -4591,6 +4592,7 @@
public abstract class FragmentContainer {
ctor public FragmentContainer();
+ method public android.app.Fragment instantiate(android.content.Context, java.lang.String, android.os.Bundle);
method public abstract android.view.View onFindViewById(int);
method public abstract boolean onHasView();
}
@@ -5025,6 +5027,7 @@
field public static final int DEFAULT_LIGHTS = 4; // 0x4
field public static final int DEFAULT_SOUND = 1; // 0x1
field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
@@ -5108,6 +5111,7 @@
method public android.app.Notification.Action clone();
method public int describeContents();
method public boolean getAllowGeneratedReplies();
+ method public android.app.RemoteInput[] getDataOnlyRemoteInputs();
method public android.os.Bundle getExtras();
method public android.graphics.drawable.Icon getIcon();
method public android.app.RemoteInput[] getRemoteInputs();
@@ -5573,14 +5577,18 @@
}
public final class RemoteInput implements android.os.Parcelable {
+ method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String, android.net.Uri>);
method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
method public int describeContents();
method public boolean getAllowFreeFormInput();
+ method public java.util.Set<java.lang.String> getAllowedDataTypes();
method public java.lang.CharSequence[] getChoices();
+ method public static java.util.Map<java.lang.String, android.net.Uri> getDataResultsFromIntent(android.content.Intent, java.lang.String);
method public android.os.Bundle getExtras();
method public java.lang.CharSequence getLabel();
method public java.lang.String getResultKey();
method public static android.os.Bundle getResultsFromIntent(android.content.Intent);
+ method public boolean isDataOnly();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RemoteInput> CREATOR;
field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
@@ -5592,6 +5600,7 @@
method public android.app.RemoteInput.Builder addExtras(android.os.Bundle);
method public android.app.RemoteInput build();
method public android.os.Bundle getExtras();
+ method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean);
method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]);
method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence);
@@ -8999,10 +9008,10 @@
field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER";
field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
- field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
- field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
- field public static final java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
- field public static final java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
@@ -9787,6 +9796,8 @@
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
+ method public android.content.IntentSender getShortcutConfigActivityIntent(android.content.pm.LauncherActivityInfo);
+ method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(java.lang.String, android.os.UserHandle);
method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
method public boolean hasShortcutHostPermission();
@@ -10390,6 +10401,7 @@
public class ShortcutManager {
method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+ method public android.content.Intent createShortcutResultIntent(android.content.pm.ShortcutInfo);
method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.CharSequence);
method public void enableShortcuts(java.util.List<java.lang.String>);
@@ -12121,15 +12133,57 @@
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
method public static void RGBToHSV(int, int, int, float[]);
+ method public float alpha();
+ method public static float alpha(long);
method public static int alpha(int);
method public static int argb(int, int, int, int);
+ method public static int argb(float, float, float, float);
+ method public float blue();
+ method public static float blue(long);
method public static int blue(int);
+ method public static android.graphics.ColorSpace colorSpace(long);
method public static void colorToHSV(int, float[]);
+ method public android.graphics.Color convert(android.graphics.ColorSpace);
+ method public static long convert(int, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace.Connector);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace.Connector);
+ method public android.graphics.ColorSpace getColorSpace();
+ method public float getComponent(int);
+ method public int getComponentCount();
+ method public float[] getComponents();
+ method public android.graphics.ColorSpace.Model getModel();
+ method public float green();
+ method public static float green(long);
method public static int green(int);
+ method public static boolean isInColorSpace(long, android.graphics.ColorSpace);
+ method public boolean isSrgb();
+ method public static boolean isSrgb(long);
+ method public boolean isWideGamut();
+ method public static boolean isWideGamut(long);
+ method public float luminance();
+ method public static float luminance(long);
method public static float luminance(int);
+ method public long pack();
+ method public static long pack(int);
+ method public static long pack(float, float, float);
+ method public static long pack(float, float, float, float);
+ method public static long pack(float, float, float, float, android.graphics.ColorSpace);
method public static int parseColor(java.lang.String);
+ method public float red();
+ method public static float red(long);
method public static int red(int);
method public static int rgb(int, int, int);
+ method public static int rgb(float, float, float);
+ method public int toArgb();
+ method public static int toArgb(long);
+ method public static android.graphics.Color valueOf(int);
+ method public static android.graphics.Color valueOf(long);
+ method public static android.graphics.Color valueOf(float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float, android.graphics.ColorSpace);
+ method public static android.graphics.Color valueOf(float[], android.graphics.ColorSpace);
field public static final int BLACK = -16777216; // 0xff000000
field public static final int BLUE = -16776961; // 0xff0000ff
field public static final int CYAN = -16711681; // 0xff00ffff
@@ -12201,7 +12255,7 @@
field public static final float[] ILLUMINANT_D65;
field public static final float[] ILLUMINANT_D75;
field public static final float[] ILLUMINANT_E;
- field public static final int MAX_ID = 64; // 0x40
+ field public static final int MAX_ID = 63; // 0x3f
field public static final int MIN_ID = -1; // 0xffffffff
}
@@ -29846,6 +29900,15 @@
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
}
+ public abstract class ProxyFileDescriptorCallback {
+ ctor public ProxyFileDescriptorCallback();
+ method public void onFsync() throws android.system.ErrnoException;
+ method public long onGetSize() throws android.system.ErrnoException;
+ method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
+ method public abstract void onRelease();
+ method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
+ }
+
public class RecoverySystem {
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
@@ -30263,6 +30326,7 @@
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -32360,7 +32424,7 @@
ctor public ContactsContract.Intents();
field public static final java.lang.String ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS = "android.provider.action.VOICE_SEND_MESSAGE_TO_CONTACTS";
field public static final java.lang.String ATTACH_IMAGE = "com.android.contacts.action.ATTACH_IMAGE";
- field public static final java.lang.String CONTACTS_DATABASE_CREATED = "android.provider.Contacts.DATABASE_CREATED";
+ field public static final deprecated java.lang.String CONTACTS_DATABASE_CREATED = "android.provider.Contacts.DATABASE_CREATED";
field public static final java.lang.String EXTRA_CREATE_DESCRIPTION = "com.android.contacts.action.CREATE_DESCRIPTION";
field public static final java.lang.String EXTRA_FORCE_CREATE = "com.android.contacts.action.FORCE_CREATE";
field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID";
@@ -32470,8 +32534,10 @@
public static final class ContactsContract.ProviderStatus {
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/provider_status";
field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String DATABASE_CREATION_TIMESTAMP = "database_creation_timestamp";
field public static final java.lang.String STATUS = "status";
field public static final int STATUS_BUSY = 1; // 0x1
+ field public static final android.net.Uri STATUS_CHANGE_NOTIFICATION_CONTENT_URI;
field public static final int STATUS_EMPTY = 2; // 0x2
field public static final int STATUS_NORMAL = 0; // 0x0
}
@@ -35410,7 +35476,6 @@
field public static final java.lang.String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
field public static final java.lang.String EXTRA_RECENT = "android.service.media.extra.RECENT";
field public static final java.lang.String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
- field public static final java.lang.String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
}
public class MediaBrowserService.Result<T> {
@@ -53120,6 +53185,133 @@
}
+package java.lang.invoke {
+
+ public class LambdaConversionException extends java.lang.Exception {
+ ctor public LambdaConversionException();
+ ctor public LambdaConversionException(java.lang.String);
+ ctor public LambdaConversionException(java.lang.String, java.lang.Throwable);
+ ctor public LambdaConversionException(java.lang.Throwable);
+ ctor public LambdaConversionException(java.lang.String, java.lang.Throwable, boolean, boolean);
+ }
+
+ public abstract class MethodHandle {
+ method public java.lang.invoke.MethodHandle asFixedArity();
+ method public java.lang.invoke.MethodHandle asType(java.lang.invoke.MethodType);
+ method public java.lang.invoke.MethodHandle asVarargsCollector(java.lang.Class<?>);
+ method public java.lang.invoke.MethodHandle bindTo(java.lang.Object);
+ method public final java.lang.Object invoke(java.lang.Object...) throws java.lang.Throwable;
+ method public final java.lang.Object invokeExact(java.lang.Object...) throws java.lang.Throwable;
+ method public java.lang.Object invokeWithArguments(java.util.List<?>) throws java.lang.Throwable;
+ method public boolean isVarargsCollector();
+ method public java.lang.invoke.MethodType type();
+ }
+
+ public abstract interface MethodHandleInfo {
+ method public abstract java.lang.Class<?> getDeclaringClass();
+ method public abstract java.lang.invoke.MethodType getMethodType();
+ method public abstract int getModifiers();
+ method public abstract java.lang.String getName();
+ method public abstract int getReferenceKind();
+ method public default boolean isVarArgs();
+ method public static boolean refKindIsField(int);
+ method public static boolean refKindIsValid(int);
+ method public static java.lang.String refKindName(int);
+ method public static java.lang.String referenceKindToString(int);
+ method public abstract <T extends java.lang.reflect.Member> T reflectAs(java.lang.Class<T>, java.lang.invoke.MethodHandles.Lookup);
+ method public static java.lang.String toString(int, java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType);
+ field public static final int REF_getField = 1; // 0x1
+ field public static final int REF_getStatic = 2; // 0x2
+ field public static final int REF_invokeInterface = 9; // 0x9
+ field public static final int REF_invokeSpecial = 7; // 0x7
+ field public static final int REF_invokeStatic = 6; // 0x6
+ field public static final int REF_invokeVirtual = 5; // 0x5
+ field public static final int REF_newInvokeSpecial = 8; // 0x8
+ field public static final int REF_putField = 3; // 0x3
+ field public static final int REF_putStatic = 4; // 0x4
+ }
+
+ public class MethodHandles {
+ method public static java.lang.invoke.MethodHandle arrayElementGetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
+ method public static java.lang.invoke.MethodHandle arrayElementSetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
+ method public static java.lang.invoke.MethodHandle catchException(java.lang.invoke.MethodHandle, java.lang.Class<? extends java.lang.Throwable>, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle constant(java.lang.Class<?>, java.lang.Object);
+ method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
+ method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle guardWithTest(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle identity(java.lang.Class<?>);
+ method public static java.lang.invoke.MethodHandle invoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandles.Lookup lookup();
+ method public static java.lang.invoke.MethodHandle permuteArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType, int...);
+ method public static java.lang.invoke.MethodHandles.Lookup publicLookup();
+ method public static java.lang.invoke.MethodHandle throwException(java.lang.Class<?>, java.lang.Class<? extends java.lang.Throwable>);
+ }
+
+ public static final class MethodHandles.Lookup {
+ method public java.lang.invoke.MethodHandle bind(java.lang.Object, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findConstructor(java.lang.Class<?>, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findGetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findSetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findSpecial(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findStatic(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findStaticGetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findStaticSetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findVirtual(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandles.Lookup in(java.lang.Class<?>);
+ method public java.lang.Class<?> lookupClass();
+ method public int lookupModes();
+ method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectSetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectSpecial(java.lang.reflect.Method, java.lang.Class<?>) throws java.lang.IllegalAccessException;
+ field public static final int PACKAGE = 8; // 0x8
+ field public static final int PRIVATE = 2; // 0x2
+ field public static final int PROTECTED = 4; // 0x4
+ field public static final int PUBLIC = 1; // 0x1
+ }
+
+ public final class MethodType implements java.io.Serializable {
+ method public java.lang.invoke.MethodType appendParameterTypes(java.lang.Class<?>...);
+ method public java.lang.invoke.MethodType appendParameterTypes(java.util.List<java.lang.Class<?>>);
+ method public java.lang.invoke.MethodType changeParameterType(int, java.lang.Class<?>);
+ method public java.lang.invoke.MethodType changeReturnType(java.lang.Class<?>);
+ method public java.lang.invoke.MethodType dropParameterTypes(int, int);
+ method public java.lang.invoke.MethodType erase();
+ method public static java.lang.invoke.MethodType fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) throws java.lang.IllegalArgumentException, java.lang.TypeNotPresentException;
+ method public java.lang.invoke.MethodType generic();
+ method public static java.lang.invoke.MethodType genericMethodType(int, boolean);
+ method public static java.lang.invoke.MethodType genericMethodType(int);
+ method public boolean hasPrimitives();
+ method public boolean hasWrappers();
+ method public java.lang.invoke.MethodType insertParameterTypes(int, java.lang.Class<?>...);
+ method public java.lang.invoke.MethodType insertParameterTypes(int, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>[]);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>, java.lang.Class<?>...);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.invoke.MethodType);
+ method public java.lang.Class<?>[] parameterArray();
+ method public int parameterCount();
+ method public java.util.List<java.lang.Class<?>> parameterList();
+ method public java.lang.Class<?> parameterType(int);
+ method public java.lang.Class<?> returnType();
+ method public java.lang.String toMethodDescriptorString();
+ method public java.lang.invoke.MethodType unwrap();
+ method public java.lang.invoke.MethodType wrap();
+ }
+
+ public class WrongMethodTypeException extends java.lang.RuntimeException {
+ ctor public WrongMethodTypeException();
+ ctor public WrongMethodTypeException(java.lang.String);
+ }
+
+}
+
package java.lang.ref {
public class PhantomReference<T> extends java.lang.ref.Reference {
diff --git a/api/system-current.txt b/api/system-current.txt
index 87df130..d5d84a0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1098,6 +1098,7 @@
field public static final int preferenceStyle = 16842894; // 0x101008e
field public static final int presentationTheme = 16843712; // 0x10103c0
field public static final int previewImage = 16843482; // 0x10102da
+ field public static final int primaryContentAlpha = 16843367; // 0x1010267
field public static final int priority = 16842780; // 0x101001c
field public static final int privateImeOptions = 16843299; // 0x1010223
field public static final int process = 16842769; // 0x1010011
@@ -4748,6 +4749,7 @@
public abstract class FragmentContainer {
ctor public FragmentContainer();
+ method public android.app.Fragment instantiate(android.content.Context, java.lang.String, android.os.Bundle);
method public abstract android.view.View onFindViewById(int);
method public abstract boolean onHasView();
}
@@ -5183,6 +5185,7 @@
field public static final int DEFAULT_LIGHTS = 4; // 0x4
field public static final int DEFAULT_SOUND = 1; // 0x1
field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
@@ -5268,6 +5271,7 @@
method public android.app.Notification.Action clone();
method public int describeContents();
method public boolean getAllowGeneratedReplies();
+ method public android.app.RemoteInput[] getDataOnlyRemoteInputs();
method public android.os.Bundle getExtras();
method public android.graphics.drawable.Icon getIcon();
method public android.app.RemoteInput[] getRemoteInputs();
@@ -5484,6 +5488,17 @@
field protected android.app.Notification.Builder mBuilder;
}
+ public static final class Notification.TvExtender implements android.app.Notification.Extender {
+ ctor public Notification.TvExtender();
+ ctor public Notification.TvExtender(android.app.Notification);
+ method public android.app.Notification.Builder extend(android.app.Notification.Builder);
+ method public android.app.PendingIntent getContentIntent();
+ method public android.app.PendingIntent getDeleteIntent();
+ method public boolean isAvailableOnTv();
+ method public android.app.Notification.TvExtender setContentIntent(android.app.PendingIntent);
+ method public android.app.Notification.TvExtender setDeleteIntent(android.app.PendingIntent);
+ }
+
public static final class Notification.WearableExtender implements android.app.Notification.Extender {
ctor public Notification.WearableExtender();
ctor public Notification.WearableExtender(android.app.Notification);
@@ -5747,14 +5762,18 @@
}
public final class RemoteInput implements android.os.Parcelable {
+ method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String, android.net.Uri>);
method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
method public int describeContents();
method public boolean getAllowFreeFormInput();
+ method public java.util.Set<java.lang.String> getAllowedDataTypes();
method public java.lang.CharSequence[] getChoices();
+ method public static java.util.Map<java.lang.String, android.net.Uri> getDataResultsFromIntent(android.content.Intent, java.lang.String);
method public android.os.Bundle getExtras();
method public java.lang.CharSequence getLabel();
method public java.lang.String getResultKey();
method public static android.os.Bundle getResultsFromIntent(android.content.Intent);
+ method public boolean isDataOnly();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RemoteInput> CREATOR;
field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
@@ -5766,6 +5785,7 @@
method public android.app.RemoteInput.Builder addExtras(android.os.Bundle);
method public android.app.RemoteInput build();
method public android.os.Bundle getExtras();
+ method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean);
method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]);
method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence);
@@ -9381,10 +9401,10 @@
field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER";
field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
- field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
- field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
- field public static final java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
- field public static final java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
field public static final java.lang.String EXTRA_SPLIT_NAME = "android.intent.extra.SPLIT_NAME";
field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
@@ -10204,6 +10224,8 @@
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
+ method public android.content.IntentSender getShortcutConfigActivityIntent(android.content.pm.LauncherActivityInfo);
+ method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(java.lang.String, android.os.UserHandle);
method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
method public boolean hasShortcutHostPermission();
@@ -10880,6 +10902,7 @@
public class ShortcutManager {
method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+ method public android.content.Intent createShortcutResultIntent(android.content.pm.ShortcutInfo);
method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.CharSequence);
method public void enableShortcuts(java.util.List<java.lang.String>);
@@ -12625,15 +12648,57 @@
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
method public static void RGBToHSV(int, int, int, float[]);
+ method public float alpha();
+ method public static float alpha(long);
method public static int alpha(int);
method public static int argb(int, int, int, int);
+ method public static int argb(float, float, float, float);
+ method public float blue();
+ method public static float blue(long);
method public static int blue(int);
+ method public static android.graphics.ColorSpace colorSpace(long);
method public static void colorToHSV(int, float[]);
+ method public android.graphics.Color convert(android.graphics.ColorSpace);
+ method public static long convert(int, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace.Connector);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace.Connector);
+ method public android.graphics.ColorSpace getColorSpace();
+ method public float getComponent(int);
+ method public int getComponentCount();
+ method public float[] getComponents();
+ method public android.graphics.ColorSpace.Model getModel();
+ method public float green();
+ method public static float green(long);
method public static int green(int);
+ method public static boolean isInColorSpace(long, android.graphics.ColorSpace);
+ method public boolean isSrgb();
+ method public static boolean isSrgb(long);
+ method public boolean isWideGamut();
+ method public static boolean isWideGamut(long);
+ method public float luminance();
+ method public static float luminance(long);
method public static float luminance(int);
+ method public long pack();
+ method public static long pack(int);
+ method public static long pack(float, float, float);
+ method public static long pack(float, float, float, float);
+ method public static long pack(float, float, float, float, android.graphics.ColorSpace);
method public static int parseColor(java.lang.String);
+ method public float red();
+ method public static float red(long);
method public static int red(int);
method public static int rgb(int, int, int);
+ method public static int rgb(float, float, float);
+ method public int toArgb();
+ method public static int toArgb(long);
+ method public static android.graphics.Color valueOf(int);
+ method public static android.graphics.Color valueOf(long);
+ method public static android.graphics.Color valueOf(float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float, android.graphics.ColorSpace);
+ method public static android.graphics.Color valueOf(float[], android.graphics.ColorSpace);
field public static final int BLACK = -16777216; // 0xff000000
field public static final int BLUE = -16776961; // 0xff0000ff
field public static final int CYAN = -16711681; // 0xff00ffff
@@ -12705,7 +12770,7 @@
field public static final float[] ILLUMINANT_D65;
field public static final float[] ILLUMINANT_D75;
field public static final float[] ILLUMINANT_E;
- field public static final int MAX_ID = 64; // 0x40
+ field public static final int MAX_ID = 63; // 0x3f
field public static final int MIN_ID = -1; // 0xffffffff
}
@@ -32456,6 +32521,15 @@
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
}
+ public abstract class ProxyFileDescriptorCallback {
+ ctor public ProxyFileDescriptorCallback();
+ method public void onFsync() throws android.system.ErrnoException;
+ method public long onGetSize() throws android.system.ErrnoException;
+ method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
+ method public abstract void onRelease();
+ method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
+ }
+
public class RecoverySystem {
method public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
@@ -32703,7 +32777,8 @@
method public android.os.UserHandle getUserForSerialNumber(long);
method public java.lang.String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
- method public int getUserRestrictionSource(java.lang.String, android.os.UserHandle);
+ method public deprecated int getUserRestrictionSource(java.lang.String, android.os.UserHandle);
+ method public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(java.lang.String, android.os.UserHandle);
method public android.os.Bundle getUserRestrictions();
method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean hasUserRestriction(java.lang.String);
@@ -32769,6 +32844,14 @@
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public static final class UserManager.EnforcingUser implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.UserHandle getUserHandle();
+ method public int getUserRestrictionSource();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.os.UserManager.EnforcingUser> CREATOR;
+ }
+
public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation {
}
@@ -32959,6 +33042,7 @@
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -35095,7 +35179,7 @@
ctor public ContactsContract.Intents();
field public static final java.lang.String ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS = "android.provider.action.VOICE_SEND_MESSAGE_TO_CONTACTS";
field public static final java.lang.String ATTACH_IMAGE = "com.android.contacts.action.ATTACH_IMAGE";
- field public static final java.lang.String CONTACTS_DATABASE_CREATED = "android.provider.Contacts.DATABASE_CREATED";
+ field public static final deprecated java.lang.String CONTACTS_DATABASE_CREATED = "android.provider.Contacts.DATABASE_CREATED";
field public static final java.lang.String EXTRA_CREATE_DESCRIPTION = "com.android.contacts.action.CREATE_DESCRIPTION";
field public static final java.lang.String EXTRA_FORCE_CREATE = "com.android.contacts.action.FORCE_CREATE";
field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID";
@@ -35235,8 +35319,10 @@
public static final class ContactsContract.ProviderStatus {
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/provider_status";
field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String DATABASE_CREATION_TIMESTAMP = "database_creation_timestamp";
field public static final java.lang.String STATUS = "status";
field public static final int STATUS_BUSY = 1; // 0x1
+ field public static final android.net.Uri STATUS_CHANGE_NOTIFICATION_CONTENT_URI;
field public static final int STATUS_EMPTY = 2; // 0x2
field public static final int STATUS_NORMAL = 0; // 0x0
}
@@ -38289,7 +38375,6 @@
field public static final java.lang.String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
field public static final java.lang.String EXTRA_RECENT = "android.service.media.extra.RECENT";
field public static final java.lang.String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
- field public static final java.lang.String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
}
public class MediaBrowserService.Result<T> {
@@ -56716,6 +56801,133 @@
}
+package java.lang.invoke {
+
+ public class LambdaConversionException extends java.lang.Exception {
+ ctor public LambdaConversionException();
+ ctor public LambdaConversionException(java.lang.String);
+ ctor public LambdaConversionException(java.lang.String, java.lang.Throwable);
+ ctor public LambdaConversionException(java.lang.Throwable);
+ ctor public LambdaConversionException(java.lang.String, java.lang.Throwable, boolean, boolean);
+ }
+
+ public abstract class MethodHandle {
+ method public java.lang.invoke.MethodHandle asFixedArity();
+ method public java.lang.invoke.MethodHandle asType(java.lang.invoke.MethodType);
+ method public java.lang.invoke.MethodHandle asVarargsCollector(java.lang.Class<?>);
+ method public java.lang.invoke.MethodHandle bindTo(java.lang.Object);
+ method public final java.lang.Object invoke(java.lang.Object...) throws java.lang.Throwable;
+ method public final java.lang.Object invokeExact(java.lang.Object...) throws java.lang.Throwable;
+ method public java.lang.Object invokeWithArguments(java.util.List<?>) throws java.lang.Throwable;
+ method public boolean isVarargsCollector();
+ method public java.lang.invoke.MethodType type();
+ }
+
+ public abstract interface MethodHandleInfo {
+ method public abstract java.lang.Class<?> getDeclaringClass();
+ method public abstract java.lang.invoke.MethodType getMethodType();
+ method public abstract int getModifiers();
+ method public abstract java.lang.String getName();
+ method public abstract int getReferenceKind();
+ method public default boolean isVarArgs();
+ method public static boolean refKindIsField(int);
+ method public static boolean refKindIsValid(int);
+ method public static java.lang.String refKindName(int);
+ method public static java.lang.String referenceKindToString(int);
+ method public abstract <T extends java.lang.reflect.Member> T reflectAs(java.lang.Class<T>, java.lang.invoke.MethodHandles.Lookup);
+ method public static java.lang.String toString(int, java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType);
+ field public static final int REF_getField = 1; // 0x1
+ field public static final int REF_getStatic = 2; // 0x2
+ field public static final int REF_invokeInterface = 9; // 0x9
+ field public static final int REF_invokeSpecial = 7; // 0x7
+ field public static final int REF_invokeStatic = 6; // 0x6
+ field public static final int REF_invokeVirtual = 5; // 0x5
+ field public static final int REF_newInvokeSpecial = 8; // 0x8
+ field public static final int REF_putField = 3; // 0x3
+ field public static final int REF_putStatic = 4; // 0x4
+ }
+
+ public class MethodHandles {
+ method public static java.lang.invoke.MethodHandle arrayElementGetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
+ method public static java.lang.invoke.MethodHandle arrayElementSetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
+ method public static java.lang.invoke.MethodHandle catchException(java.lang.invoke.MethodHandle, java.lang.Class<? extends java.lang.Throwable>, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle constant(java.lang.Class<?>, java.lang.Object);
+ method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
+ method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle guardWithTest(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle identity(java.lang.Class<?>);
+ method public static java.lang.invoke.MethodHandle invoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandles.Lookup lookup();
+ method public static java.lang.invoke.MethodHandle permuteArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType, int...);
+ method public static java.lang.invoke.MethodHandles.Lookup publicLookup();
+ method public static java.lang.invoke.MethodHandle throwException(java.lang.Class<?>, java.lang.Class<? extends java.lang.Throwable>);
+ }
+
+ public static final class MethodHandles.Lookup {
+ method public java.lang.invoke.MethodHandle bind(java.lang.Object, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findConstructor(java.lang.Class<?>, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findGetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findSetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findSpecial(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findStatic(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findStaticGetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findStaticSetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findVirtual(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandles.Lookup in(java.lang.Class<?>);
+ method public java.lang.Class<?> lookupClass();
+ method public int lookupModes();
+ method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectSetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectSpecial(java.lang.reflect.Method, java.lang.Class<?>) throws java.lang.IllegalAccessException;
+ field public static final int PACKAGE = 8; // 0x8
+ field public static final int PRIVATE = 2; // 0x2
+ field public static final int PROTECTED = 4; // 0x4
+ field public static final int PUBLIC = 1; // 0x1
+ }
+
+ public final class MethodType implements java.io.Serializable {
+ method public java.lang.invoke.MethodType appendParameterTypes(java.lang.Class<?>...);
+ method public java.lang.invoke.MethodType appendParameterTypes(java.util.List<java.lang.Class<?>>);
+ method public java.lang.invoke.MethodType changeParameterType(int, java.lang.Class<?>);
+ method public java.lang.invoke.MethodType changeReturnType(java.lang.Class<?>);
+ method public java.lang.invoke.MethodType dropParameterTypes(int, int);
+ method public java.lang.invoke.MethodType erase();
+ method public static java.lang.invoke.MethodType fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) throws java.lang.IllegalArgumentException, java.lang.TypeNotPresentException;
+ method public java.lang.invoke.MethodType generic();
+ method public static java.lang.invoke.MethodType genericMethodType(int, boolean);
+ method public static java.lang.invoke.MethodType genericMethodType(int);
+ method public boolean hasPrimitives();
+ method public boolean hasWrappers();
+ method public java.lang.invoke.MethodType insertParameterTypes(int, java.lang.Class<?>...);
+ method public java.lang.invoke.MethodType insertParameterTypes(int, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>[]);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>, java.lang.Class<?>...);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.invoke.MethodType);
+ method public java.lang.Class<?>[] parameterArray();
+ method public int parameterCount();
+ method public java.util.List<java.lang.Class<?>> parameterList();
+ method public java.lang.Class<?> parameterType(int);
+ method public java.lang.Class<?> returnType();
+ method public java.lang.String toMethodDescriptorString();
+ method public java.lang.invoke.MethodType unwrap();
+ method public java.lang.invoke.MethodType wrap();
+ }
+
+ public class WrongMethodTypeException extends java.lang.RuntimeException {
+ ctor public WrongMethodTypeException();
+ ctor public WrongMethodTypeException(java.lang.String);
+ }
+
+}
+
package java.lang.ref {
public class PhantomReference<T> extends java.lang.ref.Reference {
diff --git a/api/test-current.txt b/api/test-current.txt
index de86022..7933890 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -989,6 +989,7 @@
field public static final int preferenceStyle = 16842894; // 0x101008e
field public static final int presentationTheme = 16843712; // 0x10103c0
field public static final int previewImage = 16843482; // 0x10102da
+ field public static final int primaryContentAlpha = 16843367; // 0x1010267
field public static final int priority = 16842780; // 0x101001c
field public static final int privateImeOptions = 16843299; // 0x1010223
field public static final int process = 16842769; // 0x1010011
@@ -4601,6 +4602,7 @@
public abstract class FragmentContainer {
ctor public FragmentContainer();
+ method public android.app.Fragment instantiate(android.content.Context, java.lang.String, android.os.Bundle);
method public abstract android.view.View onFindViewById(int);
method public abstract boolean onHasView();
}
@@ -5035,6 +5037,7 @@
field public static final int DEFAULT_LIGHTS = 4; // 0x4
field public static final int DEFAULT_SOUND = 1; // 0x1
field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
@@ -5118,6 +5121,7 @@
method public android.app.Notification.Action clone();
method public int describeContents();
method public boolean getAllowGeneratedReplies();
+ method public android.app.RemoteInput[] getDataOnlyRemoteInputs();
method public android.os.Bundle getExtras();
method public android.graphics.drawable.Icon getIcon();
method public android.app.RemoteInput[] getRemoteInputs();
@@ -5584,14 +5588,18 @@
}
public final class RemoteInput implements android.os.Parcelable {
+ method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String, android.net.Uri>);
method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
method public int describeContents();
method public boolean getAllowFreeFormInput();
+ method public java.util.Set<java.lang.String> getAllowedDataTypes();
method public java.lang.CharSequence[] getChoices();
+ method public static java.util.Map<java.lang.String, android.net.Uri> getDataResultsFromIntent(android.content.Intent, java.lang.String);
method public android.os.Bundle getExtras();
method public java.lang.CharSequence getLabel();
method public java.lang.String getResultKey();
method public static android.os.Bundle getResultsFromIntent(android.content.Intent);
+ method public boolean isDataOnly();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RemoteInput> CREATOR;
field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
@@ -5603,6 +5611,7 @@
method public android.app.RemoteInput.Builder addExtras(android.os.Bundle);
method public android.app.RemoteInput build();
method public android.os.Bundle getExtras();
+ method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean);
method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]);
method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence);
@@ -9024,10 +9033,10 @@
field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER";
field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
- field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
- field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
- field public static final java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
- field public static final java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
+ field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
@@ -9815,6 +9824,8 @@
method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
+ method public android.content.IntentSender getShortcutConfigActivityIntent(android.content.pm.LauncherActivityInfo);
+ method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(java.lang.String, android.os.UserHandle);
method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int);
method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle);
method public boolean hasShortcutHostPermission();
@@ -10422,6 +10433,7 @@
public class ShortcutManager {
ctor public ShortcutManager(android.content.Context);
method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+ method public android.content.Intent createShortcutResultIntent(android.content.pm.ShortcutInfo);
method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.CharSequence);
method public void enableShortcuts(java.util.List<java.lang.String>);
@@ -12153,15 +12165,57 @@
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
method public static void RGBToHSV(int, int, int, float[]);
+ method public float alpha();
+ method public static float alpha(long);
method public static int alpha(int);
method public static int argb(int, int, int, int);
+ method public static int argb(float, float, float, float);
+ method public float blue();
+ method public static float blue(long);
method public static int blue(int);
+ method public static android.graphics.ColorSpace colorSpace(long);
method public static void colorToHSV(int, float[]);
+ method public android.graphics.Color convert(android.graphics.ColorSpace);
+ method public static long convert(int, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace, android.graphics.ColorSpace);
+ method public static long convert(long, android.graphics.ColorSpace.Connector);
+ method public static long convert(float, float, float, float, android.graphics.ColorSpace.Connector);
+ method public android.graphics.ColorSpace getColorSpace();
+ method public float getComponent(int);
+ method public int getComponentCount();
+ method public float[] getComponents();
+ method public android.graphics.ColorSpace.Model getModel();
+ method public float green();
+ method public static float green(long);
method public static int green(int);
+ method public static boolean isInColorSpace(long, android.graphics.ColorSpace);
+ method public boolean isSrgb();
+ method public static boolean isSrgb(long);
+ method public boolean isWideGamut();
+ method public static boolean isWideGamut(long);
+ method public float luminance();
+ method public static float luminance(long);
method public static float luminance(int);
+ method public long pack();
+ method public static long pack(int);
+ method public static long pack(float, float, float);
+ method public static long pack(float, float, float, float);
+ method public static long pack(float, float, float, float, android.graphics.ColorSpace);
method public static int parseColor(java.lang.String);
+ method public float red();
+ method public static float red(long);
method public static int red(int);
method public static int rgb(int, int, int);
+ method public static int rgb(float, float, float);
+ method public int toArgb();
+ method public static int toArgb(long);
+ method public static android.graphics.Color valueOf(int);
+ method public static android.graphics.Color valueOf(long);
+ method public static android.graphics.Color valueOf(float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float);
+ method public static android.graphics.Color valueOf(float, float, float, float, android.graphics.ColorSpace);
+ method public static android.graphics.Color valueOf(float[], android.graphics.ColorSpace);
field public static final int BLACK = -16777216; // 0xff000000
field public static final int BLUE = -16776961; // 0xff0000ff
field public static final int CYAN = -16711681; // 0xff00ffff
@@ -12233,7 +12287,7 @@
field public static final float[] ILLUMINANT_D65;
field public static final float[] ILLUMINANT_D75;
field public static final float[] ILLUMINANT_E;
- field public static final int MAX_ID = 64; // 0x40
+ field public static final int MAX_ID = 63; // 0x3f
field public static final int MIN_ID = -1; // 0xffffffff
}
@@ -29956,6 +30010,15 @@
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
}
+ public abstract class ProxyFileDescriptorCallback {
+ ctor public ProxyFileDescriptorCallback();
+ method public void onFsync() throws android.system.ErrnoException;
+ method public long onGetSize() throws android.system.ErrnoException;
+ method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
+ method public abstract void onRelease();
+ method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
+ }
+
public class RecoverySystem {
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
@@ -30374,6 +30437,7 @@
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -32474,7 +32538,7 @@
ctor public ContactsContract.Intents();
field public static final java.lang.String ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS = "android.provider.action.VOICE_SEND_MESSAGE_TO_CONTACTS";
field public static final java.lang.String ATTACH_IMAGE = "com.android.contacts.action.ATTACH_IMAGE";
- field public static final java.lang.String CONTACTS_DATABASE_CREATED = "android.provider.Contacts.DATABASE_CREATED";
+ field public static final deprecated java.lang.String CONTACTS_DATABASE_CREATED = "android.provider.Contacts.DATABASE_CREATED";
field public static final java.lang.String EXTRA_CREATE_DESCRIPTION = "com.android.contacts.action.CREATE_DESCRIPTION";
field public static final java.lang.String EXTRA_FORCE_CREATE = "com.android.contacts.action.FORCE_CREATE";
field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID";
@@ -32584,8 +32648,10 @@
public static final class ContactsContract.ProviderStatus {
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/provider_status";
field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String DATABASE_CREATION_TIMESTAMP = "database_creation_timestamp";
field public static final java.lang.String STATUS = "status";
field public static final int STATUS_BUSY = 1; // 0x1
+ field public static final android.net.Uri STATUS_CHANGE_NOTIFICATION_CONTENT_URI;
field public static final int STATUS_EMPTY = 2; // 0x2
field public static final int STATUS_NORMAL = 0; // 0x0
}
@@ -35529,7 +35595,6 @@
field public static final java.lang.String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
field public static final java.lang.String EXTRA_RECENT = "android.service.media.extra.RECENT";
field public static final java.lang.String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
- field public static final java.lang.String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
}
public class MediaBrowserService.Result<T> {
@@ -53430,6 +53495,133 @@
}
+package java.lang.invoke {
+
+ public class LambdaConversionException extends java.lang.Exception {
+ ctor public LambdaConversionException();
+ ctor public LambdaConversionException(java.lang.String);
+ ctor public LambdaConversionException(java.lang.String, java.lang.Throwable);
+ ctor public LambdaConversionException(java.lang.Throwable);
+ ctor public LambdaConversionException(java.lang.String, java.lang.Throwable, boolean, boolean);
+ }
+
+ public abstract class MethodHandle {
+ method public java.lang.invoke.MethodHandle asFixedArity();
+ method public java.lang.invoke.MethodHandle asType(java.lang.invoke.MethodType);
+ method public java.lang.invoke.MethodHandle asVarargsCollector(java.lang.Class<?>);
+ method public java.lang.invoke.MethodHandle bindTo(java.lang.Object);
+ method public final java.lang.Object invoke(java.lang.Object...) throws java.lang.Throwable;
+ method public final java.lang.Object invokeExact(java.lang.Object...) throws java.lang.Throwable;
+ method public java.lang.Object invokeWithArguments(java.util.List<?>) throws java.lang.Throwable;
+ method public boolean isVarargsCollector();
+ method public java.lang.invoke.MethodType type();
+ }
+
+ public abstract interface MethodHandleInfo {
+ method public abstract java.lang.Class<?> getDeclaringClass();
+ method public abstract java.lang.invoke.MethodType getMethodType();
+ method public abstract int getModifiers();
+ method public abstract java.lang.String getName();
+ method public abstract int getReferenceKind();
+ method public default boolean isVarArgs();
+ method public static boolean refKindIsField(int);
+ method public static boolean refKindIsValid(int);
+ method public static java.lang.String refKindName(int);
+ method public static java.lang.String referenceKindToString(int);
+ method public abstract <T extends java.lang.reflect.Member> T reflectAs(java.lang.Class<T>, java.lang.invoke.MethodHandles.Lookup);
+ method public static java.lang.String toString(int, java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType);
+ field public static final int REF_getField = 1; // 0x1
+ field public static final int REF_getStatic = 2; // 0x2
+ field public static final int REF_invokeInterface = 9; // 0x9
+ field public static final int REF_invokeSpecial = 7; // 0x7
+ field public static final int REF_invokeStatic = 6; // 0x6
+ field public static final int REF_invokeVirtual = 5; // 0x5
+ field public static final int REF_newInvokeSpecial = 8; // 0x8
+ field public static final int REF_putField = 3; // 0x3
+ field public static final int REF_putStatic = 4; // 0x4
+ }
+
+ public class MethodHandles {
+ method public static java.lang.invoke.MethodHandle arrayElementGetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
+ method public static java.lang.invoke.MethodHandle arrayElementSetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
+ method public static java.lang.invoke.MethodHandle catchException(java.lang.invoke.MethodHandle, java.lang.Class<? extends java.lang.Throwable>, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle constant(java.lang.Class<?>, java.lang.Object);
+ method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
+ method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle guardWithTest(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
+ method public static java.lang.invoke.MethodHandle identity(java.lang.Class<?>);
+ method public static java.lang.invoke.MethodHandle invoker(java.lang.invoke.MethodType);
+ method public static java.lang.invoke.MethodHandles.Lookup lookup();
+ method public static java.lang.invoke.MethodHandle permuteArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType, int...);
+ method public static java.lang.invoke.MethodHandles.Lookup publicLookup();
+ method public static java.lang.invoke.MethodHandle throwException(java.lang.Class<?>, java.lang.Class<? extends java.lang.Throwable>);
+ }
+
+ public static final class MethodHandles.Lookup {
+ method public java.lang.invoke.MethodHandle bind(java.lang.Object, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findConstructor(java.lang.Class<?>, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findGetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findSetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findSpecial(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findStatic(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandle findStaticGetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findStaticSetter(java.lang.Class<?>, java.lang.String, java.lang.Class<?>) throws java.lang.IllegalAccessException, java.lang.NoSuchFieldException;
+ method public java.lang.invoke.MethodHandle findVirtual(java.lang.Class<?>, java.lang.String, java.lang.invoke.MethodType) throws java.lang.IllegalAccessException, java.lang.NoSuchMethodException;
+ method public java.lang.invoke.MethodHandles.Lookup in(java.lang.Class<?>);
+ method public java.lang.Class<?> lookupClass();
+ method public int lookupModes();
+ method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectSetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
+ method public java.lang.invoke.MethodHandle unreflectSpecial(java.lang.reflect.Method, java.lang.Class<?>) throws java.lang.IllegalAccessException;
+ field public static final int PACKAGE = 8; // 0x8
+ field public static final int PRIVATE = 2; // 0x2
+ field public static final int PROTECTED = 4; // 0x4
+ field public static final int PUBLIC = 1; // 0x1
+ }
+
+ public final class MethodType implements java.io.Serializable {
+ method public java.lang.invoke.MethodType appendParameterTypes(java.lang.Class<?>...);
+ method public java.lang.invoke.MethodType appendParameterTypes(java.util.List<java.lang.Class<?>>);
+ method public java.lang.invoke.MethodType changeParameterType(int, java.lang.Class<?>);
+ method public java.lang.invoke.MethodType changeReturnType(java.lang.Class<?>);
+ method public java.lang.invoke.MethodType dropParameterTypes(int, int);
+ method public java.lang.invoke.MethodType erase();
+ method public static java.lang.invoke.MethodType fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) throws java.lang.IllegalArgumentException, java.lang.TypeNotPresentException;
+ method public java.lang.invoke.MethodType generic();
+ method public static java.lang.invoke.MethodType genericMethodType(int, boolean);
+ method public static java.lang.invoke.MethodType genericMethodType(int);
+ method public boolean hasPrimitives();
+ method public boolean hasWrappers();
+ method public java.lang.invoke.MethodType insertParameterTypes(int, java.lang.Class<?>...);
+ method public java.lang.invoke.MethodType insertParameterTypes(int, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>[]);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.util.List<java.lang.Class<?>>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>, java.lang.Class<?>...);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.Class<?>);
+ method public static java.lang.invoke.MethodType methodType(java.lang.Class<?>, java.lang.invoke.MethodType);
+ method public java.lang.Class<?>[] parameterArray();
+ method public int parameterCount();
+ method public java.util.List<java.lang.Class<?>> parameterList();
+ method public java.lang.Class<?> parameterType(int);
+ method public java.lang.Class<?> returnType();
+ method public java.lang.String toMethodDescriptorString();
+ method public java.lang.invoke.MethodType unwrap();
+ method public java.lang.invoke.MethodType wrap();
+ }
+
+ public class WrongMethodTypeException extends java.lang.RuntimeException {
+ ctor public WrongMethodTypeException();
+ ctor public WrongMethodTypeException(java.lang.String);
+ }
+
+}
+
package java.lang.ref {
public class PhantomReference<T> extends java.lang.ref.Reference {
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index d5580ac..0ea141c 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -184,10 +184,6 @@
int main(int argc, char* const argv[])
{
- if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
- LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
- }
-
if (!LOG_NDEBUG) {
String8 argv_String;
for (int i = 0; i < argc; ++i) {
diff --git a/cmds/svc/src/com/android/commands/svc/NfcCommand.java b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
index 8e9791f..02a92b9 100644
--- a/cmds/svc/src/com/android/commands/svc/NfcCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
@@ -58,7 +58,8 @@
IPackageManager pm = IPackageManager.Stub.asInterface(
ServiceManager.getService("package"));
try {
- if (pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0)) {
+ if (pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0) ||
+ pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
INfcAdapter nfc = INfcAdapter.Stub
.asInterface(ServiceManager.getService(Context.NFC_SERVICE));
try {
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 07a8253..b76aeb7 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -414,9 +414,9 @@
public int flags;
/**
- * The unique string Id to identify the accessibility service.
+ * The component name the accessibility service.
*/
- private String mId;
+ private ComponentName mComponentName;
/**
* The Service that implements this accessibility service component.
@@ -464,7 +464,7 @@
public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
throws XmlPullParserException, IOException {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
- mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString();
+ mComponentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
mResolveInfo = resolveInfo;
XmlResourceParser parser = null;
@@ -574,7 +574,14 @@
* @hide
*/
public void setComponentName(ComponentName component) {
- mId = component.flattenToShortString();
+ mComponentName = component;
+ }
+
+ /**
+ * @hide
+ */
+ public ComponentName getComponentName() {
+ return mComponentName;
}
/**
@@ -585,7 +592,7 @@
* @return The id.
*/
public String getId() {
- return mId;
+ return mComponentName.flattenToShortString();
}
/**
@@ -715,7 +722,7 @@
parcel.writeInt(feedbackType);
parcel.writeLong(notificationTimeout);
parcel.writeInt(flags);
- parcel.writeString(mId);
+ parcel.writeParcelable(mComponentName, flagz);
parcel.writeParcelable(mResolveInfo, 0);
parcel.writeString(mSettingsActivityName);
parcel.writeInt(mCapabilities);
@@ -729,7 +736,7 @@
feedbackType = parcel.readInt();
notificationTimeout = parcel.readLong();
flags = parcel.readInt();
- mId = parcel.readString();
+ mComponentName = parcel.readParcelable(this.getClass().getClassLoader());
mResolveInfo = parcel.readParcelable(null);
mSettingsActivityName = parcel.readString();
mCapabilities = parcel.readInt();
@@ -739,7 +746,7 @@
@Override
public int hashCode() {
- return 31 * 1 + ((mId == null) ? 0 : mId.hashCode());
+ return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode());
}
@Override
@@ -754,11 +761,11 @@
return false;
}
AccessibilityServiceInfo other = (AccessibilityServiceInfo) obj;
- if (mId == null) {
- if (other.mId != null) {
+ if (mComponentName == null) {
+ if (other.mComponentName != null) {
return false;
}
- } else if (!mId.equals(other.mId)) {
+ } else if (!mComponentName.equals(other.mComponentName)) {
return false;
}
return true;
@@ -777,7 +784,7 @@
stringBuilder.append(", ");
appendFlags(stringBuilder, flags);
stringBuilder.append(", ");
- stringBuilder.append("id: ").append(mId);
+ stringBuilder.append("id: ").append(getId());
stringBuilder.append(", ");
stringBuilder.append("resolveInfo: ").append(mResolveInfo);
stringBuilder.append(", ");
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f1f6e7b..2e3c100 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2176,6 +2176,12 @@
dest.writeParcelable(mContentInsets, 0);
}
+ @Override
+ public String toString() {
+ return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation
+ + " mContentInsets=" + mContentInsets.toShortString();
+ }
+
public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
public TaskSnapshot createFromParcel(Parcel source) {
return new TaskSnapshot(source);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 10ab2bc..62d6898 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -102,15 +102,19 @@
mSavedFragmentState = in.readBundle();
}
- public Fragment instantiate(FragmentHostCallback host, Fragment parent,
- FragmentManagerNonConfig childNonConfig) {
+ public Fragment instantiate(FragmentHostCallback host, FragmentContainer container,
+ Fragment parent, FragmentManagerNonConfig childNonConfig) {
if (mInstance == null) {
final Context context = host.getContext();
if (mArguments != null) {
mArguments.setClassLoader(context.getClassLoader());
}
- mInstance = Fragment.instantiate(context, mClassName, mArguments);
+ if (container != null) {
+ mInstance = container.instantiate(context, mClassName, mArguments);
+ } else {
+ mInstance = Fragment.instantiate(context, mClassName, mArguments);
+ }
if (mSavedFragmentState != null) {
mSavedFragmentState.setClassLoader(context.getClassLoader());
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index b2e0300..6ed54dc 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -18,6 +18,8 @@
import android.annotation.IdRes;
import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
import android.view.View;
/**
@@ -35,4 +37,13 @@
* Return {@code true} if the container holds any view.
*/
public abstract boolean onHasView();
+
+ /**
+ * Creates an instance of the specified fragment, can be overridden to construct fragments
+ * with dependencies, or change the fragment being constructed. By default just calls
+ * {@link Fragment#instantiate(Context, String, Bundle)}.
+ */
+ public Fragment instantiate(Context context, String className, Bundle arguments) {
+ return Fragment.instantiate(context, className, arguments);
+ }
}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index efd2b69..44f1322 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1984,11 +1984,13 @@
if (startIndex != recordNum) {
executeOpsTogether(records, isRecordPop, startIndex, recordNum);
}
- // execute all unoptimized together
- int optimizeEnd;
- for (optimizeEnd = recordNum + 1; optimizeEnd < numRecords; optimizeEnd++) {
- if (records.get(optimizeEnd).mAllowOptimization) {
- break;
+ // execute all unoptimized pop operations together or one add operation
+ int optimizeEnd = recordNum + 1;
+ if (isRecordPop.get(recordNum)) {
+ while (optimizeEnd < numRecords
+ && isRecordPop.get(optimizeEnd)
+ && !records.get(optimizeEnd).mAllowOptimization) {
+ optimizeEnd++;
}
}
executeOpsTogether(records, isRecordPop, recordNum, optimizeEnd);
@@ -2651,7 +2653,7 @@
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
- Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
+ Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.add(f);
// Now that the fragment is instantiated (or came from being
@@ -3270,7 +3272,7 @@
+ Integer.toHexString(id) + " fname=" + fname
+ " existing=" + fragment);
if (fragment == null) {
- fragment = Fragment.instantiate(context, fname);
+ fragment = mContainer.instantiate(context, fname, null);
fragment.mFromLayout = true;
fragment.mFragmentId = id != 0 ? id : containerId;
fragment.mContainerId = containerId;
diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java
index 6d57cd4..80a5aac 100644
--- a/core/java/android/app/FragmentTransition.java
+++ b/core/java/android/app/FragmentTransition.java
@@ -188,7 +188,10 @@
private static void configureTransitionsOptimized(FragmentManagerImpl fragmentManager,
int containerId, FragmentContainerTransition fragments,
View nonExistentView, ArrayMap<String, String> nameOverrides) {
- ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ ViewGroup sceneRoot = null;
+ if (fragmentManager.mContainer.onHasView()) {
+ sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ }
if (sceneRoot == null) {
return;
}
@@ -257,7 +260,10 @@
private static void configureTransitionsUnoptimized(FragmentManagerImpl fragmentManager,
int containerId, FragmentContainerTransition fragments,
View nonExistentView, ArrayMap<String, String> nameOverrides) {
- ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ ViewGroup sceneRoot = null;
+ if (fragmentManager.mContainer.onHasView()) {
+ sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ }
if (sceneRoot == null) {
return;
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 21ae853..5824c32 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -31,7 +31,6 @@
import android.app.ITaskStackListener;
import android.app.IUiAutomationConnection;
import android.app.IUidObserver;
-
import android.app.IUserSwitchObserver;
import android.app.Notification;
import android.app.PendingIntent;
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index ef997c9..6deedb6 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -102,4 +102,9 @@
* been locked.
*/
void onTaskProfileLocked(int taskId, int userId);
+
+ /**
+ * Called when a task snapshot got updated.
+ */
+ void onTaskSnapshotChanged(int taskId, in ActivityManager.TaskSnapshot snapshot);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index da57873..ab0b68d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -988,6 +988,32 @@
*/
public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
+ /**
+ * {@link #extras} key: the audio contents of this notification.
+ *
+ * This is for use when rendering the notification on an audio-focused interface;
+ * the audio contents are a complete sound sample that contains the contents/body of the
+ * notification. This may be used in substitute of a Text-to-Speech reading of the
+ * notification. For example if the notification represents a voice message this should point
+ * to the audio of that message.
+ *
+ * The data stored under this key should be a String representation of a Uri that contains the
+ * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB.
+ *
+ * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message}
+ * has a field for holding data URI. That field can be used for audio.
+ * See {@code Message#setData}.
+ *
+ * Example usage:
+ * <pre>
+ * {@code
+ * Notification.Builder myBuilder = (build your Notification as normal);
+ * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
+ * }
+ * </pre>
+ */
+ public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
+
/** @hide */
@SystemApi
public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
@@ -1007,6 +1033,21 @@
* to attach actions.
*/
public static class Action implements Parcelable {
+ /**
+ * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of
+ * {@link RemoteInput}s.
+ *
+ * This is intended for {@link RemoteInput}s that only accept data, meaning
+ * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices}
+ * is null or empty, and {@link RemoteInput#getAllowedDataTypes is non-null and not
+ * empty. These {@link RemoteInput}s will be ignored by devices that do not
+ * support non-text-based {@link RemoteInput}s. See {@link Builder#build}.
+ *
+ * You can test if a RemoteInput matches these constraints using
+ * {@link RemoteInput#isDataOnly}.
+ */
+ private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
+
private final Bundle mExtras;
private Icon mIcon;
private final RemoteInput[] mRemoteInputs;
@@ -1097,13 +1138,28 @@
/**
* Get the list of inputs to be collected from the user when this action is sent.
- * May return null if no remote inputs were added.
+ * May return null if no remote inputs were added. Only returns inputs which accept
+ * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}.
*/
public RemoteInput[] getRemoteInputs() {
return mRemoteInputs;
}
/**
+ * Get the list of inputs to be collected from the user that ONLY accept data when this
+ * action is sent. These remote inputs are guaranteed to return true on a call to
+ * {@link RemoteInput#isDataOnly}.
+ *
+ * May return null if no data-only remote inputs were added.
+ *
+ * This method exists so that legacy RemoteInput collectors that pre-date the addition
+ * of non-textual RemoteInputs do not access these remote inputs.
+ */
+ public RemoteInput[] getDataOnlyRemoteInputs() {
+ return (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS);
+ }
+
+ /**
* Builder class for {@link Action} objects.
*/
public static final class Builder {
@@ -1226,9 +1282,32 @@
* @return the built action
*/
public Action build() {
- RemoteInput[] remoteInputs = mRemoteInputs != null
- ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null;
- return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs,
+ ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
+ RemoteInput[] previousDataInputs =
+ (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS);
+ if (previousDataInputs == null) {
+ for (RemoteInput input : previousDataInputs) {
+ dataOnlyInputs.add(input);
+ }
+ }
+ List<RemoteInput> textInputs = new ArrayList<>();
+ if (mRemoteInputs != null) {
+ for (RemoteInput input : mRemoteInputs) {
+ if (input.isDataOnly()) {
+ dataOnlyInputs.add(input);
+ } else {
+ textInputs.add(input);
+ }
+ }
+ }
+ if (!dataOnlyInputs.isEmpty()) {
+ RemoteInput[] dataInputsArr =
+ dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]);
+ mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr);
+ }
+ RemoteInput[] textInputsArr = textInputs.isEmpty()
+ ? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
+ return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
mAllowGeneratedReplies);
}
}
@@ -1521,7 +1600,7 @@
/**
* Get a hint that this Action should be displayed inline.
*
- * @return {@code true} if the Action should be displayed inline, {@code false}
+ * @return {@code true} if the Action should be displayed inline, {@code false}
* otherwise. The default value is {@code false} if this was never set.
*/
public boolean getHintDisplayActionInline() {
@@ -6975,6 +7054,142 @@
}
/**
+ * <p>Helper class to add Android TV extensions to notifications. To create a notification
+ * with a TV extension:
+ *
+ * <ol>
+ * <li>Create an {@link Notification.Builder}, setting any desired properties.
+ * <li>Create a {@link TvExtender}.
+ * <li>Set TV-specific properties using the {@code set} methods of
+ * {@link TvExtender}.
+ * <li>Call {@link Notification.Builder#extend(Notification.Extender)}
+ * to apply the extension to a notification.
+ * </ol>
+ *
+ * <pre class="prettyprint">
+ * Notification notification = new Notification.Builder(context)
+ * ...
+ * .extend(new TvExtender()
+ * .set*(...))
+ * .build();
+ * </pre>
+ *
+ * <p>TV extensions can be accessed on an existing notification by using the
+ * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
+ * to access values.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class TvExtender implements Extender {
+ private static final String TAG = "TvExtender";
+
+ private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS";
+ private static final String EXTRA_FLAGS = "flags";
+ private static final String EXTRA_CONTENT_INTENT = "content_intent";
+ private static final String EXTRA_DELETE_INTENT = "delete_intent";
+
+ // Flags bitwise-ored to mFlags
+ private static final int FLAG_AVAILABLE_ON_TV = 0x1;
+
+ private int mFlags;
+ private PendingIntent mContentIntent;
+ private PendingIntent mDeleteIntent;
+
+ /**
+ * Create a {@link TvExtender} with default options.
+ */
+ public TvExtender() {
+ mFlags = FLAG_AVAILABLE_ON_TV;
+ }
+
+ /**
+ * Create a {@link TvExtender} from the TvExtender options of an existing Notification.
+ *
+ * @param notif The notification from which to copy options.
+ */
+ public TvExtender(Notification notif) {
+ Bundle bundle = notif.extras == null ?
+ null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
+ if (bundle != null) {
+ mFlags = bundle.getInt(EXTRA_FLAGS);
+ mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
+ mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
+ }
+ }
+
+ /**
+ * Apply a TV extension to a notification that is being built. This is typically called by
+ * the {@link Notification.Builder#extend(Notification.Extender)}
+ * method of {@link Notification.Builder}.
+ */
+ @Override
+ public Notification.Builder extend(Notification.Builder builder) {
+ Bundle bundle = new Bundle();
+
+ bundle.putInt(EXTRA_FLAGS, mFlags);
+ if (mContentIntent != null) {
+ bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
+ }
+
+ if (mDeleteIntent != null) {
+ bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent);
+ }
+
+ builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle);
+ return builder;
+ }
+
+ /**
+ * Returns true if this notification should be shown on TV. This method return true
+ * if the notification was extended with a TvExtender.
+ */
+ public boolean isAvailableOnTv() {
+ return (mFlags & FLAG_AVAILABLE_ON_TV) != 0;
+ }
+
+ /**
+ * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
+ * If provided, it is used instead of the content intent specified
+ * at the level of Notification.
+ */
+ public TvExtender setContentIntent(PendingIntent intent) {
+ mContentIntent = intent;
+ return this;
+ }
+
+ /**
+ * Returns the TV-specific content intent. If this method returns null, the
+ * main content intent on the notification should be used.
+ *
+ * @see {@link Notification#contentIntent}
+ */
+ public PendingIntent getContentIntent() {
+ return mContentIntent;
+ }
+
+ /**
+ * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
+ * by the user on TV. If provided, it is used instead of the delete intent specified
+ * at the level of Notification.
+ */
+ public TvExtender setDeleteIntent(PendingIntent intent) {
+ mDeleteIntent = intent;
+ return this;
+ }
+
+ /**
+ * Returns the TV-specific delete intent. If this method returns null, the
+ * main delete intent on the notification should be used.
+ *
+ * @see {@link Notification#deleteIntent}
+ */
+ public PendingIntent getDeleteIntent() {
+ return mDeleteIntent;
+ }
+ }
+
+ /**
* Get an array of Notification objects from a parcelable array bundle field.
* Update the bundle to have a typed array so fetches in the future don't need
* to do an array copy.
diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java
index 11420c5..d1dc859 100644
--- a/core/java/android/app/RemoteInput.java
+++ b/core/java/android/app/RemoteInput.java
@@ -19,9 +19,14 @@
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
/**
* A {@code RemoteInput} object specifies input to be collected from a user to be passed along with
@@ -61,9 +66,13 @@
/** Label used to denote the clip data type used for remote input transport */
public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
- /** Extra added to a clip data intent object to hold the results bundle. */
+ /** Extra added to a clip data intent object to hold the text results bundle. */
public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
+ /** Extra added to a clip data intent object to hold the data results bundle. */
+ private static final String EXTRA_DATA_TYPE_RESULTS_DATA =
+ "android.remoteinput.dataTypeResultsData";
+
// Flags bitwise-ored to mFlags
private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1;
@@ -75,14 +84,16 @@
private final CharSequence[] mChoices;
private final int mFlags;
private final Bundle mExtras;
+ private final ArraySet<String> mAllowedDataTypes;
private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
- int flags, Bundle extras) {
+ int flags, Bundle extras, ArraySet<String> allowedDataTypes) {
this.mResultKey = resultKey;
this.mLabel = label;
this.mChoices = choices;
this.mFlags = flags;
this.mExtras = extras;
+ this.mAllowedDataTypes = allowedDataTypes;
}
/**
@@ -107,6 +118,21 @@
return mChoices;
}
+ public Set<String> getAllowedDataTypes() {
+ return mAllowedDataTypes;
+ }
+
+ /**
+ * Returns true if the input only accepts data, meaning {@link #getAllowFreeFormInput}
+ * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes is
+ * non-null and not empty.
+ */
+ public boolean isDataOnly() {
+ return !getAllowFreeFormInput()
+ && (getChoices() == null || getChoices().length == 0)
+ && !getAllowedDataTypes().isEmpty();
+ }
+
/**
* Get whether or not users can provide an arbitrary value for
* input. If you set this to {@code false}, users must select one of the
@@ -133,6 +159,7 @@
private CharSequence[] mChoices;
private int mFlags = DEFAULT_FLAGS;
private Bundle mExtras = new Bundle();
+ private final ArraySet<String> mAllowedDataTypes = new ArraySet<>();
/**
* Create a builder object for {@link RemoteInput} objects.
@@ -177,14 +204,34 @@
/**
* Specifies whether the user can provide arbitrary values.
*
- * @param allowFreeFormInput The default is {@code true}.
- * If you specify {@code false}, you must provide a non-null
- * and non-empty array to {@link #setChoices} or an
+ * @param mimeType A mime type that results are allowed to come in.
+ * Be aware that text results (see {@link #setAllowFreeFormInput}
+ * are allowed by default. If you do not want text results you will have to
+ * pass false to {@code setAllowFreeFormInput}.
+ * @param doAllow Whether the mime type should be allowed or not.
+ * @return this object for method chaining
+ */
+ public Builder setAllowDataType(String mimeType, boolean doAllow) {
+ if (doAllow) {
+ mAllowedDataTypes.add(mimeType);
+ } else {
+ mAllowedDataTypes.remove(mimeType);
+ }
+ return this;
+ }
+
+ /**
+ * Specifies whether the user can provide arbitrary text values.
+ *
+ * @param allowFreeFormTextInput The default is {@code true}.
+ * If you specify {@code false}, you must either provide a non-null
+ * and non-empty array to {@link #setChoices}, or enable a data result
+ * in {@code setAllowDataType}. Otherwise an
* {@link IllegalArgumentException} is thrown.
* @return this object for method chaining
*/
- public Builder setAllowFreeFormInput(boolean allowFreeFormInput) {
- setFlag(mFlags, allowFreeFormInput);
+ public Builder setAllowFreeFormInput(boolean allowFreeFormTextInput) {
+ setFlag(mFlags, allowFreeFormTextInput);
return this;
}
@@ -224,7 +271,8 @@
* object.
*/
public RemoteInput build() {
- return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mExtras);
+ return new RemoteInput(
+ mResultKey, mLabel, mChoices, mFlags, mExtras, mAllowedDataTypes);
}
}
@@ -234,32 +282,68 @@
mChoices = in.readCharSequenceArray();
mFlags = in.readInt();
mExtras = in.readBundle();
+ mAllowedDataTypes = (ArraySet<String>) in.readArraySet(null);
}
/**
- * Get the remote input results bundle from an intent. The returned Bundle will
- * contain a key/value for every result key populated by remote input collector.
- * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value.
+ * Similar as {@link #getResultsFromIntent} but retrieves data results for a
+ * specific RemoteInput result. To retrieve a value use:
+ * <pre>
+ * {@code
+ * Map<String, Uri> results =
+ * RemoteInput.getDataResultsFromIntent(intent, REMOTE_INPUT_KEY);
+ * if (results != null) {
+ * Uri data = results.get(MIME_TYPE_OF_INTEREST);
+ * }
+ * }
+ * </pre>
+ * @param intent The intent object that fired in response to an action or content intent
+ * which also had one or more remote input requested.
+ * @param remoteInputResultKey The result key for the RemoteInput you want results for.
+ */
+ public static Map<String, Uri> getDataResultsFromIntent(
+ Intent intent, String remoteInputResultKey) {
+ Intent clipDataIntent = getClipDataIntentFromIntent(intent);
+ if (clipDataIntent == null) {
+ return null;
+ }
+ Map<String, Uri> results = new HashMap<>();
+ Bundle extras = clipDataIntent.getExtras();
+ for (String key : extras.keySet()) {
+ if (key.startsWith(EXTRA_DATA_TYPE_RESULTS_DATA)) {
+ String mimeType = key.substring(EXTRA_DATA_TYPE_RESULTS_DATA.length());
+ if (mimeType == null || mimeType.isEmpty()) {
+ continue;
+ }
+ Bundle bundle = clipDataIntent.getBundleExtra(key);
+ String uriStr = bundle.getString(remoteInputResultKey);
+ if (uriStr == null || uriStr.isEmpty()) {
+ continue;
+ }
+ results.put(mimeType, Uri.parse(uriStr));
+ }
+ }
+ return results.isEmpty() ? null : results;
+ }
+
+ /**
+ * Get the remote input text results bundle from an intent. The returned Bundle will
+ * contain a key/value for every result key populated with text by remote input collector.
+ * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value. For non-text
+ * results use {@link #getDataResultsFromIntent}.
* @param intent The intent object that fired in response to an action or content intent
* which also had one or more remote input requested.
*/
public static Bundle getResultsFromIntent(Intent intent) {
- ClipData clipData = intent.getClipData();
- if (clipData == null) {
+ Intent clipDataIntent = getClipDataIntentFromIntent(intent);
+ if (clipDataIntent == null) {
return null;
}
- ClipDescription clipDescription = clipData.getDescription();
- if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
- return null;
- }
- if (clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) {
- return clipData.getItemAt(0).getIntent().getExtras().getParcelable(EXTRA_RESULTS_DATA);
- }
- return null;
+ return clipDataIntent.getExtras().getParcelable(EXTRA_RESULTS_DATA);
}
/**
- * Populate an intent object with the results gathered from remote input. This method
+ * Populate an intent object with the text results gathered from remote input. This method
* should only be called by remote input collection services when sending results to a
* pending intent.
* @param remoteInputs The remote inputs for which results are being provided
@@ -267,20 +351,61 @@
* field of the intent will be modified to contain the results.
* @param results A bundle holding the remote input results. This bundle should
* be populated with keys matching the result keys specified in
- * {@code remoteInputs} with values being the result per key.
+ * {@code remoteInputs} with values being the CharSequence results per key.
*/
public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent,
Bundle results) {
- Bundle resultsBundle = new Bundle();
+ Intent clipDataIntent = getClipDataIntentFromIntent(intent);
+ if (clipDataIntent == null) {
+ clipDataIntent = new Intent(); // First time we've added a result.
+ }
+ Bundle resultsBundle = clipDataIntent.getBundleExtra(EXTRA_RESULTS_DATA);
+ if (resultsBundle == null) {
+ resultsBundle = new Bundle();
+ }
for (RemoteInput remoteInput : remoteInputs) {
Object result = results.get(remoteInput.getResultKey());
if (result instanceof CharSequence) {
resultsBundle.putCharSequence(remoteInput.getResultKey(), (CharSequence) result);
}
}
- Intent clipIntent = new Intent();
- clipIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle);
- intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipIntent));
+ clipDataIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle);
+ intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent));
+ }
+
+ /**
+ * Same as {@link #addResultsToIntent} but for setting data results.
+ * @param remoteInput The remote input for which results are being provided
+ * @param intent The intent to add remote input results to. The {@link ClipData}
+ * field of the intent will be modified to contain the results.
+ * @param results A map of mime type to the Uri result for that mime type.
+ */
+ public static void addDataResultToIntent(RemoteInput remoteInput, Intent intent,
+ Map<String, Uri> results) {
+ Intent clipDataIntent = getClipDataIntentFromIntent(intent);
+ if (clipDataIntent == null) {
+ clipDataIntent = new Intent(); // First time we've added a result.
+ }
+ for (Map.Entry<String, Uri> entry : results.entrySet()) {
+ String mimeType = entry.getKey();
+ Uri uri = entry.getValue();
+ if (mimeType == null) {
+ continue;
+ }
+ Bundle resultsBundle =
+ clipDataIntent.getBundleExtra(getExtraResultsKeyForData(mimeType));
+ if (resultsBundle == null) {
+ resultsBundle = new Bundle();
+ }
+ resultsBundle.putString(remoteInput.getResultKey(), uri.toString());
+
+ clipDataIntent.putExtra(getExtraResultsKeyForData(mimeType), resultsBundle);
+ }
+ intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent));
+ }
+
+ private static String getExtraResultsKeyForData(String mimeType) {
+ return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType;
}
@Override
@@ -295,6 +420,7 @@
out.writeCharSequenceArray(mChoices);
out.writeInt(mFlags);
out.writeBundle(mExtras);
+ out.writeArraySet(mAllowedDataTypes);
}
public static final Creator<RemoteInput> CREATOR = new Creator<RemoteInput>() {
@@ -308,4 +434,19 @@
return new RemoteInput[size];
}
};
+
+ private static Intent getClipDataIntentFromIntent(Intent intent) {
+ ClipData clipData = intent.getClipData();
+ if (clipData == null) {
+ return null;
+ }
+ ClipDescription clipDescription = clipData.getDescription();
+ if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
+ return null;
+ }
+ if (!clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) {
+ return null;
+ }
+ return clipData.getItemAt(0).getIntent();
+ }
}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index ad5e69b..fd766bf 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -16,6 +16,7 @@
package android.app;
+import android.app.ActivityManager.TaskSnapshot;
import android.content.ComponentName;
import android.os.RemoteException;
@@ -78,4 +79,9 @@
@Override
public void onTaskProfileLocked(int taskId, int userId) {
}
+
+ @Override
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
+ throws RemoteException {
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c95e011..b8b9793 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6317,7 +6317,7 @@
* @see #setPermissionGrantState(ComponentName, String, String, int)
* @see PackageManager#checkPermission(String, String)
*/
- public int getPermissionGrantState(@NonNull ComponentName admin, String packageName,
+ public int getPermissionGrantState(@Nullable ComponentName admin, String packageName,
String permission) {
throwIfParentInstance("getPermissionGrantState");
try {
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 4b6076b..e437de0 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -653,6 +653,13 @@
return mBase.bindServiceAsUser(service, conn, flags, user);
}
+ /** @hide */
+ @Override
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ Handler handler, UserHandle user) {
+ return mBase.bindServiceAsUser(service, conn, flags, handler, user);
+ }
+
@Override
public void unbindService(ServiceConnection conn) {
mBase.unbindService(conn);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d8358f9..8cc9a3a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -715,11 +715,13 @@
/**
* Activity Action: Creates a shortcut.
* <p>Input: Nothing.</p>
- * <p>Output: An Intent representing the shortcut. The intent must contain three
+ * <p>Output: An Intent representing the {@link android.content.pm.ShortcutInfo} result.</p>
+ * <p>For compatibility with older versions of android the intent may also contain three
* extras: SHORTCUT_INTENT (value: Intent), SHORTCUT_NAME (value: String),
* and SHORTCUT_ICON (value: Bitmap) or SHORTCUT_ICON_RESOURCE
* (value: ShortcutIconResource).</p>
*
+ * @see android.content.pm.ShortcutManager#createShortcutResultIntent
* @see #EXTRA_SHORTCUT_INTENT
* @see #EXTRA_SHORTCUT_NAME
* @see #EXTRA_SHORTCUT_ICON
@@ -733,26 +735,34 @@
* The name of the extra used to define the Intent of a shortcut.
*
* @see #ACTION_CREATE_SHORTCUT
+ * @deprecated Replaced with {@link android.content.pm.ShortcutManager#createShortcutResultIntent}
*/
+ @Deprecated
public static final String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
/**
* The name of the extra used to define the name of a shortcut.
*
* @see #ACTION_CREATE_SHORTCUT
+ * @deprecated Replaced with {@link android.content.pm.ShortcutManager#createShortcutResultIntent}
*/
+ @Deprecated
public static final String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
/**
* The name of the extra used to define the icon, as a Bitmap, of a shortcut.
*
* @see #ACTION_CREATE_SHORTCUT
+ * @deprecated Replaced with {@link android.content.pm.ShortcutManager#createShortcutResultIntent}
*/
+ @Deprecated
public static final String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
/**
* The name of the extra used to define the icon, as a ShortcutIconResource, of a shortcut.
*
* @see #ACTION_CREATE_SHORTCUT
* @see android.content.Intent.ShortcutIconResource
+ * @deprecated Replaced with {@link android.content.pm.ShortcutManager#createShortcutResultIntent}
*/
+ @Deprecated
public static final String EXTRA_SHORTCUT_ICON_RESOURCE =
"android.intent.extra.shortcut.ICON_RESOURCE";
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 430c7e7..5152416 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
@@ -60,4 +61,8 @@
int userId);
boolean hasShortcutHostPermission(String callingPackage);
+
+ ParceledListSlice getShortcutConfigActivities(String packageName, in UserHandle user);
+ IntentSender getShortcutConfigActivityIntent(String callingPackage, in ComponentName component,
+ in UserHandle user);
}
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 91df8e8..c90134a 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -15,6 +15,7 @@
*/
package android.content.pm;
+import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
@@ -45,6 +46,8 @@
boolean requestPinShortcut(String packageName, in ShortcutInfo shortcut,
in IntentSender resultIntent, int userId);
+ Intent createShortcutResultIntent(String packageName, in ShortcutInfo shortcut, int userId);
+
void disableShortcuts(String packageName, in List shortcutIds, CharSequence disabledMessage,
int disabledMessageResId, int userId);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 4cdd653..cf873b0 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -27,6 +27,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
@@ -384,25 +385,11 @@
* @return List of launchable activities. Can be an empty list but will not be null.
*/
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
- ParceledListSlice<ResolveInfo> activities = null;
try {
- activities = mService.getLauncherActivities(packageName, user);
+ return convertToActivityList(mService.getLauncherActivities(packageName, user), user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
- if (activities == null) {
- return Collections.EMPTY_LIST;
- }
- ArrayList<LauncherActivityInfo> lais = new ArrayList<LauncherActivityInfo>();
- for (ResolveInfo ri : activities.getList()) {
- LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri.activityInfo, user);
- if (DEBUG) {
- Log.v(TAG, "Returning activity for profile " + user + " : "
- + lai.getComponentName());
- }
- lais.add(lai);
- }
- return lais;
}
/**
@@ -465,6 +452,73 @@
}
/**
+ * Retrieves a list of config activities for creating {@link ShortcutInfo}.
+ *
+ * @param packageName The specific package to query. If null, it checks all installed packages
+ * in the profile.
+ * @param user The UserHandle of the profile.
+ * @return List of config activities. Can be an empty list but will not be null.
+ *
+ * @see Intent#ACTION_CREATE_SHORTCUT
+ * @see #getShortcutConfigActivityIntent(LauncherActivityInfo)
+ */
+ public List<LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String packageName,
+ @NonNull UserHandle user) {
+ try {
+ return convertToActivityList(mService.getShortcutConfigActivities(packageName, user),
+ user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ private List<LauncherActivityInfo> convertToActivityList(
+ @Nullable ParceledListSlice<ResolveInfo> activities, UserHandle user) {
+ if (activities == null) {
+ return Collections.EMPTY_LIST;
+ }
+ ArrayList<LauncherActivityInfo> lais = new ArrayList<>();
+ for (ResolveInfo ri : activities.getList()) {
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri.activityInfo, user);
+ if (DEBUG) {
+ Log.v(TAG, "Returning activity for profile " + user + " : "
+ + lai.getComponentName());
+ }
+ lais.add(lai);
+ }
+ return lais;
+ }
+
+ /**
+ * Returns an intent sender which can be used to start the configure activity for creating
+ * custom shortcuts. Use this method if the provider is in another profile as you are not
+ * allowed to start an activity in another profile.
+ *
+ * <p>The caller should receive {@link PinItemRequest} in onActivityResult on
+ * {@link android.app.Activity#RESULT_OK}.
+ *
+ * <p>Callers must be allowed to access the shortcut information, as defined in {@link
+ * #hasShortcutHostPermission()}.
+ *
+ * @param info a configuration activity returned by {@link #getShortcutConfigActivityList}
+ *
+ * @throws IllegalStateException when the user is locked or not running.
+ * @throws SecurityException if {@link #hasShortcutHostPermission()} is false.
+ *
+ * @see #getPinItemRequest(Intent)
+ * @see Intent#ACTION_CREATE_SHORTCUT
+ * @see android.app.Activity#startIntentSenderForResult
+ */
+ public IntentSender getShortcutConfigActivityIntent(@NonNull LauncherActivityInfo info) {
+ try {
+ return mService.getShortcutConfigActivityIntent(
+ mContext.getPackageName(), info.getComponentName(), info.getUser());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if the package is installed and enabled for a profile.
*
* @param packageName The package to check.
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 3853400..805054f 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -881,6 +881,31 @@
}
/**
+ * Returns an Intent which can be used by the default launcher to pin {@param shortcut}.
+ * This should be used by an Activity to set result in response to
+ * {@link Intent#ACTION_CREATE_SHORTCUT}.
+ *
+ * @param shortcut New shortcut to pin. If an app wants to pin an existing (either dynamic
+ * or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
+ * be set, in which case, the target shortcut must be enabled.
+ * If it's a new shortcut, all the mandatory fields, such as a short label, must be
+ * set.
+ * @return The intent that should be set as the result for the calling activity or null.
+ *
+ * @see Intent#ACTION_CREATE_SHORTCUT
+ *
+ * @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled.
+ */
+ public Intent createShortcutResultIntent(@NonNull ShortcutInfo shortcut) {
+ try {
+ return mService.createShortcutResultIntent(mContext.getPackageName(), shortcut,
+ injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Called internally when an app is considered to have come to the foreground
* even when technically it's not. This method resets the throttling for this package.
* For example, when the user sends an "inline reply" on a notification, the system UI will
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 04ee1e6..3ccac69 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -492,7 +492,7 @@
if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION ||
type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE ||
type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE ||
- type == Sensor.TYPE_WRIST_TILT_GESTURE) {
+ type == Sensor.TYPE_WRIST_TILT_GESTURE || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
wakeUpSensor = true;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 5e9fd66..4befb29 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -48,8 +48,6 @@
/** Input surface configured by native camera framework based on user-specified configuration */
private final Surface mInput;
- /** User-specified set of surfaces used as the configuration outputs */
- private final List<Surface> mOutputs;
/**
* User-specified state callback, used for outgoing events; calls to this object will be
* automatically {@link Handler#post(Runnable) posted} to {@code mStateHandler}.
@@ -87,21 +85,17 @@
* There must be no pending actions
* (e.g. no pending captures, no repeating requests, no flush).</p>
*/
- CameraCaptureSessionImpl(int id, Surface input, List<Surface> outputs,
+ CameraCaptureSessionImpl(int id, Surface input,
CameraCaptureSession.StateCallback callback, Handler stateHandler,
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
Handler deviceStateHandler, boolean configureSuccess) {
- if (outputs == null || outputs.isEmpty()) {
- throw new IllegalArgumentException("outputs must be a non-null, non-empty list");
- } else if (callback == null) {
+ if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
mId = id;
mIdString = String.format("Session %d: ", mId);
- // TODO: extra verification of outputs
- mOutputs = outputs;
mInput = input;
mStateHandler = checkHandler(stateHandler);
mStateCallback = createUserStateCallbackProxy(mStateHandler, callback);
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index 4481a74..01e58f4 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -58,14 +58,14 @@
* There must be no pending actions
* (e.g. no pending captures, no repeating requests, no flush).</p>
*/
- CameraConstrainedHighSpeedCaptureSessionImpl(int id, List<Surface> outputs,
+ CameraConstrainedHighSpeedCaptureSessionImpl(int id,
CameraCaptureSession.StateCallback callback, Handler stateHandler,
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
Handler deviceStateHandler, boolean configureSuccess,
CameraCharacteristics characteristics) {
mCharacteristics = characteristics;
CameraCaptureSession.StateCallback wrapperCallback = new WrapperCallback(callback);
- mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, outputs, wrapperCallback,
+ mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, wrapperCallback,
stateHandler, deviceImpl, deviceStateHandler, configureSuccess);
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 97ca56ef..d2aeaea 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -501,7 +501,7 @@
CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
if (DEBUG) {
- Log.d(TAG, "createCaptureSessionByOutputConfiguration");
+ Log.d(TAG, "createCaptureSessionByOutputConfigurations");
}
// OutputConfiguration objects are immutable, but need to have our own array
@@ -621,19 +621,15 @@
}
}
- List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size());
- for (OutputConfiguration config : outputConfigurations) {
- outSurfaces.add(config.getSurface());
- }
// Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
CameraCaptureSessionCore newSession = null;
if (isConstrainedHighSpeed) {
newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
- outSurfaces, callback, handler, this, mDeviceHandler, configureSuccess,
+ callback, handler, this, mDeviceHandler, configureSuccess,
mCharacteristics);
} else {
newSession = new CameraCaptureSessionImpl(mNextSessionId++, input,
- outSurfaces, callback, handler, this, mDeviceHandler,
+ callback, handler, this, mDeviceHandler,
configureSuccess);
}
@@ -1946,24 +1942,33 @@
Runnable failureDispatch = null;
if (errorCode == ERROR_CAMERA_BUFFER) {
- final Surface outputSurface =
- mConfiguredOutputs.get(resultExtras.getErrorStreamId()).getSurface();
- if (DEBUG) {
- Log.v(TAG, String.format("Lost output buffer reported for frame %d, target %s",
- frameNumber, outputSurface));
- }
- failureDispatch = new Runnable() {
- @Override
- public void run() {
- if (!CameraDeviceImpl.this.isClosed()){
- holder.getCallback().onCaptureBufferLost(
- CameraDeviceImpl.this,
- request,
- outputSurface,
- frameNumber);
- }
+ // Because 1 stream id could map to multiple surfaces, we need to specify both
+ // streamId and surfaceId.
+ List<Surface> surfaces =
+ mConfiguredOutputs.get(resultExtras.getErrorStreamId()).getSurfaces();
+ for (Surface surface : surfaces) {
+ if (!request.containsTarget(surface)) {
+ continue;
}
- };
+ if (DEBUG) {
+ Log.v(TAG, String.format("Lost output buffer reported for frame %d, target %s",
+ frameNumber, surface));
+ }
+ failureDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDeviceImpl.this.isClosed()){
+ holder.getCallback().onCaptureBufferLost(
+ CameraDeviceImpl.this,
+ request,
+ surface,
+ frameNumber);
+ }
+ }
+ };
+ // Dispatch the failure callback
+ holder.getHandler().post(failureDispatch);
+ }
} else {
boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT);
@@ -2000,10 +2005,11 @@
}
mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.isReprocess());
checkAndFireSequenceComplete();
+
+ // Dispatch the failure callback
+ holder.getHandler().post(failureDispatch);
}
- // Dispatch the failure callback
- holder.getHandler().post(failureDispatch);
}
} // public class CameraDeviceCallbacks
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index f897d85..4654fc2 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -31,13 +31,17 @@
import android.util.Size;
import android.view.Surface;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Collections;
+
import static com.android.internal.util.Preconditions.*;
/**
* A class for describing camera output, which contains a {@link Surface} and its specific
* configuration for creating capture session.
*
- * @see CameraDevice#createCaptureSessionByOutputConfiguration
+ * @see CameraDevice#createCaptureSessionByOutputConfigurations
*
*/
public final class OutputConfiguration implements Parcelable {
@@ -147,6 +151,50 @@
}
/**
+ * Create a new {@link OutputConfiguration} instance with two surfaces sharing the same stream,
+ * with a surface group ID.
+ *
+ * <p>For advanced use cases, a camera application may require more streams than the combination
+ * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, two compatible
+ * surfaces can be attached to one OutputConfiguration so that they map to one camera stream,
+ * and buffers are reference counted when being consumed by both surfaces. </p>
+ *
+ * <p>Two surfaces are compatible in below 2 cases:</p>
+ *
+ * <ol>
+ * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case,
+ * {@link CameraDevice#createCaptureSessionByOutputConfigurations} is guaranteed to succeed.
+ *
+ * <li> Surfaces with the same size, format, and dataSpace, but different Surface
+ * source classes. However, on some devices, the underlying camera device is able to use the
+ * same buffer layout for both surfaces. The only way to discover if this is the case is to
+ * create a capture session with that output configuration. For example, if the camera device
+ * uses the same private buffer format between a SurfaceView/SurfaceTexture and a
+ * MediaRecorder/MediaCodec, {@link CameraDevice#createCaptureSessionByOutputConfigurations}
+ * will succeed. Otherwise, it throws {@code IllegalArgumentException}.
+ * </ol>
+ *
+ * @param surfaceGroupId
+ * A group ID for this output, used for sharing memory between multiple outputs.
+ * @param surface
+ * A Surface for camera to output to.
+ * @param surface2
+ * Second surface for camera to output to.
+ * @throws IllegalArgumentException if the two surfaces have different size, format, or
+ * dataSpace.
+ *
+ * @hide
+ */
+ public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface,
+ @NonNull Surface surface2) {
+ this(surfaceGroupId, surface, ROTATION_0, surface2);
+
+ checkNotNull(surface2, "Surface must not be null");
+ checkMatchingSurfaces(mConfiguredSize, mConfiguredFormat, mConfiguredDataspace,
+ mConfiguredGenerationId, surface2);
+ }
+
+ /**
* Create a new {@link OutputConfiguration} instance.
*
* <p>This constructor takes an argument for desired camera rotation</p>
@@ -169,7 +217,6 @@
this(SURFACE_GROUP_ID_NONE, surface, rotation);
}
-
/**
* Create a new {@link OutputConfiguration} instance, with rotation and a group ID.
*
@@ -193,17 +240,68 @@
*/
@SystemApi
public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation) {
+ this(surfaceGroupId, surface, rotation, null /*surface2*/);
+ }
+
+ /**
+ * Create a new {@link OutputConfiguration} instance, with rotation, a group ID, and a secondary
+ * surface.
+ *
+ * <p>This constructor takes an argument for desired camera rotation, the surface group
+ * ID, and a secondary surface. See {@link #OutputConfiguration(int, Surface)} for details
+ * of the group ID.</p>
+ *
+ * <p>surface2 should be compatible with surface. See {@link #OutputConfiguration(int, Surface,
+ * Surface} for details of compatibility between surfaces.</p>
+ *
+ * <p>Since the rotation is done by the CameraDevice, both surfaces will receive buffers with
+ * the same rotation applied. This means that if the application needs two compatible surfaces
+ * to have different rotations, these surfaces cannot be shared within one OutputConfiguration.
+ * </p>
+ *
+ * @param surfaceGroupId
+ * A group ID for this output, used for sharing memory between multiple outputs.
+ * @param surface
+ * A Surface for camera to output to.
+ * @param rotation
+ * The desired rotation to be applied on camera output. Value must be one of
+ * ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees,
+ * application should make sure corresponding surface size has width and height
+ * transposed relative to the width and height without rotation. For example,
+ * if application needs camera to capture 1280x720 picture and rotate it by 90 degree,
+ * application should set rotation to {@code ROTATION_90} and make sure the
+ * corresponding Surface size is 720x1280. Note that {@link CameraDevice} might
+ * throw {@code IllegalArgumentException} if device cannot perform such rotation.
+ * @param surface2
+ * Second surface for camera to output to.
+
+ * @throws IllegalArgumentException if the two surfaces are not compatible to be shared in
+ * one OutputConfiguration.
+ *
+ * @hide
+ */
+ private OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation,
+ @Nullable Surface surface2) {
checkNotNull(surface, "Surface must not be null");
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
+
mSurfaceGroupId = surfaceGroupId;
mSurfaceType = SURFACE_TYPE_UNKNOWN;
- mSurface = surface;
mRotation = rotation;
mConfiguredSize = SurfaceUtils.getSurfaceSize(surface);
mConfiguredFormat = SurfaceUtils.getSurfaceFormat(surface);
mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(surface);
mConfiguredGenerationId = surface.getGenerationId();
mIsDeferredConfig = false;
+
+ if (surface2 == null) {
+ mSurfaces = new Surface[1];
+ mSurfaces[0] = surface;
+ } else {
+ mSurfaces = new Surface[MAX_SURFACES_COUNT];
+ mSurfaces[0] = surface;
+ mSurfaces[1] = surface2;
+ }
}
/**
@@ -231,25 +329,34 @@
* {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
*/
public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
- checkNotNull(klass, "surfaceSize must not be null");
- checkNotNull(klass, "klass must not be null");
- if (klass == android.view.SurfaceHolder.class) {
- mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
- } else if (klass == android.graphics.SurfaceTexture.class) {
- mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
- } else {
- mSurfaceType = SURFACE_TYPE_UNKNOWN;
- throw new IllegalArgumentException("Unknow surface source class type");
- }
+ this(surfaceSize, klass, true /* dummy */);
- mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
- mSurface = null;
- mRotation = ROTATION_0;
- mConfiguredSize = surfaceSize;
- mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
- mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
- mConfiguredGenerationId = 0;
- mIsDeferredConfig = true;
+ mSurfaces = new Surface[1];
+ }
+
+ /**
+ * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface
+ * source class for the deferred surface, and a secondary surface.
+ *
+ * <p>This constructor takes an argument for desired surface size and surface source class of
+ * the deferred surface, and a secondary surface. See {@link #OutputConfiguration(Size, Class)}
+ * for details of the surface size and surface source class.</p>
+ *
+ * <p> The deferred surface and secondary surface should be compatible. See
+ * {@link #OutputConfiguration(int, Surface, Surface)} for details of compatible surfaces.
+ *
+ * @hide
+ */
+ public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass,
+ @NonNull Surface surface2) {
+ this(surfaceSize, klass, true /* dummy */);
+
+ checkMatchingSurfaces(mConfiguredSize, mConfiguredFormat, mConfiguredDataspace,
+ mConfiguredGenerationId, surface2);
+
+ mSurfaces = new Surface[MAX_SURFACES_COUNT];
+ mSurfaces[0] = null;
+ mSurfaces[1] = surface2;
}
/**
@@ -285,7 +392,7 @@
*/
public void setDeferredSurface(@NonNull Surface surface) {
checkNotNull(surface, "Surface must not be null");
- if (mSurface != null) {
+ if (mSurfaces[0] != null) {
throw new IllegalStateException("Deferred surface is already set!");
}
@@ -297,7 +404,7 @@
", the pre-configured size will be used.");
}
- mSurface = surface;
+ mSurfaces[0] = surface;
}
/**
@@ -313,7 +420,7 @@
throw new IllegalArgumentException("OutputConfiguration shouldn't be null");
}
- this.mSurface = other.mSurface;
+ this.mSurfaces = other.mSurfaces;
this.mRotation = other.mRotation;
this.mSurfaceGroupId = other.mSurfaceGroupId;
this.mSurfaceType = other.mSurfaceType;
@@ -325,6 +432,49 @@
}
/**
+ * Private constructor to initialize Configuration based on surface size and class
+ */
+ private <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass,
+ boolean dummy) {
+ checkNotNull(surfaceSize, "surfaceSize must not be null");
+ checkNotNull(klass, "klass must not be null");
+ if (klass == android.view.SurfaceHolder.class) {
+ mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
+ } else if (klass == android.graphics.SurfaceTexture.class) {
+ mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
+ } else {
+ mSurfaceType = SURFACE_TYPE_UNKNOWN;
+ throw new IllegalArgumentException("Unknow surface source class type");
+ }
+
+ mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
+ mRotation = ROTATION_0;
+ mConfiguredSize = surfaceSize;
+ mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
+ mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+ mConfiguredGenerationId = 0;
+ mIsDeferredConfig = true;
+ }
+
+ /**
+ * Check if the surface properties match that of the given surface.
+ *
+ * @return true if the properties and the surface match.
+ */
+ private void checkMatchingSurfaces(Size size, int format, int dataSpace, int generationId,
+ @NonNull Surface surface) {
+ if (!size.equals(SurfaceUtils.getSurfaceSize(surface))) {
+ throw new IllegalArgumentException("Secondary surface size doesn't match");
+ }
+ if (dataSpace != SurfaceUtils.getSurfaceDataspace(surface)) {
+ throw new IllegalArgumentException("Secondary surface dataspace doesn't match");
+ }
+ if (format != SurfaceUtils.getSurfaceFormat(surface)) {
+ throw new IllegalArgumentException("Secondary surface format doesn't match");
+ }
+ }
+
+ /**
* Create an OutputConfiguration from Parcel.
*/
private OutputConfiguration(@NonNull Parcel source) {
@@ -333,25 +483,52 @@
int surfaceType = source.readInt();
int width = source.readInt();
int height = source.readInt();
- Surface surface = Surface.CREATOR.createFromParcel(source);
+ int surfaceCnt = source.readInt();
+
+ if (surfaceCnt <= 0) {
+ throw new IllegalArgumentException(
+ "Surface count in OutputConfiguration must be greater than 0");
+ }
+ if (surfaceCnt > MAX_SURFACES_COUNT) {
+ throw new IllegalArgumentException(
+ "Surface count in OutputConfiguration must not be more than "
+ + MAX_SURFACES_COUNT);
+ }
+
+ Surface[] surfaces = new Surface[surfaceCnt];
+ for (int i = 0; i < surfaceCnt; i++) {
+ Surface surface = Surface.CREATOR.createFromParcel(source);
+ surfaces[i] = surface;
+
+ if (surface == null && i > 0) {
+ throw new IllegalArgumentException("Only the first surface can be deferred");
+ }
+ }
+
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
+
mSurfaceGroupId = surfaceSetId;
- mSurface = surface;
mRotation = rotation;
- if (surface != null) {
+ mSurfaces = surfaces;
+ mConfiguredSize = new Size(width, height);
+ // First surface could be null (being deferred). Use last surface to look up surface
+ // characteristics.
+ if (mSurfaces[surfaceCnt-1] != null) {
mSurfaceType = SURFACE_TYPE_UNKNOWN;
- mConfiguredSize = SurfaceUtils.getSurfaceSize(mSurface);
- mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurface);
- mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
- mConfiguredGenerationId = mSurface.getGenerationId();
- mIsDeferredConfig = true;
+ mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces[surfaceCnt-1]);
+ mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurfaces[surfaceCnt-1]);
+ mConfiguredGenerationId = mSurfaces[surfaceCnt-1].getGenerationId();
} else {
mSurfaceType = surfaceType;
- mConfiguredSize = new Size(width, height);
mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
- mConfiguredGenerationId = 0;
mConfiguredDataspace =
StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+ mConfiguredGenerationId = 0;
+ }
+
+ if (mSurfaces[0] == null) {
+ mIsDeferredConfig = true;
+ } else {
mIsDeferredConfig = false;
}
}
@@ -359,11 +536,27 @@
/**
* Get the {@link Surface} associated with this {@link OutputConfiguration}.
*
- * @return the {@link Surface} associated with this {@link OutputConfiguration}.
+ * @return the {@link Surface} associated with this {@link OutputConfiguration}. If more than
+ * one surface is associated with this {@link OutputConfiguration}, return the first one as
+ * specified in the constructor. If there is a deferred surface, null will be returned.
+ */
+ public @Nullable Surface getSurface() {
+ return mSurfaces[0];
+ }
+
+ /**
+ * Get the immutable list of surfaces associated with this {@link OutputConfiguration}.
+ *
+ * @return the list of surfaces associated with this {@link OutputConfiguration} in the order
+ * specified in the constructor. If there is a deferred surface in the {@link
+ * OutputConfiguration}, it is returned as null as first element of the list. The list should
+ * not be modified.
+ *
+ * @hide
*/
@NonNull
- public Surface getSurface() {
- return mSurface;
+ public List<Surface> getSurfaces() {
+ return Collections.unmodifiableList(Arrays.asList(mSurfaces));
}
/**
@@ -423,8 +616,11 @@
dest.writeInt(mSurfaceType);
dest.writeInt(mConfiguredSize.getWidth());
dest.writeInt(mConfiguredSize.getHeight());
- if (mSurface != null) {
- mSurface.writeToParcel(dest, flags);
+ dest.writeInt(mSurfaces.length);
+ for (int i = 0; i < mSurfaces.length; i++) {
+ if (mSurfaces[i] != null) {
+ mSurfaces[i].writeToParcel(dest, flags);
+ }
}
}
@@ -445,20 +641,26 @@
return true;
} else if (obj instanceof OutputConfiguration) {
final OutputConfiguration other = (OutputConfiguration) obj;
- boolean iSSurfaceEqual = mSurface == other.mSurface &&
- mConfiguredGenerationId == other.mConfiguredGenerationId ;
- if (mIsDeferredConfig) {
- Log.i(TAG, "deferred config has the same surface");
- iSSurfaceEqual = true;
+ if (mRotation != other.mRotation ||
+ !mConfiguredSize.equals(other.mConfiguredSize) ||
+ mConfiguredFormat != other.mConfiguredFormat ||
+ mSurfaceGroupId != other.mSurfaceGroupId ||
+ mSurfaceType != other.mSurfaceType ||
+ mIsDeferredConfig != other.mIsDeferredConfig ||
+ mConfiguredFormat != other.mConfiguredFormat ||
+ mConfiguredDataspace != other.mConfiguredDataspace ||
+ mSurfaces.length != other.mSurfaces.length ||
+ mConfiguredGenerationId != other.mConfiguredGenerationId)
+ return false;
+
+ // If deferred, skip the first surface of mSurfaces when comparing.
+ int minIndex = (mIsDeferredConfig ? 1 : 0);
+ for (int i = minIndex; i < mSurfaces.length; i++) {
+ if (mSurfaces[i] != other.mSurfaces[i])
+ return false;
}
- return mRotation == other.mRotation &&
- iSSurfaceEqual&&
- mConfiguredSize.equals(other.mConfiguredSize) &&
- mConfiguredFormat == other.mConfiguredFormat &&
- mConfiguredDataspace == other.mConfiguredDataspace &&
- mSurfaceGroupId == other.mSurfaceGroupId &&
- mSurfaceType == other.mSurfaceType &&
- mIsDeferredConfig == other.mIsDeferredConfig;
+
+ return true;
}
return false;
}
@@ -469,21 +671,22 @@
@Override
public int hashCode() {
// Need ensure that the hashcode remains unchanged after set a deferred surface. Otherwise
- // The deferred output configuration will be lost in the camera streammap after the deferred
+ // the deferred output configuration will be lost in the camera streammap after the deferred
// surface is set.
- if (mIsDeferredConfig) {
- return HashCodeHelpers.hashCode(
- mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace,
- mSurfaceGroupId, mSurfaceType);
- }
+ int minIndex = (mIsDeferredConfig ? 1 : 0);
+ Surface nonDeferredSurfaces[] = Arrays.copyOfRange(mSurfaces,
+ minIndex, mSurfaces.length);
+ int surfaceHash = HashCodeHelpers.hashCodeGeneric(nonDeferredSurfaces);
return HashCodeHelpers.hashCode(
- mRotation, mSurface.hashCode(), mConfiguredGenerationId,
- mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId);
+ mRotation, surfaceHash, mConfiguredGenerationId,
+ mConfiguredSize.hashCode(), mConfiguredFormat,
+ mConfiguredDataspace, mSurfaceGroupId);
}
private static final String TAG = "OutputConfiguration";
- private Surface mSurface;
+ private static final int MAX_SURFACES_COUNT = 2;
+ private Surface mSurfaces[];
private final int mRotation;
private final int mSurfaceGroupId;
// Surface source type, this is only used by the deferred surface configuration objects.
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index c704ef0..0775bda 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -24,8 +24,10 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
+import android.service.NetworkIdentityProto;
import android.telephony.TelephonyManager;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import java.util.Objects;
@@ -110,6 +112,23 @@
return builder.append("}").toString();
}
+ public void writeToProto(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ proto.write(NetworkIdentityProto.TYPE, mType);
+
+ // Not dumping mSubType, subtypes are no longer supported.
+
+ if (mSubscriberId != null) {
+ proto.write(NetworkIdentityProto.SUBSCRIBER_ID, scrubSubscriberId(mSubscriberId));
+ }
+ proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId);
+ proto.write(NetworkIdentityProto.ROAMING, mRoaming);
+ proto.write(NetworkIdentityProto.METERED, mMetered);
+
+ proto.end(start);
+ }
+
public int getType() {
return mType;
}
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 7a832d1..57cf1a5 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -21,6 +21,7 @@
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -36,6 +37,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CompletableFuture;
/**
* Class that manages communication between network subsystems and a network scorer.
@@ -370,25 +372,26 @@
*
* @param request a {@link RecommendationRequest} instance containing additional
* request details
- * @param callback a {@link RecommendationCallback} instance that will be invoked when
- * the {@link RecommendationResult} is available
- * @param handler a {@link Handler} instance representing the thread to run the callback on.
+ * @param handler a {@link Handler} instance representing the thread to complete the future on.
+ * If null the responding binder thread will be used.
+ * @return a {@link CompletableFuture} instance that will eventually receive the
+ * {@link RecommendationResult}.
* @throws SecurityException
* @hide
*/
- public void requestRecommendation(
+ public CompletableFuture<RecommendationResult> requestRecommendation(
final @NonNull RecommendationRequest request,
- final @NonNull RecommendationCallback callback,
- final @NonNull Handler handler) {
+ final @Nullable Handler handler) {
Preconditions.checkNotNull(request, "RecommendationRequest cannot be null.");
- Preconditions.checkNotNull(callback, "RecommendationCallback cannot be null.");
- Preconditions.checkNotNull(handler, "Handler cannot be null.");
+
+ final CompletableFuture<RecommendationResult> futureResult =
+ new CompletableFuture<>();
RemoteCallback remoteCallback = new RemoteCallback(new RemoteCallback.OnResultListener() {
@Override
public void onResult(Bundle data) {
RecommendationResult result = data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
- callback.onRecommendationAvailable(result);
+ futureResult.complete(result);
}
}, handler);
@@ -397,18 +400,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- }
- /**
- * Callers of {@link #requestRecommendation(RecommendationRequest, RecommendationCallback, Handler)}
- * must pass in an implementation of this class.
- * @hide
- */
- public abstract static class RecommendationCallback {
- /**
- * Invoked when a {@link RecommendationResult} is available.
- * @param result a {@link RecommendationResult} instance.
- */
- public abstract void onRecommendationAvailable(RecommendationResult result);
+ return futureResult;
}
}
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 4a4accb..5f521de 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -31,7 +31,10 @@
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.NetworkStatsHistoryBucketProto;
+import android.service.NetworkStatsHistoryProto;
import android.util.MathUtils;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.IndentingPrintWriter;
@@ -628,6 +631,33 @@
}
}
+ public void writeToProto(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ proto.write(NetworkStatsHistoryProto.BUCKET_DURATION_MS, bucketDuration);
+
+ for (int i = 0; i < bucketCount; i++) {
+ final long startBucket = proto.start(NetworkStatsHistoryProto.BUCKETS);
+
+ proto.write(NetworkStatsHistoryBucketProto.BUCKET_START_MS, bucketStart[i]);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.RX_BYTES, rxBytes, i);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.RX_PACKETS, rxPackets, i);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.TX_BYTES, txBytes, i);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.TX_PACKETS, txPackets, i);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.OPERATIONS, operations, i);
+
+ proto.end(startBucket);
+ }
+
+ proto.end(start);
+ }
+
+ private static void writeToProto(ProtoOutputStream proto, long tag, long[] array, int index) {
+ if (array != null) {
+ proto.write(tag, array[index]);
+ }
+ }
+
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index daf8b2d..80ecf97 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -887,6 +887,21 @@
"eng".equals(getString("ro.build.type"));
/**
+ * Whether this build is running inside a container.
+ *
+ * We should try to avoid checking this flag if possible to minimize
+ * unnecessarily diverging from non-container Android behavior.
+ * Checking this flag is acceptable when low-level resources being
+ * different, e.g. the availability of certain capabilities, access to
+ * system resources being restricted, and the fact that the host OS might
+ * handle some features for us.
+ * For higher-level behavior differences, other checks should be preferred.
+ * @hide
+ */
+ public static final boolean IS_CONTAINER =
+ SystemProperties.getBoolean("ro.boot.container", false);
+
+ /**
* Specifies whether the permissions needed by a legacy app should be
* reviewed before any of its components can run. A legacy app is one
* with targetSdkVersion < 23, i.e apps using the old permission model.
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index 1ee83ae..c5ceecd 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -25,5 +25,5 @@
boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener);
boolean setupBcb(in String command);
boolean clearBcb();
- void rebootRecoveryWithCommand(in String command, in boolean update);
+ void rebootRecoveryWithCommand(in String command);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 9513854..1c2588a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -19,6 +19,7 @@
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.UserManager;
import android.content.pm.UserInfo;
import android.content.IntentSender;
import android.content.RestrictionEntry;
@@ -61,6 +62,7 @@
int getUserSerialNumber(int userHandle);
int getUserHandle(int userSerialNumber);
int getUserRestrictionSource(String restrictionKey, int userHandle);
+ List<UserManager.EnforcingUser> getUserRestrictionSources(String restrictionKey, int userHandle);
Bundle getUserRestrictions(int userHandle);
boolean hasBaseUserRestriction(String restrictionKey, int userHandle);
boolean hasUserRestriction(in String restrictionKey, int userHandle);
diff --git a/core/java/android/os/IProxyFileDescriptorCallback.java b/core/java/android/os/ProxyFileDescriptorCallback.java
similarity index 69%
rename from core/java/android/os/IProxyFileDescriptorCallback.java
rename to core/java/android/os/ProxyFileDescriptorCallback.java
index e41e194..2e9f8d9 100644
--- a/core/java/android/os/IProxyFileDescriptorCallback.java
+++ b/core/java/android/os/ProxyFileDescriptorCallback.java
@@ -17,18 +17,20 @@
package android.os;
import android.system.ErrnoException;
+import android.system.OsConstants;
/**
* Callback that handles file system requests from ProxyFileDescriptor.
- * @hide
*/
-public interface IProxyFileDescriptorCallback {
+public abstract class ProxyFileDescriptorCallback {
/**
* Returns size of bytes provided by the file descriptor.
* @return Size of bytes
* @throws ErrnoException
*/
- long onGetSize() throws ErrnoException;
+ public long onGetSize() throws ErrnoException {
+ throw new ErrnoException("onGetSize", OsConstants.EBADF);
+ }
/**
* Provides bytes read from file descriptor.
@@ -39,7 +41,9 @@
* @return Size of bytes returned by the function.
* @throws ErrnoException
*/
- int onRead(long offset, int size, byte[] data) throws ErrnoException;
+ public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+ throw new ErrnoException("onRead", OsConstants.EBADF);
+ }
/**
* Handles bytes written to file descriptor.
@@ -49,11 +53,20 @@
* @return Size of bytes processed by the function.
* @throws ErrnoException
*/
- int onWrite(long offset, int size, byte[] data) throws ErrnoException;
+ public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+ throw new ErrnoException("onWrite", OsConstants.EBADF);
+ }
/**
* Processes fsync request.
* @throws ErrnoException
*/
- void onFsync() throws ErrnoException;
+ public void onFsync() throws ErrnoException {
+ throw new ErrnoException("onFsync", OsConstants.EINVAL);
+ }
+
+ /**
+ * Invoked after the file is closed.
+ */
+ abstract public void onRelease();
}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 7f9ea438..d48431a 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -491,10 +491,15 @@
command += securityArg;
}
- // RECOVERY_SERVICE writes to BCB (bootloader control block) and triggers the reboot.
RecoverySystem rs = (RecoverySystem) context.getSystemService(
Context.RECOVERY_SERVICE);
- rs.rebootRecoveryWithCommand(command, true /* update */);
+ if (!rs.setupBcb(command)) {
+ throw new IOException("Setup BCB failed");
+ }
+
+ // Having set up the BCB (bootloader control block), go ahead and reboot
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);
throw new IOException("Reboot failed (no permissions?)");
}
@@ -708,7 +713,7 @@
// Write the command into BCB (bootloader control block) and boot from
// there. Will not return unless failed.
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
- rs.rebootRecoveryWithCommand(command.toString(), false);
+ rs.rebootRecoveryWithCommand(command.toString());
throw new IOException("Reboot failed (no permissions?)");
}
@@ -908,9 +913,9 @@
* Talks to RecoverySystemService via Binder to set up the BCB command and
* reboot into recovery accordingly.
*/
- private void rebootRecoveryWithCommand(String command, boolean update) {
+ private void rebootRecoveryWithCommand(String command) {
try {
- mService.rebootRecoveryWithCommand(command, update);
+ mService.rebootRecoveryWithCommand(command);
} catch (RemoteException ignored) {
}
}
diff --git a/core/java/android/os/UserManager.aidl b/core/java/android/os/UserManager.aidl
new file mode 100644
index 0000000..2611b0f
--- /dev/null
+++ b/core/java/android/os/UserManager.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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.os;
+
+parcelable UserManager.EnforcingUser;
\ No newline at end of file
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3478eaa..efacb20 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1187,7 +1187,9 @@
* @return The source of user restriction. Any combination of {@link #RESTRICTION_NOT_SET},
* {@link #RESTRICTION_SOURCE_SYSTEM}, {@link #RESTRICTION_SOURCE_DEVICE_OWNER}
* and {@link #RESTRICTION_SOURCE_PROFILE_OWNER}
+ * @deprecated use {@link #getUserRestrictionSources(String, int)} instead.
*/
+ @Deprecated
@SystemApi
@UserRestrictionSource
public int getUserRestrictionSource(String restrictionKey, UserHandle userHandle) {
@@ -1199,6 +1201,25 @@
}
/**
+ * @hide
+ *
+ * Returns a list of users who set a user restriction on a given user.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param restrictionKey the string key representing the restriction
+ * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
+ * @return a list of user ids enforcing this restriction.
+ */
+ @SystemApi
+ public List<EnforcingUser> getUserRestrictionSources(
+ String restrictionKey, UserHandle userHandle) {
+ try {
+ return mService.getUserRestrictionSources(restrictionKey, userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the user-wide restrictions imposed on this user.
* @return a Bundle containing all the restrictions.
*/
@@ -2310,8 +2331,8 @@
* @hide
* Checks if any uninitialized user has the specific seed account name and type.
*
- * @param mAccountName The account name to check for
- * @param mAccountType The account type of the account to check for
+ * @param accountName The account name to check for
+ * @param accountType The account type of the account to check for
* @return whether the seed account was found
*/
public boolean someUserHasSeedAccount(String accountName, String accountType) {
@@ -2321,4 +2342,73 @@
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * @hide
+ * User that enforces a restriction.
+ *
+ * @see #getUserRestrictionSources(String, UserHandle)
+ */
+ @SystemApi
+ public static final class EnforcingUser implements Parcelable {
+ private final @UserIdInt int userId;
+ private final @UserRestrictionSource int userRestrictionSource;
+
+ /**
+ * @hide
+ */
+ public EnforcingUser(
+ @UserIdInt int userId, @UserRestrictionSource int userRestrictionSource) {
+ this.userId = userId;
+ this.userRestrictionSource = userRestrictionSource;
+ }
+
+ private EnforcingUser(Parcel in) {
+ userId = in.readInt();
+ userRestrictionSource = in.readInt();
+ }
+
+ public static final Creator<EnforcingUser> CREATOR = new Creator<EnforcingUser>() {
+ @Override
+ public EnforcingUser createFromParcel(Parcel in) {
+ return new EnforcingUser(in);
+ }
+
+ @Override
+ public EnforcingUser[] newArray(int size) {
+ return new EnforcingUser[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(userId);
+ dest.writeInt(userRestrictionSource);
+ }
+
+ /**
+ * Returns an id of the enforcing user.
+ *
+ * <p> Will be UserHandle.USER_NULL when restriction is set by the system.
+ */
+ public UserHandle getUserHandle() {
+ return UserHandle.of(userId);
+ }
+
+ /**
+ * Returns the status of the enforcing user.
+ *
+ * <p> One of {@link #RESTRICTION_SOURCE_SYSTEM},
+ * {@link #RESTRICTION_SOURCE_DEVICE_OWNER} and
+ * {@link #RESTRICTION_SOURCE_PROFILE_OWNER}
+ */
+ public @UserRestrictionSource int getUserRestrictionSource() {
+ return userRestrictionSource;
+ }
+ }
}
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 466a7e3..97da588 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -15,7 +15,6 @@
*/
package android.os;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
@@ -24,6 +23,10 @@
* @hide Only for use within the system server.
*/
public abstract class UserManagerInternal {
+ public static final int CAMERA_NOT_DISABLED = 0;
+ public static final int CAMERA_DISABLED_LOCALLY = 1;
+ public static final int CAMERA_DISABLED_GLOBALLY = 2;
+
public interface UserRestrictionsListener {
/**
* Called when a user restriction changes.
@@ -36,18 +39,19 @@
}
/**
- * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
- * to set per-user as well as global user restrictions.
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set
+ * restrictions enforced by the user.
*
* @param userId target user id for the local restrictions.
- * @param localRestrictions per-user restrictions.
- * Caller must not change it once passed to this method.
- * @param globalRestrictions global restrictions set by DO. Must be null when PO changed user
- * restrictions, in which case global restrictions won't change.
- * Caller must not change it once passed to this method.
+ * @param restrictions a bundle of user restrictions.
+ * @param isDeviceOwner whether {@code userId} corresponds to device owner user id.
+ * @param cameraRestrictionScope is camera disabled and if so what is the scope of restriction.
+ * Should be one of {@link #CAMERA_NOT_DISABLED}, {@link #CAMERA_DISABLED_LOCALLY} or
+ * {@link #CAMERA_DISABLED_GLOBALLY}
*/
- public abstract void setDevicePolicyUserRestrictions(int userId,
- @NonNull Bundle localRestrictions, @Nullable Bundle globalRestrictions);
+ public abstract void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
+ boolean isDeviceOwner, int cameraRestrictionScope);
+
/**
* Returns the "base" user restrictions.
*
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 27c0526..59394b2 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -25,6 +25,7 @@
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
+import com.android.internal.os.AppFuseMount;
/**
* WARNING! Update IMountService.h and IMountService.cpp if you change this
@@ -289,4 +290,6 @@
void addUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 70;
void fixateNewestUserKeyAuth(int userId) = 71;
void fstrim(int flags) = 72;
+ AppFuseMount mountProxyFileDescriptorBridge() = 73;
+ ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 03fd8d3..85df48f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -30,6 +30,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.ProxyFileDescriptorCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -44,7 +45,10 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AppFuseMount;
+import com.android.internal.os.FuseAppLoop;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
@@ -62,6 +66,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -1322,6 +1327,90 @@
}
}
+
+ /** {@hide} */
+ @VisibleForTesting
+ public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+ int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory)
+ throws IOException {
+ // Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
+ // invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
+ // the bridge by calling mountProxyFileDescriptorBridge.
+ int retry = 3;
+ while (retry-- > 0) {
+ try {
+ synchronized (mFuseAppLoopLock) {
+ if (mFuseAppLoop == null) {
+ final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
+ if (mount == null) {
+ Log.e(TAG, "Failed to open proxy file bridge.");
+ throw new IOException("Failed to open proxy file bridge.");
+ }
+ mFuseAppLoop = FuseAppLoop.open(mount.mountPointId, mount.fd, factory);
+ }
+
+ try {
+ final int fileId = mFuseAppLoop.registerCallback(callback);
+ final ParcelFileDescriptor pfd =
+ mStorageManager.openProxyFileDescriptor(
+ mFuseAppLoop.getMountPointId(), fileId, mode);
+ if (pfd != null) {
+ return pfd;
+ }
+ // Probably the bridge is being unmounted but mFuseAppLoop has not been
+ // noticed it yet.
+ mFuseAppLoop.unregisterCallback(fileId);
+ } catch (FuseAppLoop.UnmountedException error) {
+ Log.d(TAG, "mFuseAppLoop has been already unmounted.");
+ mFuseAppLoop = null;
+ continue;
+ }
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ break;
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ throw new IOException("Failed to mount bridge.");
+ }
+
+ /**
+ * Opens seekable ParcelFileDescriptor that routes file operation requests to
+ * ProxyFileDescriptorCallback.
+ *
+ * @param mode The desired access mode, must be one of
+ * {@link ParcelFileDescriptor#MODE_READ_ONLY},
+ * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
+ * {@link ParcelFileDescriptor#MODE_READ_WRITE}
+ * @param callback Callback to process file operation requests issued on returned file
+ * descriptor. The callback is invoked on a thread managed by the framework.
+ * @return Seekable ParcelFileDescriptor.
+ * @throws IOException
+ */
+ public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+ int mode, ProxyFileDescriptorCallback callback)
+ throws IOException {
+ return openProxyFileDescriptor(mode, callback, null);
+ }
+
+ /** {@hide} */
+ @VisibleForTesting
+ public int getProxyFileDescriptorMountPointId() {
+ synchronized (mFuseAppLoopLock) {
+ return mFuseAppLoop != null ? mFuseAppLoop.getMountPointId() : -1;
+ }
+ }
+
+ private final Object mFuseAppLoopLock = new Object();
+
+ @GuardedBy("mFuseAppLoopLock")
+ private @Nullable FuseAppLoop mFuseAppLoop = null;
+
/// Consts to match the password types in cryptfs.h
/** @hide */
public static final int CRYPT_TYPE_PASSWORD = 0;
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a07aee5..1b512c6 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -8170,11 +8170,29 @@
/**
* The content:// style URI for this table. Requests to this URI can be
* performed on the UI thread because they are always unblocking.
+ *
+ * <p>Note when you listen on this URI (or any other sub-URIs), you'll be notified for
+ * regular contact change notifications too, which will be sent on the root URI.
+ * If you only want to be notified for provider status change notifications, listen on
+ * {@link #STATUS_CHANGE_NOTIFICATION_CONTENT_URI} instead.
*/
public static final Uri CONTENT_URI =
Uri.withAppendedPath(AUTHORITY_URI, "provider_status");
/**
+ * URI to listen to provider status changes without listening to regular
+ * contact changes. If a client only wants to monitor {@link ProviderStatus} with
+ * {@link android.app.job.JobScheduler}, then this URI should be used instead of
+ * {@link #CONTENT_URI}, because a job on {@link #CONTENT_URI} will also be invoked
+ * when contacts are changed.
+ *
+ * <p>Note this URI cannot be queried. A query should be always made on
+ * {@link #CONTENT_URI}.
+ */
+ public static final Uri STATUS_CHANGE_NOTIFICATION_CONTENT_URI =
+ Uri.parse("content://com.android.contacts.provider_status");
+
+ /**
* The MIME-type of {@link #CONTENT_URI} providing a directory of
* settings.
*/
@@ -8201,6 +8219,13 @@
* on the device.
*/
public static final int STATUS_EMPTY = 2;
+
+ /**
+ * Timestamp (milliseconds since epoch) of when the provider's database was created.
+ *
+ * <P>Type: long
+ */
+ public static final String DATABASE_CREATION_TIMESTAMP = "database_creation_timestamp";
}
/**
@@ -8768,7 +8793,14 @@
/**
* This is the intent that is fired when the contacts database is created. <p> The
* READ_CONTACT permission is required to receive these broadcasts.
+ *
+ * <p>As of O, this broadcast will no longer be sent. Applications can use
+ * use {@link android.app.job.JobScheduler} to monitor
+ * {@link ProviderStatus#STATUS_CHANGE_NOTIFICATION_CONTENT_URI}, and read
+ * {@link ProviderStatus#DATABASE_CREATION_TIMESTAMP} to get when
+ * the contacts database was initialized.
*/
+ @Deprecated
public static final String CONTACTS_DATABASE_CREATED =
"android.provider.Contacts.DATABASE_CREATED";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 893e53c..371c0f3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5313,6 +5313,21 @@
public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
/**
+ * Setting specifying if the accessibility shortcut dialog has been shown to this user.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN =
+ "accessibility_shortcut_dialog_shown";
+
+ /**
+ * Setting specifying the the accessibility service to be toggled via the accessibility
+ * shortcut. Must be its flattened {@link ComponentName}.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE =
+ "accessibility_shortcut_target_service";
+
+ /**
* If touch exploration is enabled.
*/
public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
@@ -6782,6 +6797,8 @@
TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
TOUCH_EXPLORATION_ENABLED,
ACCESSIBILITY_ENABLED,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
ACCESSIBILITY_SPEAK_PASSWORD,
ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
ACCESSIBILITY_CAPTIONING_PRESET,
@@ -7071,7 +7088,9 @@
* Setting whether the global gesture for enabling accessibility is enabled.
* If this gesture is enabled the user will be able to perfrom it to enable
* the accessibility state without visiting the settings app.
+ *
* @hide
+ * No longer used. Should be removed once all dependencies have been updated.
*/
public static final String ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED =
"enable_accessibility_global_gesture_enabled";
@@ -9372,7 +9391,6 @@
DOCK_SOUNDS_ENABLED,
CHARGING_SOUNDS_ENABLED,
USB_MASS_STORAGE_ENABLED,
- ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9d1af50..37dfdb9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6934,8 +6934,7 @@
& (View.AUTO_FILL_FLAG_TYPE_FILL
| View.AUTO_FILL_FLAG_TYPE_SAVE)) != 0;
final int id = mID;
- if (id > 0 && (id&0xff000000) != 0 && (id&0x00ff0000) != 0
- && (id&0x0000ffff) != 0) {
+ if (id != NO_ID && !isViewIdGenerated(id)) {
String pkg, type, entry;
try {
final Resources res = getResources();
@@ -22640,6 +22639,10 @@
}
}
+ private static boolean isViewIdGenerated(int id) {
+ return (id & 0xFF000000) == 0 && (id & 0x00FFFFFF) != 0;
+ }
+
/**
* Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions.
* @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 6d2f850..0e753f3 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -83,6 +83,13 @@
private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 500;
/**
+ * Defines the duration in milliseconds a user needs to hold down the
+ * appropriate button to bring up the accessibility shortcut (first time) or enable it
+ * (once shortcut is configured).
+ */
+ private static final int A11Y_SHORTCUT_KEY_TIMEOUT = 3000;
+
+ /**
* Defines the duration in milliseconds we will wait to see if a touch event
* is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
@@ -785,6 +792,18 @@
}
/**
+ * The amount of time a user needs to press the relevant keys to activate the accessibility
+ * shortcut.
+ *
+ * @return how long a user needs to press the relevant keys to activate the accessibility
+ * shortcut.
+ * @hide
+ */
+ public long getAccessibilityShortcutKeyTimeout() {
+ return A11Y_SHORTCUT_KEY_TIMEOUT;
+ }
+
+ /**
* The amount of friction applied to scrolls and flings.
*
* @return A scalar dimensionless value representing the coefficient of
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 1b42a90..a479bb3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1788,15 +1788,15 @@
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
- final PointF point = getLocalPoint();
- if (isTransformedTouchPointInView(x, y, child, point)) {
- final PointerIcon pointerIcon =
- dispatchResolvePointerIcon(event, pointerIndex, child);
- if (pointerIcon != null) {
- if (preorderedList != null) preorderedList.clear();
- return pointerIcon;
- }
- break;
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
+ final PointerIcon pointerIcon =
+ dispatchResolvePointerIcon(event, pointerIndex, child);
+ if (pointerIcon != null) {
+ if (preorderedList != null) preorderedList.clear();
+ return pointerIcon;
}
}
if (preorderedList != null) preorderedList.clear();
@@ -2091,11 +2091,12 @@
getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child =
getAndVerifyPreorderedView(preorderedList, children, childIndex);
- final PointF point = getLocalPoint();
- if (isTransformedTouchPointInView(x, y, child, point)) {
- if (dispatchTooltipHoverEvent(event, child)) {
- newTarget = child;
- }
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
+ if (dispatchTooltipHoverEvent(event, child)) {
+ newTarget = child;
break;
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 7f940f1..bfb8d83 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -21,6 +21,7 @@
import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
@@ -241,6 +242,8 @@
* @hide
*/
public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
+ // Constructor can't be chained because we can't create an instance of an inner class
+ // before calling another constructor.
mHandler = new MyHandler(context.getMainLooper());
mUserId = userId;
synchronized (mLock) {
@@ -249,6 +252,23 @@
}
/**
+ * Create an instance.
+ *
+ * @param handler The handler to use
+ * @param service An interface to the backing service.
+ * @param userId User id under which to run.
+ *
+ * @hide
+ */
+ public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
+ mHandler = handler;
+ mUserId = userId;
+ synchronized (mLock) {
+ tryConnectToServiceLocked(service);
+ }
+ }
+
+ /**
* @hide
*/
public IAccessibilityManagerClient getClient() {
@@ -647,6 +667,30 @@
}
/**
+ * Find an installed service with the specified {@link ComponentName}.
+ *
+ * @param componentName The name to match to the service.
+ *
+ * @return The info corresponding to the installed service, or {@code null} if no such service
+ * is installed.
+ * @hide
+ */
+ public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
+ ComponentName componentName) {
+ final List<AccessibilityServiceInfo> installedServiceInfos =
+ getInstalledAccessibilityServiceList();
+ if ((installedServiceInfos == null) || (componentName == null)) {
+ return null;
+ }
+ for (int i = 0; i < installedServiceInfos.size(); i++) {
+ if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
+ return installedServiceInfos.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
* Adds an accessibility interaction connection interface for a given window.
* @param windowToken The window token to which a connection is added.
* @param connection The connection.
@@ -693,6 +737,26 @@
}
}
+ /**
+ * Perform the accessibility shortcut if the caller has permission.
+ *
+ * @hide
+ */
+ public void performAccessibilityShortcut() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.performAccessibilityShortcut();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
+ }
+ }
+
private IAccessibilityManager getServiceLocked() {
if (mService == null) {
tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 2829744..ed77f68 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -60,7 +60,6 @@
IBinder getWindowToken(int windowId, int userId);
- void enableAccessibilityService(in ComponentName service, int userId);
-
- void disableAccessibilityService(in ComponentName service, int userId);
+ // Requires WRITE_SECURE_SETTINGS
+ void performAccessibilityShortcut();
}
diff --git a/core/java/android/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl
index 894f080..dbca7ff 100644
--- a/core/java/android/webkit/IWebViewUpdateService.aidl
+++ b/core/java/android/webkit/IWebViewUpdateService.aidl
@@ -78,4 +78,14 @@
* Enable or disable the WebView package fallback mechanism.
*/
void enableFallbackLogic(boolean enable);
+
+ /**
+ * Used by Settings to determine whether multiprocess is enabled.
+ */
+ boolean isMultiProcessEnabled();
+
+ /**
+ * Used by Settings to enable/disable multiprocess.
+ */
+ void enableMultiProcess(boolean enable);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 345896a..b6693c7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7740,14 +7740,25 @@
desired = Math.max(desired, dr.mDrawableHeightRight);
}
- desired += getCompoundPaddingTop() + getCompoundPaddingBottom();
+ int linecount = layout.getLineCount();
+ final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
+ desired += padding;
if (mMaxMode != LINES) {
desired = Math.min(desired, mMaximum);
+ } else if (cap && linecount > mMaximum && layout instanceof DynamicLayout) {
+ desired = layout.getLineTop(mMaximum);
+
+ if (dr != null) {
+ desired = Math.max(desired, dr.mDrawableHeightLeft);
+ desired = Math.max(desired, dr.mDrawableHeightRight);
+ }
+
+ desired += padding;
+ linecount = mMaximum;
}
if (mMinMode == LINES) {
- int linecount = layout.getLineCount();
if (linecount < mMinimum) {
desired += getLineHeight() * (mMinimum - linecount);
}
diff --git a/core/java/com/android/internal/os/AppFuseMount.aidl b/core/java/com/android/internal/os/AppFuseMount.aidl
new file mode 100644
index 0000000..66cf95b
--- /dev/null
+++ b/core/java/com/android/internal/os/AppFuseMount.aidl
@@ -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.
+ */
+
+package com.android.internal.os;
+
+parcelable AppFuseMount;
diff --git a/core/java/com/android/internal/os/AppFuseMount.java b/core/java/com/android/internal/os/AppFuseMount.java
index b392186..04d7211 100644
--- a/core/java/com/android/internal/os/AppFuseMount.java
+++ b/core/java/com/android/internal/os/AppFuseMount.java
@@ -19,14 +19,26 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import java.io.File;
+import android.os.storage.IStorageManager;
+import com.android.internal.util.Preconditions;
+/**
+ * Parcelable class representing AppFuse mount.
+ * This conveys the result for IStorageManager#openProxyFileDescriptor.
+ * @see IStorageManager#openProxyFileDescriptor
+ */
public class AppFuseMount implements Parcelable {
- final public File mountPoint;
+ final public int mountPointId;
final public ParcelFileDescriptor fd;
- public AppFuseMount(File mountPoint, ParcelFileDescriptor fd) {
- this.mountPoint = mountPoint;
+ /**
+ * @param mountPointId Integer number for mount point that is unique in the lifetime of
+ * StorageManagerService.
+ * @param fd File descriptor pointing /dev/fuse and tagged with the mount point.
+ */
+ public AppFuseMount(int mountPointId, ParcelFileDescriptor fd) {
+ Preconditions.checkNotNull(fd);
+ this.mountPointId = mountPointId;
this.fd = fd;
}
@@ -37,7 +49,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(this.mountPoint.getPath());
+ dest.writeInt(this.mountPointId);
dest.writeParcelable(fd, flags);
}
@@ -45,7 +57,7 @@
new Parcelable.Creator<AppFuseMount>() {
@Override
public AppFuseMount createFromParcel(Parcel in) {
- return new AppFuseMount(new File(in.readString()), in.readParcelable(null));
+ return new AppFuseMount(in.readInt(), in.readParcelable(null));
}
@Override
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index 34253ce..3603b6d 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -18,34 +18,38 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.IProxyFileDescriptorCallback;
+import android.os.ProxyFileDescriptorCallback;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.concurrent.ThreadFactory;
public class FuseAppLoop {
private static final String TAG = "FuseAppLoop";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final int ROOT_INODE = 1;
private static final int MIN_INODE = 2;
+ private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, TAG);
+ }
+ };
private final Object mLock = new Object();
- private final File mParent;
+ private final int mMountPointId;
+ private final Thread mThread;
@GuardedBy("mLock")
private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
- @GuardedBy("mLock")
- private boolean mActive = true;
-
/**
* Sequential number can be used as file name and inode in AppFuse.
* 0 is regarded as an error, 1 is mount point. So we start the number from 2.
@@ -53,35 +57,40 @@
@GuardedBy("mLock")
private int mNextInode = MIN_INODE;
- private FuseAppLoop(@NonNull File parent) {
- mParent = parent;
- }
-
- public static @NonNull FuseAppLoop open(
- @NonNull File parent, @NonNull ParcelFileDescriptor fd) {
- Preconditions.checkNotNull(parent);
- Preconditions.checkNotNull(fd);
- final FuseAppLoop bridge = new FuseAppLoop(parent);
+ private FuseAppLoop(
+ int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
+ mMountPointId = mountPointId;
final int rawFd = fd.detachFd();
- new Thread(new Runnable() {
+ if (factory == null) {
+ factory = sDefaultThreadFactory;
+ }
+ mThread = factory.newThread(new Runnable() {
@Override
public void run() {
- bridge.native_start_loop(rawFd);
+ // rawFd is closed by native_start_loop. Java code does not need to close it.
+ native_start_loop(rawFd);
}
- }, TAG).start();
- return bridge;
+ });
}
- public @NonNull ParcelFileDescriptor openFile(int mode, IProxyFileDescriptorCallback callback)
+ public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd,
+ @Nullable ThreadFactory factory) {
+ Preconditions.checkNotNull(fd);
+ final FuseAppLoop loop = new FuseAppLoop(mountPointId, fd, factory);
+ loop.mThread.start();
+ return loop;
+ }
+
+ public int registerCallback(@NonNull ProxyFileDescriptorCallback callback)
throws UnmountedException, IOException {
- int id;
+ if (mThread.getState() == Thread.State.TERMINATED) {
+ throw new UnmountedException();
+ }
synchronized (mLock) {
- if (!mActive) {
- throw new UnmountedException();
- }
if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) {
throw new IOException("Too many opened files.");
}
+ int id;
while (true) {
id = mNextInode;
mNextInode++;
@@ -92,24 +101,17 @@
break;
}
}
-
- // Register callback after we succeed to create pfd.
mCallbackMap.put(id, new CallbackEntry(callback));
- }
- try {
- return ParcelFileDescriptor.open(new File(mParent, String.valueOf(id)), mode);
- } catch (FileNotFoundException error) {
- synchronized (mLock) {
- mCallbackMap.remove(id);
- }
- throw error;
+ return id;
}
}
- public @Nullable File getMountPoint() {
- synchronized (mLock) {
- return mActive ? mParent : null;
- }
+ public void unregisterCallback(int id) {
+ mCallbackMap.remove(id);
+ }
+
+ public int getMountPointId() {
+ return mMountPointId;
}
private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
@@ -128,7 +130,7 @@
try {
return getCallbackEntryOrThrowLocked(inode).callback.onGetSize();
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
@@ -147,7 +149,7 @@
// file twice.
return (int) inode;
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
@@ -160,7 +162,7 @@
getCallbackEntryOrThrowLocked(inode).callback.onFsync();
return 0;
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
@@ -169,12 +171,14 @@
@SuppressWarnings("unused")
private int onRelease(long inode) {
synchronized(mLock) {
- mCallbackMap.remove(checkInode(inode));
- if (mCallbackMap.size() == 0) {
- mActive = false;
- return -1;
+ try {
+ getCallbackEntryOrThrowLocked(inode).callback.onRelease();
+ return 0;
+ } catch (ErrnoException exp) {
+ return getError(exp);
+ } finally {
+ mCallbackMap.remove(checkInode(inode));
}
- return 0;
}
}
@@ -185,7 +189,7 @@
try {
return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
@@ -197,11 +201,17 @@
try {
return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes);
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
+ private static int getError(@NonNull ErrnoException exp) {
+ // Should not return ENOSYS because the kernel stops
+ // dispatching the FUSE action once FUSE implementation returns ENOSYS for the action.
+ return exp.errno != OsConstants.ENOSYS ? -exp.errno : -OsConstants.EIO;
+ }
+
native boolean native_start_loop(int fd);
private static int checkInode(long inode) {
@@ -212,9 +222,9 @@
public static class UnmountedException extends Exception {}
private static class CallbackEntry {
- final IProxyFileDescriptorCallback callback;
+ final ProxyFileDescriptorCallback callback;
boolean opened;
- CallbackEntry(IProxyFileDescriptorCallback callback) {
+ CallbackEntry(ProxyFileDescriptorCallback callback) {
Preconditions.checkNotNull(callback);
this.callback = callback;
}
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 20230cd..b172dbc 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -187,6 +187,7 @@
}
@Override
+ @RemotableViewMethod
public void setVisibility(int visibility) {
mDesiredVisibility = visibility;
updateVisibility();
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 68034f6..6a9ed8e 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -238,6 +238,7 @@
libnativehelper \
liblog \
libcutils \
+ libdebuggerd_client \
libutils \
libbinder \
libui \
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 14d7e81..be3a87b 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -33,7 +33,7 @@
#include <string>
#include <android-base/stringprintf.h>
-#include <cutils/debugger.h>
+#include <debuggerd/client.h>
#include <log/log.h>
#include <utils/misc.h>
#include <utils/String8.h>
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
index 6b634df..4150636 100644
--- a/core/jni/android_view_PointerIcon.cpp
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -78,6 +78,9 @@
status_t android_view_PointerIcon_getLoadedIcon(JNIEnv* env, jobject pointerIconObj,
PointerIcon* outPointerIcon) {
+ if (!pointerIconObj) {
+ return BAD_VALUE;
+ }
outPointerIcon->style = env->GetIntField(pointerIconObj, gPointerIconClassInfo.mType);
outPointerIcon->hotSpotX = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotX);
outPointerIcon->hotSpotY = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotY);
diff --git a/core/jni/com_android_internal_os_FuseAppLoop.cpp b/core/jni/com_android_internal_os_FuseAppLoop.cpp
index 92a6934..dd003eb 100644
--- a/core/jni/com_android_internal_os_FuseAppLoop.cpp
+++ b/core/jni/com_android_internal_os_FuseAppLoop.cpp
@@ -51,7 +51,6 @@
JNIEnv* const mEnv;
jobject const mSelf;
ScopedLocalRef<jbyteArray> mJniBuffer;
- bool mActive;
template <typename T>
T checkException(T result) const {
@@ -67,8 +66,7 @@
Callback(JNIEnv* env, jobject self) :
mEnv(env),
mSelf(self),
- mJniBuffer(env, nullptr),
- mActive(true) {}
+ mJniBuffer(env, nullptr) {}
bool Init() {
mJniBuffer.reset(mEnv->NewByteArray(kBufferSize));
@@ -76,7 +74,7 @@
}
bool IsActive() override {
- return mActive;
+ return true;
}
int64_t OnGetSize(uint64_t inode) override {
@@ -92,10 +90,7 @@
}
int32_t OnRelease(uint64_t inode) override {
- if (checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)) == -1) {
- mActive = false;
- }
- return fuse::kFuseSuccess;
+ return checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode));
}
int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override {
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index ec2f32b..c3b0ff1 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -21,6 +21,7 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/service/fingerprint.proto";
+import "frameworks/base/core/proto/android/service/netstats.proto";
package android.os;
@@ -49,4 +50,5 @@
// System Services
android.service.fingerprint.FingerprintServiceDumpProto fingerprint = 3000;
+ android.service.NetworkStatsServiceDumpProto netstats = 3001;
}
diff --git a/core/proto/android/service/netstats.proto b/core/proto/android/service/netstats.proto
new file mode 100644
index 0000000..5cca6ab
--- /dev/null
+++ b/core/proto/android/service/netstats.proto
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+syntax = "proto3";
+
+package android.service;
+
+option java_multiple_files = true;
+option java_outer_classname = "NetworkStatsServiceProto";
+
+// Represents dumpsys from NetworkStatsService (netstats).
+message NetworkStatsServiceDumpProto {
+ repeated NetworkInterfaceProto active_interfaces = 1;
+
+ repeated NetworkInterfaceProto active_uid_interfaces = 2;
+
+ NetworkStatsRecorderProto dev_stats = 3;
+
+ NetworkStatsRecorderProto xt_stats = 4;
+
+ NetworkStatsRecorderProto uid_stats = 5;
+
+ NetworkStatsRecorderProto uid_tag_stats = 6;
+}
+
+// Corresponds to NetworkStatsService.mActiveIfaces/mActiveUidIfaces.
+message NetworkInterfaceProto {
+ string interface = 1;
+
+ NetworkIdentitySetProto identities = 2;
+}
+
+// Corresponds to NetworkIdentitySet.
+message NetworkIdentitySetProto {
+ repeated NetworkIdentityProto identities = 1;
+}
+
+// Corresponds to NetworkIdentity.
+message NetworkIdentityProto {
+ // Constats from ConnectivityManager.TYPE_*.
+ int32 type = 1;
+
+ string subscriber_id = 2;
+
+ string network_id = 3;
+
+ bool roaming = 4;
+
+ bool metered = 5;
+}
+
+// Corresponds to NetworkStatsRecorder.
+message NetworkStatsRecorderProto {
+ int64 pending_total_bytes = 1;
+
+ NetworkStatsCollectionProto complete_history = 2;
+}
+
+// Corresponds to NetworkStatsCollection.
+message NetworkStatsCollectionProto {
+ repeated NetworkStatsCollectionStatsProto stats = 1;
+}
+
+// Corresponds to NetworkStatsCollection.mStats.
+message NetworkStatsCollectionStatsProto {
+ NetworkStatsCollectionKeyProto key = 1;
+
+ NetworkStatsHistoryProto history = 2;
+}
+
+// Corresponds to NetworkStatsCollection.Key.
+message NetworkStatsCollectionKeyProto {
+ NetworkIdentitySetProto identity = 1;
+
+ int32 uid = 2;
+
+ int32 set = 3;
+
+ int32 tag = 4;
+}
+
+// Corresponds to NetworkStatsHistory.
+message NetworkStatsHistoryProto {
+ // Duration for this bucket in milliseconds.
+ int64 bucket_duration_ms = 1;
+
+ repeated NetworkStatsHistoryBucketProto buckets = 2;
+}
+
+// Corresponds to each bucket in NetworkStatsHistory.
+message NetworkStatsHistoryBucketProto {
+ // Bucket start time in milliseconds since epoch.
+ int64 bucket_start_ms = 1;
+
+ int64 rx_bytes = 2;
+
+ int64 rx_packets = 3;
+
+ int64 tx_bytes = 4;
+
+ int64 tx_packets = 5;
+
+ int64 operations = 6;
+}
diff --git a/core/res/res/color/text_color_primary.xml b/core/res/res/color/text_color_primary.xml
new file mode 100644
index 0000000..831a9c4
--- /dev/null
+++ b/core/res/res/color/text_color_primary.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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:state_enabled="false"
+ android:alpha="?attr/disabledAlpha"
+ android:color="?attr/colorForeground"/>
+ <item android:alpha="?attr/primaryContentAlpha"
+ android:color="?attr/colorForeground"/>
+</selector>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index dd33718..8e9959f 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -62,6 +62,8 @@
<!-- Default disabled alpha for widgets that set enabled/disabled alpha programmatically. -->
<attr name="disabledAlpha" format="float" />
+ <!-- The alpha applied to the foreground color to create the primary text color. -->
+ <attr name="primaryContentAlpha" format="float" />
<!-- Default background dim amount when a menu, dialog, or something similar pops up. -->
<attr name="backgroundDimAmount" format="float" />
<!-- Control whether dimming behind the window is enabled. The default
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 40e7341..835b8b60 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -73,6 +73,8 @@
<item name="disabled_alpha_material_light" format="float" type="dimen">0.26</item>
<item name="disabled_alpha_material_dark" format="float" type="dimen">0.30</item>
+ <item name="primary_content_alpha_material_light" format="float" type="dimen">1</item>
+ <item name="primary_content_alpha_material_dark" format="float" type="dimen">0.87</item>
<item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
<item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index db157bf..7de48d3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2710,4 +2710,10 @@
<!-- Component name of the default cell broadcast receiver -->
<string name="config_defaultCellBroadcastReceiverComponent" translatable="false">com.android.cellbroadcastreceiver/.PrivilegedCellBroadcastReceiver</string>
+
+ <!-- The component name, flattened to a string, for the default accessibility service to be
+ enabled by the accessibility shortcut. This service must be trusted, as it can be activated
+ without explicit consent of the user. If no accessibility service with the specified name
+ exists on the device, the accessibility shortcut will be disabled by default. -->
+ <string name="config_defaultAccessibilityService" translatable="false"></string>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 064d31e..099fe08 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2793,6 +2793,8 @@
<public-group type="id" first-id="0x01020041">
</public-group>
+ <public type="attr" name="primaryContentAlpha" />
+
<!-- ===============================================================
DO NOT ADD UN-GROUPED ITEMS HERE
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 87a4732..0204e93 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3818,12 +3818,35 @@
"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."
</string>
- <!-- Text spoken when the user is performing a gesture that will enable accessibility. [CHAR LIMIT=none] -->
- <string name="continue_to_enable_accessibility">Keep holding down two fingers to enable accessibility.</string>
- <!-- Text spoken when the user enabled accessibility. [CHAR LIMIT=none] -->
- <string name="accessibility_enabled">Accessibility enabled.</string>
- <!-- Text spoken when the user stops preforming a gesture that would enable accessibility. [CHAR LIMIT=none] -->
- <string name="enable_accessibility_canceled">Accessibility canceled.</string>
+ <!-- Dialog title for dialog shown when the accessibility shortcut is activated, and we want
+ to confirm that the user understands what's going to happen-->
+ <string name="accessibility_shortcut_warning_dialog_title">Accessibility Shortcut is ON</string>
+
+ <!-- Message shown in dialog when user is in the process of enabling the accessibility
+ service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
+ <string name="accessibility_shortcut_toogle_warning">
+ Turn <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> on or off by holding down
+ both volume buttons for 3 seconds.\n\nYou can change the service in
+ Settings > Accessibility.
+ </string>
+
+ <!-- Text in button that turns off the accessibility shortcut -->
+ <string name="disable_accessibility_shortcut">Turn Off Shortcut</string>
+
+ <!-- Text in button that closes the warning dialog about the accessibility shortcut, leaving the
+ shortcut enabled.-->
+ <string name="leave_accessibility_shortcut_on">Leave on</string>
+
+ <!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility
+ service.-->
+ <string name="accessibility_shortcut_enabling_service">Accessibility Shortcut turned
+ <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> on</string>
+
+ <!-- Text in toast to alert the user that the accessibility shortcut turned off an accessibility
+ service.-->
+ <string name="accessibility_shortcut_disabling_service">Accessibility Shortcut turned
+ <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> off</string>
+
<!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
<string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
<!-- Message shown when switching to a user [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b923dff..c370ef7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1139,7 +1139,6 @@
<java-symbol type="string" name="conference_call" />
<java-symbol type="string" name="tooltip_popup_title" />
-
<java-symbol type="plurals" name="bugreport_countdown" />
<java-symbol type="plurals" name="last_num_days" />
<java-symbol type="plurals" name="matches_found" />
@@ -2797,4 +2796,14 @@
<java-symbol type="raw" name="fallback_categories" />
+ <java-symbol type="attr" name="primaryContentAlpha" />
+
+ <!-- Accessibility Shortcut -->
+ <java-symbol type="string" name="accessibility_shortcut_warning_dialog_title" />
+ <java-symbol type="string" name="accessibility_shortcut_toogle_warning" />
+ <java-symbol type="string" name="accessibility_shortcut_enabling_service" />
+ <java-symbol type="string" name="accessibility_shortcut_disabling_service" />
+ <java-symbol type="string" name="disable_accessibility_shortcut" />
+ <java-symbol type="string" name="leave_accessibility_shortcut_on" />
+ <java-symbol type="string" name="config_defaultAccessibilityService" />
</resources>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 5f0ad8e..d0f202b 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -48,13 +48,14 @@
<item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
<item name="disabledAlpha">@dimen/disabled_alpha_material_dark</item>
+ <item name="primaryContentAlpha">@dimen/primary_content_alpha_material_dark</item>
<item name="backgroundDimAmount">0.6</item>
<!-- Text styles -->
<item name="textAppearance">@style/TextAppearance.Material</item>
<item name="textAppearanceInverse">@style/TextAppearance.Material.Inverse</item>
- <item name="textColorPrimary">@color/primary_text_material_dark</item>
+ <item name="textColorPrimary">@color/text_color_primary</item>
<item name="textColorPrimaryInverse">@color/primary_text_material_light</item>
<item name="textColorPrimaryActivated">@color/primary_text_inverse_when_activated_material</item>
<item name="textColorPrimaryDisableOnly">@color/primary_text_disable_only_material_dark</item>
@@ -413,13 +414,14 @@
<item name="colorBackgroundFloating">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
<item name="disabledAlpha">@dimen/disabled_alpha_material_light</item>
+ <item name="primaryContentAlpha">@dimen/primary_content_alpha_material_light</item>
<item name="backgroundDimAmount">0.6</item>
<!-- Text styles -->
<item name="textAppearance">@style/TextAppearance.Material</item>
<item name="textAppearanceInverse">@style/TextAppearance.Material.Inverse</item>
- <item name="textColorPrimary">@color/primary_text_material_light</item>
+ <item name="textColorPrimary">@color/text_color_primary</item>
<item name="textColorPrimaryInverse">@color/primary_text_material_dark</item>
<item name="textColorPrimaryActivated">@color/primary_text_inverse_when_activated_material</item>
<item name="textColorSecondary">@color/secondary_text_material_light</item>
@@ -805,7 +807,7 @@
<item name="colorBackgroundFloating">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
- <item name="textColorPrimary">@color/primary_text_material_light</item>
+ <item name="textColorPrimary">@color/text_color_primary</item>
<item name="textColorPrimaryInverse">@color/primary_text_material_dark</item>
<item name="textColorSecondary">@color/secondary_text_material_light</item>
<item name="textColorSecondaryInverse">@color/secondary_text_material_dark</item>
@@ -839,7 +841,7 @@
<item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
- <item name="textColorPrimary">@color/primary_text_material_dark</item>
+ <item name="textColorPrimary">@color/text_color_primary</item>
<item name="textColorPrimaryInverse">@color/primary_text_material_light</item>
<item name="textColorPrimaryDisableOnly">@color/primary_text_disable_only_material_dark</item>
<item name="textColorSecondary">@color/secondary_text_material_dark</item>
diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
index 37f0007..ff98eb7 100644
--- a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
@@ -18,15 +18,20 @@
import android.content.Context;
import android.os.Environment;
+import android.os.ProxyFileDescriptorCallback;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
import com.android.frameworks.coretests.R;
-
+import com.android.internal.os.FuseAppLoop;
import java.io.DataInputStream;
import java.io.IOException;
+import java.util.concurrent.ThreadFactory;
import java.io.File;
import java.io.FileInputStream;
@@ -243,4 +248,47 @@
verifyObb1Contents(filePath);
unmountObb(filePath, DONT_FORCE);
}
+
+ @LargeTest
+ public void testOpenProxyFileDescriptor() throws Exception {
+ final ProxyFileDescriptorCallback callback = new ProxyFileDescriptorCallback() {
+ @Override
+ public long onGetSize() throws ErrnoException {
+ return 0;
+ }
+
+ @Override
+ public void onRelease() {}
+ };
+
+ final MyThreadFactory factory = new MyThreadFactory();
+ int firstMountId;
+ try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
+ ParcelFileDescriptor.MODE_READ_ONLY, callback, factory)) {
+ assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
+ firstMountId = mSm.getProxyFileDescriptorMountPointId();
+ assertNotSame(-1, firstMountId);
+ }
+
+ // After closing descriptor, the loop should terminate.
+ factory.thread.join(3000);
+ assertEquals(Thread.State.TERMINATED, factory.thread.getState());
+
+ // StorageManager should mount another bridge on the next open request.
+ try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
+ ParcelFileDescriptor.MODE_WRITE_ONLY, callback, factory)) {
+ assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
+ assertNotSame(firstMountId, mSm.getProxyFileDescriptorMountPointId());
+ }
+ }
+
+ private static class MyThreadFactory implements ThreadFactory {
+ Thread thread = null;
+
+ @Override
+ public Thread newThread(Runnable r) {
+ thread = new Thread(r);
+ return thread;
+ }
+ }
}
\ No newline at end of file
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 05db9b0..f1be4a9 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -110,11 +110,21 @@
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.omadm.service">
+ <permission name="android.permission.CHANGE_CONFIGURATION"/>
+ <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+ <permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.WRITE_APN_SETTINGS"/>
+ <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.packageinstaller">
<permission name="android.permission.CLEAR_APP_CACHE"/>
<permission name="android.permission.DELETE_PACKAGES"/>
<permission name="android.permission.INSTALL_PACKAGES"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
</privapp-permissions>
@@ -122,6 +132,7 @@
<permission name="android.permission.ACCESS_IMS_CALL_SERVICE"/>
<permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
+ <permission name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"/>
<permission name="android.permission.CALL_PRIVILEGED"/>
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_CONFIGURATION"/>
@@ -327,4 +338,4 @@
<permission name="android.permission.CONTROL_VPN"/>
</privapp-permissions>
-</permissions>
+</permissions>
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index a2c104a..ff21cac 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -16,27 +16,278 @@
package android.graphics;
+import android.annotation.AnyThread;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.HalfFloat;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Size;
+import android.util.Half;
import com.android.internal.util.XmlUtils;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.function.DoubleUnaryOperator;
/**
- * The Color class defines methods for creating and converting color ints.
- * Colors are represented as packed ints, made up of 4 bytes: alpha, red,
- * green, blue. The values are unpremultiplied, meaning any transparency is
- * stored solely in the alpha component, and not in the color components. The
- * components are stored as follows (alpha << 24) | (red << 16) |
- * (green << 8) | blue. Each component ranges between 0..255 with 0
- * meaning no contribution for that component, and 255 meaning 100%
- * contribution. Thus opaque-black would be 0xFF000000 (100% opaque but
- * no contributions from red, green, or blue), and opaque-white would be
- * 0xFFFFFFFF
+ * {@usesMathJax}
+ *
+ * <p>The <code>Color</code> class provides methods for creating, converting and
+ * manipulating colors. Colors have three different representations:</p>
+ * <ul>
+ * <li>Color ints, the most common representation</li>
+ * <li>Color longs</li>
+ * <li><code>Color</code> instances</li>
+ * </ul>
+ * <p>The section below describe each representation in detail.</p>
+ *
+ * <h3>Color ints</h3>
+ * <p>Color ints are the most common representation of colors on Android and
+ * have been used since {@link android.os.Build.VERSION_CODES#BASE API level 1}.</p>
+ *
+ * <p>A color int always defines a color in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space using 4 components packed in a single 32 bit integer value:</p>
+ *
+ * <table summary="Color int definition">
+ * <tr>
+ * <th>Component</th><th>Name</th><th>Size</th><th>Range</th>
+ * </tr>
+ * <tr><td>A</td><td>Alpha</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * <tr><td>R</td><td>Red</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * <tr><td>G</td><td>Green</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * <tr><td>B</td><td>Blue</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * </table>
+ *
+ * <p>The components in this table are listed in encoding order (see below),
+ * which is why color ints are called ARGB colors.</p>
+ *
+ * <h4>Usage in code</h4>
+ * <p>To avoid confusing color ints with arbitrary integer values, it is a
+ * good practice to annotate them with the <code>@ColorInt</code> annotation
+ * found in the Android Support Library.</p>
+ *
+ * <h4>Encoding</h4>
+ * <p>The four components of a color int are encoded in the following way:</p>
+ * <pre class="prettyprint">
+ * int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 16 | (B & 0xff);
+ * </pre>
+ *
+ * <p>Because of this encoding, color ints can easily be described as an integer
+ * constant in source. For instance, opaque blue is <code>0xff0000ff</code>
+ * and yellow is <code>0xffffff00</code>.</p>
+ *
+ * <p>To easily encode color ints, it is recommended to use the static methods
+ * {@link #argb(int, int, int, int)} and {@link #rgb(int, int, int)}. The second
+ * method omits the alpha component and assumes the color is opaque (alpha is 255).
+ * As a convenience this class also offers methods to encode color ints from components
+ * defined in the \([0..1]\) range: {@link #argb(float, float, float, float)} and
+ * {@link #rgb(float, float, float)}.</p>
+ *
+ * <p>Color longs (defined below) can be easily converted to color ints by invoking
+ * the {@link #toArgb(long)} method. This method performs a color space conversion
+ * if needed.</p>
+ *
+ * <p>It is also possible to create a color int by invoking the method {@link #toArgb()}
+ * on a color instance.</p>
+ *
+ * <h4>Decoding</h4>
+ * <p>The four ARGB components can be individually extracted from a color int
+ * using the following expressions:</p>
+ * <pre class="prettyprint">
+ * int A = (color >> 24) & 0xff; // or color >>> 24
+ * int R = (color >> 16) & 0xff;
+ * int G = (color >> 8) & 0xff;
+ * int B = (color ) & 0xff;
+ * </pre>
+ *
+ * <p>This class offers convenience methods to easily extract these components:</p>
+ * <ul>
+ * <li>{@link #alpha(int)} to extract the alpha component</li>
+ * <li>{@link #red(int)} to extract the red component</li>
+ * <li>{@link #green(int)} to extract the green component</li>
+ * <li>{@link #blue(int)} to extract the blue component</li>
+ * </ul>
+ *
+ * <h3>Color longs</h3>
+ * <p>Color longs are a representation introduced in
+ * {@link android.os.Build.VERSION_CODES#O Android O} to store colors in different
+ * {@link ColorSpace color spaces}, with more precision than color ints.</p>
+ *
+ * <p>A color long always defines a color using 4 components packed in a single
+ * 64 bit long value. One of these components is always alpha while the other
+ * three components depend on the color space's {@link ColorSpace.Model color model}.
+ * The most common color model is the {@link ColorSpace.Model#RGB RGB} model in
+ * which the components represent red, green and blue values.</p>
+ *
+ * <p class="note"><b>Component ranges:</b> the ranges defined in the tables
+ * below indicate the ranges that can be encoded in a color long. They do not
+ * represent the actual ranges as they may differ per color space. For instance,
+ * the RGB components of a color in the {@link ColorSpace.Named#DISPLAY_P3 Display P3}
+ * color space use the \([0..1]\) range. Please refer to the documentation of the
+ * various {@link ColorSpace.Named color spaces} to find their respective ranges.</p>
+ *
+ * <p class="note"><b>Alpha range:</b> while alpha is encoded in a color long using
+ * a 10 bit integer (thus using a range of \([0..1023]\)), it is converted to and
+ * from \([0..1]\) float values when decoding and encoding color longs.</p>
+ *
+ * <p class="note"><b>sRGB color space:</b> for compatibility reasons and ease of
+ * use, color longs encoding {@link ColorSpace.Named#SRGB sRGB} colors do not
+ * use the same encoding as other color longs.</p>
+ *
+ * <table summary="Color long definition">
+ * <tr>
+ * <th>Component</th><th>Name</th><th>Size</th><th>Range</th>
+ * </tr>
+ * <tr><td colspan="4">{@link ColorSpace.Model#RGB RGB} color model</td></tr>
+ * <tr><td>R</td><td>Red</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>G</td><td>Green</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>B</td><td>Blue</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>A</td><td>Alpha</td><td>10 bits</td><td>\([0..1023]\)</td></tr>
+ * <tr><td></td><td>Color space</td><td>6 bits</td><td>\([0..63]\)</td></tr>
+ * <tr><td colspan="4">{@link ColorSpace.Named#SRGB sRGB} color space</td></tr>
+ * <tr><td>A</td><td>Alpha</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * <tr><td>R</td><td>Red</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * <tr><td>G</td><td>Green</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * <tr><td>B</td><td>Blue</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * <tr><td>X</td><td>Unused</td><td>32 bits</td><td>\(0\)</td></tr>
+ * <tr><td colspan="4">{@link ColorSpace.Model#XYZ XYZ} color model</td></tr>
+ * <tr><td>X</td><td>X</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>Y</td><td>Y</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>Z</td><td>Z</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>A</td><td>Alpha</td><td>10 bits</td><td>\([0..1023]\)</td></tr>
+ * <tr><td></td><td>Color space</td><td>6 bits</td><td>\([0..63]\)</td></tr>
+ * <tr><td colspan="4">{@link ColorSpace.Model#XYZ Lab} color model</td></tr>
+ * <tr><td>L</td><td>L</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>a</td><td>a</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>b</td><td>b</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ * <tr><td>A</td><td>Alpha</td><td>10 bits</td><td>\([0..1023]\)</td></tr>
+ * <tr><td></td><td>Color space</td><td>6 bits</td><td>\([0..63]\)</td></tr>
+ * <tr><td colspan="4">{@link ColorSpace.Model#CMYK CMYK} color model</td></tr>
+ * <tr><td colspan="4">Unsupported</td></tr>
+ * </table>
+ *
+ * <p>The components in this table are listed in encoding order (see below),
+ * which is why color longs in the RGB model are called RGBA colors (even if
+ * this doesn't quite hold for the special case of sRGB colors).</p>
+ *
+ * <p>The color long encoding relies on half-precision float values (fp16). If you
+ * wish to know more about the limitations of half-precision float values, please
+ * refer to the documentation of the {@link Half} class.</p>
+ *
+ * <h4>Usage in code</h4>
+ * <p>To avoid confusing color longs with arbitrary long values, it is a
+ * good practice to annotate them with the <code>@ColorLong</code> annotation
+ * found in the Android Support Library.</p>
+ *
+ * <h4>Encoding</h4>
+ *
+ * <p>Given the complex nature of color longs, it is strongly encouraged to use
+ * the various methods provided by this class to encode them.</p>
+ *
+ * <p>The most flexible way to encode a color long is to use the method
+ * {@link #pack(float, float, float, float, ColorSpace)}. This method allows you
+ * to specify three color components (typically RGB), an alpha component and a
+ * color space. To encode sRGB colors, use {@link #pack(float, float, float)}
+ * and {@link #pack(float, float, float, float)} which are the
+ * equivalent of {@link #rgb(int, int, int)} and {@link #argb(int, int, int, int)}
+ * for color ints. If you simply need to convert a color int into a color long,
+ * use {@link #pack(int)}.</p>
+ *
+ * <p>It is also possible to create a color long value by invoking the method
+ * {@link #pack()} on a color instance.</p>
+ *
+ * <h4>Decoding</h4>
+ *
+ * <p>This class offers convenience methods to easily extract the components
+ * of a color long:</p>
+ * <ul>
+ * <li>{@link #alpha(long)} to extract the alpha component</li>
+ * <li>{@link #red(long)} to extract the red/X/L component</li>
+ * <li>{@link #green(long)} to extract the green/Y/a component</li>
+ * <li>{@link #blue(long)} to extract the blue/Z/b component</li>
+ * </ul>
+ *
+ * <p>The values returned by these methods depend on the color space encoded
+ * in the color long. The values are however typically in the \([0..1]\) range
+ * for RGB colors. Please refer to the documentation of the various
+ * {@link ColorSpace.Named color spaces} for the exact ranges.</p>
+ *
+ * <h3>Color instances</h3>
+ * <p>Color instances are a representation introduced in
+ * {@link android.os.Build.VERSION_CODES#O Android O} to store colors in different
+ * {@link ColorSpace color spaces}, with more precision than both color ints and
+ * color longs. Color instances also offer the ability to store more than 4
+ * components if necessary.</p>
+ *
+ * <p>Colors instances are immutable and can be created using one of the various
+ * <code>valueOf</code> methods. For instance:</p>
+ * <pre class="prettyprint">
+ * // sRGB
+ * Color opaqueRed = Color.valueOf(0xffff0000); // from a color int
+ * Color translucentRed = Color.valueOf(1.0f, 0.0f, 0.0f, 0.5f);
+ *
+ * // Wide gamut color
+ * {@literal @}ColorLong long p3 = pack(1.0f, 1.0f, 0.0f, 1.0f, colorSpaceP3);
+ * Color opaqueYellow = Color.valueOf(p3); // from a color long
+ *
+ * // CIE L*a*b* color space
+ * ColorSpace lab = ColorSpace.get(ColorSpace.Named.LAB);
+ * Color green = Color.valueOf(100.0f, -128.0f, 128.0f, 1.0f, lab);
+ * </pre>
+ *
+ * <p>Color instances can be converted to color ints ({@link #toArgb()}) or
+ * color longs ({@link #pack()}). They also offer easy access to their various
+ * components using the following methods:</p>
+ * <ul>
+ * <li>{@link #alpha()}, returns the alpha component value</li>
+ * <li>{@link #red()}, returns the red component value (or first
+ * component value in non-RGB models)</li>
+ * <li>{@link #green()}, returns the green component value (or second
+ * component value in non-RGB models)</li>
+ * <li>{@link #blue()}, returns the blue component value (or third
+ * component value in non-RGB models)</li>
+ * <li>{@link #getComponent(int)}, returns a specific component value</li>
+ * <li>{@link #getComponents()}, returns all component values as an array</li>
+ * </ul>
+ *
+ * <h3>Color space conversions</h3>
+ * <p>You can convert colors from one color space to another using
+ * {@link ColorSpace#connect(ColorSpace, ColorSpace)} and its variants. However,
+ * the <code>Color</code> class provides a few convenience methods to simplify
+ * the process. Here is a brief description of some of them:</p>
+ * <ul>
+ * <li>{@link #convert(ColorSpace)} to convert a color instance in a color
+ * space to a new color instance in a different color space</li>
+ * <li>{@link #convert(float, float, float, float, ColorSpace, ColorSpace)} to
+ * convert a color from a source color space to a destination color space</li>
+ * <li>{@link #convert(long, ColorSpace)} to convert a color long from its
+ * built-in color space to a destination color space</li>
+ * <li>{@link #convert(int, ColorSpace)} to convert a color int from sRGB
+ * to a destination color space</li>
+ * </ul>
+ *
+ * <p>Please refere to the {@link ColorSpace} documentation for more
+ * information.</p>
+ *
+ * <h3>Alpha and transparency</h3>
+ * <p>The alpha component of a color defines the level of transparency of a
+ * color. When the alpha component is 0, the color is completely transparent.
+ * When the alpha is component is 1 (in the \([0..1]\) range) or 255 (in the
+ * \([0..255]\) range), the color is completely opaque.</p>
+ *
+ * <p>The color representations described above do not use pre-multiplied
+ * color components (a pre-multiplied color component is a color component
+ * that has been multiplied by the value of the alpha component).
+ * For instance, the color int representation of opaque red is
+ * <code>0xffff0000</code>. For semi-transparent (50%) red, the
+ * representation becomes <code>0x80ff0000</code>. The equivalent color
+ * instance representations would be <code>(1.0, 0.0, 0.0, 1.0)</code>
+ * and <code>(1.0, 0.0, 0.0, 0.5)</code>.</p>
*/
+@AnyThread
public class Color {
@ColorInt public static final int BLACK = 0xFF000000;
@ColorInt public static final int DKGRAY = 0xFF444444;
@@ -51,10 +302,905 @@
@ColorInt public static final int MAGENTA = 0xFFFF00FF;
@ColorInt public static final int TRANSPARENT = 0;
+ @NonNull
+ @Size(min = 4, max = 5)
+ private final float[] mComponents;
+
+ @NonNull
+ private final ColorSpace mColorSpace;
+
+ /**
+ * Creates a new color instance set to opaque black in the
+ * {@link ColorSpace.Named#SRGB sRGB} color space.
+ *
+ * @see #valueOf(float, float, float)
+ * @see #valueOf(float, float, float, float)
+ * @see #valueOf(float, float, float, float, ColorSpace)
+ * @see #valueOf(float[], ColorSpace)
+ * @see #valueOf(int)
+ * @see #valueOf(long)
+ */
+ public Color() {
+ // This constructor is required for compatibility with previous APIs
+ mComponents = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
+ mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ }
+
+ /**
+ * Creates a new color instance in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space.
+ *
+ * @param r The value of the red channel, must be in [0..1] range
+ * @param g The value of the green channel, must be in [0..1] range
+ * @param b The value of the blue channel, must be in [0..1] range
+ * @param a The value of the alpha channel, must be in [0..1] range
+ */
+ private Color(float r, float g, float b, float a) {
+ this(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * Creates a new color instance in the specified color space. The color space
+ * must have a 3 components model.
+ *
+ * @param r The value of the red channel, must be in the color space defined range
+ * @param g The value of the green channel, must be in the color space defined range
+ * @param b The value of the blue channel, must be in the color space defined range
+ * @param a The value of the alpha channel, must be in [0..1] range
+ * @param colorSpace This color's color space, cannot be null
+ */
+ private Color(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
+ mComponents = new float[] { r, g, b, a };
+ mColorSpace = colorSpace;
+ }
+
+ /**
+ * Creates a new color instance in the specified color space.
+ *
+ * @param components An array of color components, plus alpha
+ * @param colorSpace This color's color space, cannot be null
+ */
+ private Color(@Size(min = 4, max = 5) float[] components, @NonNull ColorSpace colorSpace) {
+ mComponents = components;
+ mColorSpace = colorSpace;
+ }
+
+ /**
+ * Returns this color's color space.
+ *
+ * @return A non-null instance of {@link ColorSpace}
+ */
+ @NonNull
+ public ColorSpace getColorSpace() {
+ return mColorSpace;
+ }
+
+ /**
+ * Returns the color model of this color.
+ *
+ * @return A non-null {@link ColorSpace.Model}
+ */
+ public ColorSpace.Model getModel() {
+ return mColorSpace.getModel();
+ }
+
+ /**
+ * Indicates whether this color color is in a wide-gamut color space.
+ * See {@link ColorSpace#isWideGamut()} for a definition of a wide-gamut
+ * color space.
+ *
+ * @return True if this color is in a wide-gamut color space, false otherwise
+ *
+ * @see #isSrgb()
+ * @see ColorSpace#isWideGamut()
+ */
+ public boolean isWideGamut() {
+ return getColorSpace().isWideGamut();
+ }
+
+ /**
+ * Indicates whether this color is in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space.
+ *
+ * @return True if this color is in the sRGB color space, false otherwise
+ *
+ * @see #isWideGamut()
+ */
+ public boolean isSrgb() {
+ return getColorSpace().isSrgb();
+ }
+
+ /**
+ * Returns the number of components that form a color value according
+ * to this color space's color model, plus one extra component for
+ * alpha.
+ *
+ * @return An integer between 4 and 5
+ */
+ @IntRange(from = 4, to = 5)
+ public int getComponentCount() {
+ return mColorSpace.getComponentCount() + 1;
+ }
+
+ /**
+ * Packs this color into a color long. See the documentation of this class
+ * for a description of the color long format.
+ *
+ * @return A color long
+ *
+ * @throws IllegalArgumentException If this color's color space has the id
+ * {@link ColorSpace#MIN_ID} or if this color has more than 4 components
+ */
+ @ColorLong
+ public long pack() {
+ return pack(mComponents[0], mComponents[1], mComponents[2], mComponents[3], mColorSpace);
+ }
+
+ /**
+ * Converts this color from its color space to the specified color space.
+ * The conversion is done using the default rendering intent as specified
+ * by {@link ColorSpace#connect(ColorSpace, ColorSpace)}.
+ *
+ * @param colorSpace The destination color space, cannot be null
+ *
+ * @return A non-null color instance in the specified color space
+ */
+ @NonNull
+ public Color convert(@NonNull ColorSpace colorSpace) {
+ ColorSpace.Connector connector = ColorSpace.connect(mColorSpace, colorSpace);
+ float[] color = new float[] {
+ mComponents[0], mComponents[1], mComponents[2], mComponents[3]
+ };
+ connector.transform(color);
+ return new Color(color, colorSpace);
+ }
+
+ /**
+ * Converts this color to an ARGB color int. A color int is always in
+ * the {@link ColorSpace.Named#SRGB sRGB} color space. This implies
+ * a color space conversion is applied if needed.
+ *
+ * @return An ARGB color in the sRGB color space
+ */
+ @ColorInt
+ public int toArgb() {
+ if (mColorSpace.isSrgb()) {
+ return ((int) (mComponents[3] * 255.0f + 0.5f) << 24) |
+ ((int) (mComponents[0] * 255.0f + 0.5f) << 16) |
+ ((int) (mComponents[1] * 255.0f + 0.5f) << 8) |
+ (int) (mComponents[2] * 255.0f + 0.5f);
+ }
+
+ float[] color = new float[] {
+ mComponents[0], mComponents[1], mComponents[2], mComponents[3]
+ };
+ // The transformation saturates the output
+ ColorSpace.connect(mColorSpace).transform(color);
+
+ return ((int) (color[3] * 255.0f + 0.5f) << 24) |
+ ((int) (color[0] * 255.0f + 0.5f) << 16) |
+ ((int) (color[1] * 255.0f + 0.5f) << 8) |
+ (int) (color[2] * 255.0f + 0.5f);
+ }
+
+ /**
+ * <p>Returns the value of the red component in the range defined by this
+ * color's color space (see {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}).</p>
+ *
+ * <p>If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+ * calling this method is equivalent to <code>getComponent(0)</code>.</p>
+ *
+ * @see #alpha()
+ * @see #red()
+ * @see #green
+ * @see #getComponents()
+ */
+ public float red() {
+ return mComponents[0];
+ }
+
+ /**
+ * <p>Returns the value of the green component in the range defined by this
+ * color's color space (see {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}).</p>
+ *
+ * <p>If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+ * calling this method is equivalent to <code>getComponent(1)</code>.</p>
+ *
+ * @see #alpha()
+ * @see #red()
+ * @see #green
+ * @see #getComponents()
+ */
+ public float green() {
+ return mComponents[1];
+ }
+
+ /**
+ * <p>Returns the value of the blue component in the range defined by this
+ * color's color space (see {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}).</p>
+ *
+ * <p>If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+ * calling this method is equivalent to <code>getComponent(2)</code>.</p>
+ *
+ * @see #alpha()
+ * @see #red()
+ * @see #green
+ * @see #getComponents()
+ */
+ public float blue() {
+ return mComponents[2];
+ }
+
+ /**
+ * Returns the value of the alpha component in the range \([0..1]\).
+ * Calling this method is equivalent to
+ * <code>getComponent(getComponentCount())</code>.
+ *
+ * @see #red()
+ * @see #green()
+ * @see #blue()
+ * @see #getComponents()
+ * @see #getComponent(int)
+ */
+ public float alpha() {
+ return mComponents[mComponents.length - 1];
+ }
+
+ /**
+ * Returns this color's components as a new array. The last element of the
+ * array is always the alpha component.
+ *
+ * @return A new, non-null array whose size is equal to {@link #getComponentCount()}
+ *
+ * @see #getComponent(int)
+ */
+ @NonNull
+ @Size(min = 4, max = 5)
+ public float[] getComponents() {
+ return Arrays.copyOf(mComponents, mColorSpace.getComponentCount() + 1);
+ }
+
+ /**
+ * <p>Returns the value of the specified component in the range defined by
+ * this color's color space (see {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}).</p>
+ *
+ * <p>If the requested component index is {@link #getComponentCount()},
+ * this method returns the alpha component, always in the range
+ * \([0..1\).</p>
+ *
+ * @see #getComponents()
+ *
+ * @throws ArrayIndexOutOfBoundsException If the specified component index
+ * is < 0 or >= {@link #getComponentCount()}
+ */
+ public float getComponent(@IntRange(from = 0, to = 4) int component) {
+ return mComponents[component];
+ }
+
+ /**
+ * <p>Returns the relative luminance of this color.</p>
+ *
+ * <p>Based on the formula for relative luminance defined in WCAG 2.0,
+ * W3C Recommendation 11 December 2008.</p>
+ *
+ * @return A value between 0 (darkest black) and 1 (lightest white)
+ *
+ * @throws IllegalArgumentException If the this color's color space
+ * does not use the {@link ColorSpace.Model#RGB RGB} color model
+ */
+ public float luminance() {
+ if (mColorSpace.getModel() != ColorSpace.Model.RGB) {
+ throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
+ "color space. The supplied color space is " + mColorSpace.getModel());
+ }
+
+ DoubleUnaryOperator eotf = ((ColorSpace.Rgb) mColorSpace).getEotf();
+ double r = eotf.applyAsDouble(mComponents[0]);
+ double g = eotf.applyAsDouble(mComponents[1]);
+ double b = eotf.applyAsDouble(mComponents[2]);
+
+ return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Color color = (Color) o;
+
+ //noinspection SimplifiableIfStatement
+ if (!Arrays.equals(mComponents, color.mComponents)) return false;
+ return mColorSpace.equals(color.mColorSpace);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(mComponents);
+ result = 31 * result + mColorSpace.hashCode();
+ return result;
+ }
+
+ /**
+ * <p>Returns a string representation of the object. This method returns
+ * a string equal to the value of:</p>
+ *
+ * <pre class="prettyprint">
+ * "Color(" + r + ", " + g + ", " + b + ", " + a +
+ * ", " + getColorSpace().getName + ')'
+ * </pre>
+ *
+ * <p>For instance, the string representation of opaque black in the sRGB
+ * color space is equal to the following value:</p>
+ *
+ * <pre>
+ * Color(0.0, 0.0, 0.0, 1.0, sRGB IEC61966-2.1)
+ * </pre>
+ *
+ * @return A non-null string representation of the object
+ */
+ @Override
+ @NonNull
+ public String toString() {
+ StringBuilder b = new StringBuilder("Color(");
+ for (float c : mComponents) {
+ b.append(c).append(", ");
+ }
+ b.append(mColorSpace.getName());
+ b.append(')');
+ return b.toString();
+ }
+
+ /**
+ * Returns the color space encoded in the specified color long.
+ *
+ * @param color The color long whose color space to extract
+ * @return A non-null color space instance. If the color long encodes
+ * an unknown or invalid color space, the {@link ColorSpace.Named#SRGB sRGB}
+ * color space is returned
+ *
+ * @see #red(long)
+ * @see #green(long)
+ * @see #blue(long)
+ * @see #alpha(long)
+ */
+ @NonNull
+ public static ColorSpace colorSpace(@ColorLong long color) {
+ return ColorSpace.get((int) (color & 0x3fL));
+ }
+
+ /**
+ * Returns the red component encoded in the specified color long.
+ * The range of the returned value depends on the color space
+ * associated with the specified color. The color space can be
+ * queried by calling {@link #colorSpace(long)}.
+ *
+ * @param color The color long whose red channel to extract
+ * @return A float value with a range defined by the specified color's
+ * color space
+ *
+ * @see #colorSpace(long)
+ * @see #green(long)
+ * @see #blue(long)
+ * @see #alpha(long)
+ */
+ public static float red(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return ((color >> 48) & 0xff) / 255.0f;
+ return Half.toFloat((short) ((color >> 48) & 0xffff));
+ }
+
+ /**
+ * Returns the green component encoded in the specified color long.
+ * The range of the returned value depends on the color space
+ * associated with the specified color. The color space can be
+ * queried by calling {@link #colorSpace(long)}.
+ *
+ * @param color The color long whose green channel to extract
+ * @return A float value with a range defined by the specified color's
+ * color space
+ *
+ * @see #colorSpace(long)
+ * @see #red(long)
+ * @see #blue(long)
+ * @see #alpha(long)
+ */
+ public static float green(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return ((color >> 40) & 0xff) / 255.0f;
+ return Half.toFloat((short) ((color >> 32) & 0xffff));
+ }
+
+ /**
+ * Returns the blue component encoded in the specified color long.
+ * The range of the returned value depends on the color space
+ * associated with the specified color. The color space can be
+ * queried by calling {@link #colorSpace(long)}.
+ *
+ * @param color The color long whose blue channel to extract
+ * @return A float value with a range defined by the specified color's
+ * color space
+ *
+ * @see #colorSpace(long)
+ * @see #red(long)
+ * @see #green(long)
+ * @see #alpha(long)
+ */
+ public static float blue(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return ((color >> 32) & 0xff) / 255.0f;
+ return Half.toFloat((short) ((color >> 16) & 0xffff));
+ }
+
+ /**
+ * Returns the alpha component encoded in the specified color long.
+ * The returned value is always in the range \([0..1]\).
+ *
+ * @param color The color long whose blue channel to extract
+ * @return A float value in the range \([0..1]\)
+ *
+ * @see #colorSpace(long)
+ * @see #red(long)
+ * @see #green(long)
+ * @see #blue(long)
+ */
+ public static float alpha(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return ((color >> 56) & 0xff) / 255.0f;
+ return ((color >> 6) & 0x3ff) / 1023.0f;
+ }
+
+ /**
+ * Indicates whether the specified color is in the
+ * {@link ColorSpace.Named#SRGB sRGB} color space.
+ *
+ * @param color The color to test
+ * @return True if the color is in the sRGB color space, false otherwise
+ *
+ * @see #isInColorSpace(long, ColorSpace)
+ * @see #isWideGamut(long)
+ */
+ public static boolean isSrgb(@ColorLong long color) {
+ return colorSpace(color).isSrgb();
+ }
+
+ /**
+ * Indicates whether the specified color is in a wide-gamut color space.
+ * See {@link ColorSpace#isWideGamut()} for a definition of a wide-gamut
+ * color space.
+ *
+ * @param color The color to test
+ * @return True if the color is in a wide-gamut color space, false otherwise
+ *
+ * @see #isInColorSpace(long, ColorSpace)
+ * @see #isSrgb(long)
+ * @see ColorSpace#isWideGamut()
+ */
+ public static boolean isWideGamut(@ColorLong long color) {
+ return colorSpace(color).isWideGamut();
+ }
+
+ /**
+ * Indicates whether the specified color is in the specified color space.
+ *
+ * @param color The color to test
+ * @param colorSpace The color space to test against
+ * @return True if the color is in the specified color space, false otherwise
+ *
+ * @see #isSrgb(long)
+ * @see #isWideGamut(long)
+ */
+ public static boolean isInColorSpace(@ColorLong long color, @NonNull ColorSpace colorSpace) {
+ return (int) (color & 0x3fL) == colorSpace.getId();
+ }
+
+ /**
+ * Converts the specified color long to an ARGB color int. A color int is
+ * always in the {@link ColorSpace.Named#SRGB sRGB} color space. This implies
+ * a color space conversion is applied if needed.
+ *
+ * @return An ARGB color in the sRGB color space
+ */
+ @ColorInt
+ public static int toArgb(@ColorLong long color) {
+ if ((color & 0x3fL) == 0L) return (int) (color >> 32);
+
+ float r = red(color);
+ float g = green(color);
+ float b = blue(color);
+ float a = alpha(color);
+
+ // The transformation saturates the output
+ float[] c = ColorSpace.connect(colorSpace(color)).transform(r, g, b);
+
+ return ((int) (a * 255.0f + 0.5f) << 24) |
+ ((int) (c[0] * 255.0f + 0.5f) << 16) |
+ ((int) (c[1] * 255.0f + 0.5f) << 8) |
+ (int) (c[2] * 255.0f + 0.5f);
+ }
+
+ /**
+ * Creates a new <code>Color</code> instance from an ARGB color int.
+ * The resulting color is in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space.
+ *
+ * @param color The ARGB color int to create a <code>Color</code> from
+ * @return A non-null instance of {@link Color}
+ */
+ @NonNull
+ public static Color valueOf(@ColorInt int color) {
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color ) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+ return new Color(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * Creates a new <code>Color</code> instance from a color long.
+ * The resulting color is in the same color space as the specified color long.
+ *
+ * @param color The color long to create a <code>Color</code> from
+ * @return A non-null instance of {@link Color}
+ */
+ @NonNull
+ public static Color valueOf(@ColorLong long color) {
+ return new Color(red(color), green(color), blue(color), alpha(color), colorSpace(color));
+ }
+
+ /**
+ * Creates a new opaque <code>Color</code> in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space with the specified red, green and blue component values. The component
+ * values must be in the range \([0..1]\).
+ *
+ * @param r The red component of the opaque sRGB color to create, in \([0..1]\)
+ * @param g The green component of the opaque sRGB color to create, in \([0..1]\)
+ * @param b The blue component of the opaque sRGB color to create, in \([0..1]\)
+ * @return A non-null instance of {@link Color}
+ */
+ @NonNull
+ public static Color valueOf(float r, float g, float b) {
+ return new Color(r, g, b, 1.0f);
+ }
+
+ /**
+ * Creates a new <code>Color</code> in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space with the specified red, green, blue and alpha component values.
+ * The component values must be in the range \([0..1]\).
+ *
+ * @param r The red component of the sRGB color to create, in \([0..1]\)
+ * @param g The green component of the sRGB color to create, in \([0..1]\)
+ * @param b The blue component of the sRGB color to create, in \([0..1]\)
+ * @param a The alpha component of the sRGB color to create, in \([0..1]\)
+ * @return A non-null instance of {@link Color}
+ */
+ @NonNull
+ public static Color valueOf(float r, float g, float b, float a) {
+ return new Color(saturate(r), saturate(g), saturate(b), saturate(a));
+ }
+
+ /**
+ * Creates a new <code>Color</code> in the specified color space with the
+ * specified red, green, blue and alpha component values. The range of the
+ * components is defined by {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}. The values passed to this method
+ * must be in the proper range.
+ *
+ * @param r The red component of the color to create
+ * @param g The green component of the color to create
+ * @param b The blue component of the color to create
+ * @param a The alpha component of the color to create, in \([0..1]\)
+ * @param colorSpace The color space of the color to create
+ * @return A non-null instance of {@link Color}
+ *
+ * @throws IllegalArgumentException If the specified color space uses a
+ * color model with more than 3 components
+ */
+ @NonNull
+ public static Color valueOf(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
+ if (colorSpace.getComponentCount() > 3) {
+ throw new IllegalArgumentException("The specified color space must use a color model " +
+ "with at most 3 color components");
+ }
+ return new Color(r, g, b, a, colorSpace);
+ }
+
+ /**
+ * <p>Creates a new <code>Color</code> in the specified color space with the
+ * specified component values. The range of the components is defined by
+ * {@link ColorSpace#getMinValue(int)} and {@link ColorSpace#getMaxValue(int)}.
+ * The values passed to this method must be in the proper range. The alpha
+ * component is always in the range \([0..1]\).</p>
+ *
+ * <p>The length of the array of components must be at least
+ * <code>{@link ColorSpace#getComponentCount()} + 1</code>. The component at index
+ * {@link ColorSpace#getComponentCount()} is always alpha.</p>
+ *
+ * @param components The components of the color to create, with alpha as the last component
+ * @param colorSpace The color space of the color to create
+ * @return A non-null instance of {@link Color}
+ *
+ * @throws IllegalArgumentException If the array of components is smaller than
+ * required by the color space
+ */
+ @NonNull
+ public static Color valueOf(@NonNull @Size(min = 4, max = 5) float[] components,
+ @NonNull ColorSpace colorSpace) {
+ if (components.length < colorSpace.getComponentCount() + 1) {
+ throw new IllegalArgumentException("Received a component array of length " +
+ components.length + " but the color model requires " +
+ (colorSpace.getComponentCount() + 1) + " (including alpha)");
+ }
+ return new Color(Arrays.copyOf(components, colorSpace.getComponentCount() + 1), colorSpace);
+ }
+
+ /**
+ * Converts the specified ARGB color int to an RGBA color long in the sRGB
+ * color space. See the documentation of this class for a description of
+ * the color long format.
+ *
+ * @param color The ARGB color int to convert to an RGBA color long in sRGB
+ *
+ * @return A color long
+ */
+ @ColorLong
+ public static long pack(@ColorInt int color) {
+ return (color & 0xffffffffL) << 32;
+ }
+
+ /**
+ * Packs the sRGB color defined by the specified red, green and blue component
+ * values into an RGBA color long in the sRGB color space. The alpha component
+ * is set to 1.0. See the documentation of this class for a description of the
+ * color long format.
+ *
+ * @param red The red component of the sRGB color to create, in \([0..1]\)
+ * @param green The green component of the sRGB color to create, in \([0..1]\)
+ * @param blue The blue component of the sRGB color to create, in \([0..1]\)
+ *
+ * @return A color long
+ */
+ @ColorLong
+ public static long pack(float red, float green, float blue) {
+ return pack(red, green, blue, 1.0f, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * Packs the sRGB color defined by the specified red, green, blue and alpha
+ * component values into an RGBA color long in the sRGB color space. See the
+ * documentation of this class for a description of the color long format.
+ *
+ * @param red The red component of the sRGB color to create, in \([0..1]\)
+ * @param green The green component of the sRGB color to create, in \([0..1]\)
+ * @param blue The blue component of the sRGB color to create, in \([0..1]\)
+ * @param alpha The alpha component of the sRGB color to create, in \([0..1]\)
+ *
+ * @return A color long
+ */
+ @ColorLong
+ public static long pack(float red, float green, float blue, float alpha) {
+ return pack(red, green, blue, alpha, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * <p>Packs the 3 component color defined by the specified red, green, blue and
+ * alpha component values into a color long in the specified color space. See the
+ * documentation of this class for a description of the color long format.</p>
+ *
+ * <p>The red, green and blue components must be in the range defined by the
+ * specified color space. See {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}.</p>
+ *
+ * @param red The red component of the color to create
+ * @param green The green component of the color to create
+ * @param blue The blue component of the color to create
+ * @param alpha The alpha component of the color to create, in \([0..1]\)
+ *
+ * @return A color long
+ *
+ * @throws IllegalArgumentException If the color space's id is {@link ColorSpace#MIN_ID}
+ * or if the color space's color model has more than 3 components
+ */
+ @ColorLong
+ public static long pack(float red, float green, float blue, float alpha,
+ @NonNull ColorSpace colorSpace) {
+ if (colorSpace.isSrgb()) {
+ int argb =
+ ((int) (alpha * 255.0f + 0.5f) << 24) |
+ ((int) (red * 255.0f + 0.5f) << 16) |
+ ((int) (green * 255.0f + 0.5f) << 8) |
+ (int) (blue * 255.0f + 0.5f);
+ return (argb & 0xffffffffL) << 32;
+ }
+
+ int id = colorSpace.getId();
+ if (id == ColorSpace.MIN_ID) {
+ throw new IllegalArgumentException(
+ "Unknown color space, please use a color space returned by ColorSpace.get()");
+ }
+ if (colorSpace.getComponentCount() > 3) {
+ throw new IllegalArgumentException(
+ "The color space must use a color model with at most 3 components");
+ }
+
+ @HalfFloat short r = Half.valueOf(red);
+ @HalfFloat short g = Half.valueOf(green);
+ @HalfFloat short b = Half.valueOf(blue);
+
+ int a = (int) (Math.max(0.0f, Math.min(alpha, 1.0f)) * 1023.0f + 0.5f);
+
+ // Suppress sign extension
+ return (r & 0xffffL) << 48 |
+ (g & 0xffffL) << 32 |
+ (b & 0xffffL) << 16 |
+ (a & 0x3ffL ) << 6 |
+ id & 0x3fL;
+ }
+
+ /**
+ * Converts the specified ARGB color int from the {@link ColorSpace.Named#SRGB sRGB}
+ * color space into the specified destination color space. The resulting color is
+ * returned as a color long. See the documentation of this class for a description
+ * of the color long format.
+ *
+ * @param color The sRGB color int to convert
+ * @param colorSpace The destination color space
+ * @return A color long in the destination color space
+ */
+ @ColorLong
+ public static long convert(@ColorInt int color, @NonNull ColorSpace colorSpace) {
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color ) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+ ColorSpace source = ColorSpace.get(ColorSpace.Named.SRGB);
+ return convert(r, g, b, a, source, colorSpace);
+ }
+
+ /**
+ * <p>Converts the specified color long from its color space into the specified
+ * destination color space. The resulting color is returned as a color long. See
+ * the documentation of this class for a description of the color long format.</p>
+ *
+ * <p>When converting several colors in a row, it is recommended to use
+ * {@link #convert(long, ColorSpace.Connector)} instead to
+ * avoid the creation of a {@link ColorSpace.Connector} on every invocation.</p>
+ *
+ * @param color The color long to convert
+ * @param colorSpace The destination color space
+ * @return A color long in the destination color space
+ */
+ @ColorLong
+ public static long convert(@ColorLong long color, @NonNull ColorSpace colorSpace) {
+ float r = red(color);
+ float g = green(color);
+ float b = blue(color);
+ float a = alpha(color);
+ ColorSpace source = colorSpace(color);
+ return convert(r, g, b, a, source, colorSpace);
+ }
+
+ /**
+ * <p>Converts the specified 3 component color from the source color space to the
+ * destination color space. The resulting color is returned as a color long. See
+ * the documentation of this class for a description of the color long format.</p>
+ *
+ * <p>When converting multiple colors in a row, it is recommended to use
+ * {@link #convert(float, float, float, float, ColorSpace.Connector)} instead to
+ * avoid the creation of a {@link ColorSpace.Connector} on every invocation.</p>
+ *
+ * <p>The red, green and blue components must be in the range defined by the
+ * specified color space. See {@link ColorSpace#getMinValue(int)} and
+ * {@link ColorSpace#getMaxValue(int)}.</p>
+ *
+ * @param r The red component of the color to convert
+ * @param g The green component of the color to convert
+ * @param b The blue component of the color to convert
+ * @param a The alpha component of the color to convert, in \([0..1]\)
+ * @param source The source color space, cannot be null
+ * @param destination The destination color space, cannot be null
+ * @return A color long in the destination color space
+ *
+ * @see #convert(float, float, float, float, ColorSpace.Connector)
+ */
+ @ColorLong
+ public static long convert(float r, float g, float b, float a,
+ @NonNull ColorSpace source, @NonNull ColorSpace destination) {
+ float[] c = ColorSpace.connect(source, destination).transform(r, g, b);
+ return pack(c[0], c[1], c[2], a, destination);
+ }
+
+ /**
+ * <p>Converts the specified color long from a color space to another using the
+ * specified color space {@link ColorSpace.Connector connector}. The resulting
+ * color is returned as a color long. See the documentation of this class for a
+ * description of the color long format.</p>
+ *
+ * <p>When converting several colors in a row, this method is preferable to
+ * {@link #convert(long, ColorSpace)} as it prevents a new connector from being
+ * created on every invocation.</p>
+ *
+ * <p class="note">The connector's source color space should match the color long's
+ * color space.</p>
+ *
+ * @param color The color long to convert
+ * @param connector A color space connector, cannot be null
+ * @return A color long in the destination color space of the connector
+ */
+ @ColorLong
+ public static long convert(@ColorLong long color, @NonNull ColorSpace.Connector connector) {
+ float r = red(color);
+ float g = green(color);
+ float b = blue(color);
+ float a = alpha(color);
+ return convert(r, g, b, a, connector);
+ }
+
+ /**
+ * <p>Converts the specified 3 component color from a color space to another using
+ * the specified color space {@link ColorSpace.Connector connector}. The resulting
+ * color is returned as a color long. See the documentation of this class for a
+ * description of the color long format.</p>
+ *
+ * <p>When converting several colors in a row, this method is preferable to
+ * {@link #convert(float, float, float, float, ColorSpace, ColorSpace)} as
+ * it prevents a new connector from being created on every invocation.</p>
+ *
+ * <p>The red, green and blue components must be in the range defined by the
+ * source color space of the connector. See {@link ColorSpace#getMinValue(int)}
+ * and {@link ColorSpace#getMaxValue(int)}.</p>
+ *
+ * @param r The red component of the color to convert
+ * @param g The green component of the color to convert
+ * @param b The blue component of the color to convert
+ * @param a The alpha component of the color to convert, in \([0..1]\)
+ * @param connector A color space connector, cannot be null
+ * @return A color long in the destination color space of the connector
+ *
+ * @see #convert(float, float, float, float, ColorSpace, ColorSpace)
+ */
+ @ColorLong
+ public static long convert(float r, float g, float b, float a,
+ @NonNull ColorSpace.Connector connector) {
+ float[] c = connector.transform(r, g, b);
+ return pack(c[0], c[1], c[2], a, connector.getDestination());
+ }
+
+ /**
+ * <p>Returns the relative luminance of a color.</p>
+ *
+ * <p>Based on the formula for relative luminance defined in WCAG 2.0,
+ * W3C Recommendation 11 December 2008.</p>
+ *
+ * @return A value between 0 (darkest black) and 1 (lightest white)
+ *
+ * @throws IllegalArgumentException If the specified color's color space
+ * does not use the {@link ColorSpace.Model#RGB RGB} color model
+ */
+ public static float luminance(@ColorLong long color) {
+ ColorSpace colorSpace = colorSpace(color);
+ if (colorSpace.getModel() != ColorSpace.Model.RGB) {
+ throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
+ "color space. The supplied color space is " + colorSpace.getModel());
+ }
+
+ DoubleUnaryOperator eotf = ((ColorSpace.Rgb) colorSpace).getEotf();
+ double r = eotf.applyAsDouble(red(color));
+ double g = eotf.applyAsDouble(green(color));
+ double b = eotf.applyAsDouble(blue(color));
+
+ return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
+ }
+
+ private static float saturate(float v) {
+ return v <= 0.0f ? 0.0f : (v >= 1.0f ? 1.0f : v);
+ }
+
/**
* Return the alpha component of a color int. This is the same as saying
* color >>> 24
*/
+ @IntRange(from = 0, to = 255)
public static int alpha(int color) {
return color >>> 24;
}
@@ -63,6 +1209,7 @@
* Return the red component of a color int. This is the same as saying
* (color >> 16) & 0xFF
*/
+ @IntRange(from = 0, to = 255)
public static int red(int color) {
return (color >> 16) & 0xFF;
}
@@ -71,6 +1218,7 @@
* Return the green component of a color int. This is the same as saying
* (color >> 8) & 0xFF
*/
+ @IntRange(from = 0, to = 255)
public static int green(int color) {
return (color >> 8) & 0xFF;
}
@@ -79,41 +1227,86 @@
* Return the blue component of a color int. This is the same as saying
* color & 0xFF
*/
+ @IntRange(from = 0, to = 255)
public static int blue(int color) {
return color & 0xFF;
}
/**
* Return a color-int from red, green, blue components.
- * The alpha component is implicity 255 (fully opaque).
- * These component values should be [0..255], but there is no
+ * The alpha component is implicitly 255 (fully opaque).
+ * These component values should be \([0..255]\), but there is no
* range check performed, so if they are out of range, the
* returned color is undefined.
- * @param red Red component [0..255] of the color
- * @param green Green component [0..255] of the color
- * @param blue Blue component [0..255] of the color
+ *
+ * @param red Red component \([0..255]\) of the color
+ * @param green Green component \([0..255]\) of the color
+ * @param blue Blue component \([0..255]\) of the color
*/
@ColorInt
- public static int rgb(int red, int green, int blue) {
+ public static int rgb(
+ @IntRange(from = 0, to = 255) int red,
+ @IntRange(from = 0, to = 255) int green,
+ @IntRange(from = 0, to = 255) int blue) {
return 0xff000000 | (red << 16) | (green << 8) | blue;
}
/**
- * Return a color-int from alpha, red, green, blue components.
- * These component values should be [0..255], but there is no
- * range check performed, so if they are out of range, the
+ * Return a color-int from red, green, blue float components
+ * in the range \([0..1]\). The alpha component is implicitly
+ * 1.0 (fully opaque). If the components are out of range, the
* returned color is undefined.
- * @param alpha Alpha component [0..255] of the color
- * @param red Red component [0..255] of the color
- * @param green Green component [0..255] of the color
- * @param blue Blue component [0..255] of the color
+ *
+ * @param red Red component \([0..1]\) of the color
+ * @param green Green component \([0..1]\) of the color
+ * @param blue Blue component \([0..1]\) of the color
*/
@ColorInt
- public static int argb(int alpha, int red, int green, int blue) {
+ public static int rgb(float red, float green, float blue) {
+ return 0xff000000 |
+ ((int) (red * 255.0f + 0.5f) << 16) |
+ ((int) (green * 255.0f + 0.5f) << 8) |
+ (int) (blue * 255.0f + 0.5f);
+ }
+
+ /**
+ * Return a color-int from alpha, red, green, blue components.
+ * These component values should be \([0..255]\), but there is no
+ * range check performed, so if they are out of range, the
+ * returned color is undefined.
+ * @param alpha Alpha component \([0..255]\) of the color
+ * @param red Red component \([0..255]\) of the color
+ * @param green Green component \([0..255]\) of the color
+ * @param blue Blue component \([0..255]\) of the color
+ */
+ @ColorInt
+ public static int argb(
+ @IntRange(from = 0, to = 255) int alpha,
+ @IntRange(from = 0, to = 255) int red,
+ @IntRange(from = 0, to = 255) int green,
+ @IntRange(from = 0, to = 255) int blue) {
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
/**
+ * Return a color-int from alpha, red, green, blue float components
+ * in the range \([0..1]\). If the components are out of range, the
+ * returned color is undefined.
+ *
+ * @param alpha Alpha component \([0..1]\) of the color
+ * @param red Red component \([0..1]\) of the color
+ * @param green Green component \([0..1]\) of the color
+ * @param blue Blue component \([0..1]\) of the color
+ */
+ @ColorInt
+ public static int argb(float alpha, float red, float green, float blue) {
+ return ((int) (alpha * 255.0f + 0.5f) << 24) |
+ ((int) (red * 255.0f + 0.5f) << 16) |
+ ((int) (green * 255.0f + 0.5f) << 8) |
+ (int) (blue * 255.0f + 0.5f);
+ }
+
+ /**
* Returns the relative luminance of a color.
* <p>
* Assumes sRGB encoding. Based on the formula for relative luminance
@@ -124,23 +1317,31 @@
public static float luminance(@ColorInt int color) {
ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
DoubleUnaryOperator eotf = cs.getEotf();
- double red = eotf.applyAsDouble(Color.red(color) / 255.0);
- double green = eotf.applyAsDouble(Color.green(color) / 255.0);
- double blue = eotf.applyAsDouble(Color.blue(color) / 255.0);
- return (float) ((0.2126 * red) + (0.7152 * green) + (0.0722 * blue));
+
+ double r = eotf.applyAsDouble(red(color) / 255.0);
+ double g = eotf.applyAsDouble(green(color) / 255.0);
+ double b = eotf.applyAsDouble(blue(color) / 255.0);
+
+ return (float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b));
}
/**
- * Parse the color string, and return the corresponding color-int.
+ * </p>Parse the color string, and return the corresponding color-int.
* If the string cannot be parsed, throws an IllegalArgumentException
- * exception. Supported formats are:
- * #RRGGBB
- * #AARRGGBB
- * or one of the following names:
- * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta',
- * 'yellow', 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey',
- * 'aqua', 'fuchsia', 'lime', 'maroon', 'navy', 'olive', 'purple',
- * 'silver', 'teal'.
+ * exception. Supported formats are:</p>
+ *
+ * <ul>
+ * <li><code>#RRGGBB</code></li>
+ * <li><code>#AARRGGBB</code></li>
+ * </ul>
+ *
+ * <p>The following names are also accepted: <code>red</code>, <code>blue</code>,
+ * <code>green</code>, <code>black</code>, <code>white</code>, <code>gray</code>,
+ * <code>cyan</code>, <code>magenta</code>, <code>yellow</code>, <code>lightgray</code>,
+ * <code>darkgray</code>, <code>grey</code>, <code>lightgrey</code>, <code>darkgrey</code>,
+ * <code>aqua</code>, <code>fuchsia</code>, <code>lime</code>, <code>maroon</code>,
+ * <code>navy</code>, <code>olive</code>, <code>purple</code>, <code>silver</code>,
+ * and <code>teal</code>.</p>
*/
@ColorInt
public static int parseColor(@Size(min=1) String colorString) {
@@ -165,15 +1366,20 @@
/**
* Convert RGB components to HSV.
- * hsv[0] is Hue [0 .. 360)
- * hsv[1] is Saturation [0...1]
- * hsv[2] is Value [0...1]
- * @param red red component value [0..255]
- * @param green green component value [0..255]
- * @param blue blue component value [0..255]
+ * <ul>
+ * <li><code>hsv[0]</code> is Hue \([0..360[\)</li>
+ * <li><code>hsv[1]</code> is Saturation \([0...1]\)</li>
+ * <li><code>hsv[2]</code> is Value \([0...1]\)</li>
+ * </ul>
+ * @param red red component value \([0..255]\)
+ * @param green green component value \([0..255]\)
+ * @param blue blue component value \([0..255]\)
* @param hsv 3 element array which holds the resulting HSV components.
*/
- public static void RGBToHSV(int red, int green, int blue, @Size(3) float hsv[]) {
+ public static void RGBToHSV(
+ @IntRange(from = 0, to = 255) int red,
+ @IntRange(from = 0, to = 255) int green,
+ @IntRange(from = 0, to = 255) int blue, @Size(3) float hsv[]) {
if (hsv.length < 3) {
throw new RuntimeException("3 components required for hsv");
}
@@ -181,10 +1387,12 @@
}
/**
- * Convert the argb color to its HSV components.
- * hsv[0] is Hue [0 .. 360)
- * hsv[1] is Saturation [0...1]
- * hsv[2] is Value [0...1]
+ * Convert the ARGB color to its HSV components.
+ * <ul>
+ * <li><code>hsv[0]</code> is Hue \([0..360[\)</li>
+ * <li><code>hsv[1]</code> is Saturation \([0...1]\)</li>
+ * <li><code>hsv[2]</code> is Value \([0...1]\)</li>
+ * </ul>
* @param color the argb color to convert. The alpha component is ignored.
* @param hsv 3 element array which holds the resulting HSV components.
*/
@@ -194,13 +1402,16 @@
/**
* Convert HSV components to an ARGB color. Alpha set to 0xFF.
- * hsv[0] is Hue [0 .. 360)
- * hsv[1] is Saturation [0...1]
- * hsv[2] is Value [0...1]
+ * <ul>
+ * <li><code>hsv[0]</code> is Hue \([0..360[\)</li>
+ * <li><code>hsv[1]</code> is Saturation \([0...1]\)</li>
+ * <li><code>hsv[2]</code> is Value \([0...1]\)</li>
+ * </ul>
* If hsv values are out of range, they are pinned.
* @param hsv 3 element array which holds the input HSV components.
* @return the resulting argb color
*/
+ @ColorInt
public static int HSVToColor(@Size(3) float hsv[]) {
return HSVToColor(0xFF, hsv);
}
@@ -208,15 +1419,18 @@
/**
* Convert HSV components to an ARGB color. The alpha component is passed
* through unchanged.
- * hsv[0] is Hue [0 .. 360)
- * hsv[1] is Saturation [0...1]
- * hsv[2] is Value [0...1]
+ * <ul>
+ * <li><code>hsv[0]</code> is Hue \([0..360[\)</li>
+ * <li><code>hsv[1]</code> is Saturation \([0...1]\)</li>
+ * <li><code>hsv[2]</code> is Value \([0...1]\)</li>
+ * </ul>
* If hsv values are out of range, they are pinned.
* @param alpha the alpha component of the returned argb color.
* @param hsv 3 element array which holds the input HSV components.
* @return the resulting argb color
- */
- public static int HSVToColor(int alpha, @Size(3) float hsv[]) {
+ */
+ @ColorInt
+ public static int HSVToColor(@IntRange(from = 0, to = 255) int alpha, @Size(3) float hsv[]) {
if (hsv.length < 3) {
throw new RuntimeException("3 components required for hsv");
}
@@ -236,7 +1450,7 @@
* @hide
*/
@ColorInt
- public static int getHtmlColor(String color) {
+ public static int getHtmlColor(@NonNull String color) {
Integer i = sColorNameMap.get(color.toLowerCase(Locale.ROOT));
if (i != null) {
return i;
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index d968516..ec00c45 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.AnyThread;
import android.annotation.ColorInt;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -126,7 +127,8 @@
*
* <p>To visualize and debug color spaces, you can call {@link #createRenderer()}.
* The {@link Renderer} created by calling this method can be used to compare
- * color spaces and locate specific colors on a CIE 1931 chromaticity diagram.</p>
+ * color spaces and locate specific colors on a CIE 1931 or CIE 1976 UCS
+ * chromaticity diagram.</p>
*
* <p>The following code snippet shows how to render a bitmap that compares
* the color gamuts and white points of {@link Named#DCI_P3} and
@@ -155,6 +157,7 @@
* @see Adaptation
* @see Renderer
*/
+@AnyThread
@SuppressWarnings("StaticInitializerReferencesSubClass")
public abstract class ColorSpace {
/**
@@ -216,7 +219,7 @@
*
* @see #getId()
*/
- public static final int MAX_ID = 64; // Do not change, used to encode in longs
+ public static final int MAX_ID = 63; // Do not change, used to encode in longs
private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
@@ -341,11 +344,11 @@
* \end{equation}\)
* </td>
* </tr>
- * <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr>
+ * <tr><td>Range</td><td colspan="4">\([-0.799..2.399[\)</td></tr>
* </table>
* <p>
* <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
- * <figcaption style="text-align: center;">Extended RGB (orange) vs sRGB (white)</figcaption>
+ * <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption>
* </p>
*/
EXTENDED_SRGB,
@@ -368,11 +371,11 @@
* <td>Electro-optical transfer function</td>
* <td colspan="4">\(C_{linear} = C_{scRGB}\)</td>
* </tr>
- * <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr>
+ * <tr><td>Range</td><td colspan="4">\([-0.5..7.499[\)</td></tr>
* </table>
* <p>
* <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
- * <figcaption style="text-align: center;">Extended RGB (orange) vs sRGB (white)</figcaption>
+ * <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption>
* </p>
*/
LINEAR_EXTENDED_SRGB,
@@ -1090,7 +1093,7 @@
* space's color model. The resulting value is passed back in the specified
* array.</p>
*
- * <p class="note>The specified array's length must be at least equal to
+ * <p class="note">The specified array's length must be at least equal to
* to the number of color components as returned by
* {@link Model#getComponentCount()}, and its first 3 values must
* be the XYZ components to convert from.</p>
@@ -1125,6 +1128,7 @@
* @return A string representation of the object
*/
@Override
+ @NonNull
public String toString() {
return mName + " (id=" + mId + ", model=" + mModel + ")";
}
@@ -1403,7 +1407,7 @@
ILLUMINANT_D65,
x -> absRcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
x -> absResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
- -0.5f, 7.5f,
+ -0.799f, 2.399f,
Named.EXTENDED_SRGB.ordinal()
);
sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
@@ -1412,7 +1416,7 @@
ILLUMINANT_D65,
DoubleUnaryOperator.identity(),
DoubleUnaryOperator.identity(),
- -0.5f, 7.5f,
+ -0.5f, 7.499f,
Named.LINEAR_EXTENDED_SRGB.ordinal()
);
sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
@@ -1437,8 +1441,8 @@
"SMPTE RP 431-2-2007 DCI (P3)",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
new float[] { 0.314f, 0.351f },
- x -> Math.pow(x, 1 / 2.6),
- x -> Math.pow(x, 2.6),
+ x -> Math.pow(x < 0.0f ? 0.0f : x, 1 / 2.6),
+ x -> Math.pow(x < 0.0f ? 0.0f : x, 2.6),
0.0f, 1.0f,
Named.DCI_P3.ordinal()
);
@@ -1473,8 +1477,8 @@
"Adobe RGB (1998)",
new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
ILLUMINANT_D65,
- x -> Math.pow(x, 1 / 2.2),
- x -> Math.pow(x, 2.2),
+ x -> Math.pow(x < 0.0f ? 0.0f : x, 1 / 2.2),
+ x -> Math.pow(x < 0.0f ? 0.0f : x, 2.2),
0.0f, 1.0f,
Named.ADOBE_RGB.ordinal()
);
@@ -1720,6 +1724,7 @@
/**
* Implementation of the CIE XYZ color space. Assumes the white point is D50.
*/
+ @AnyThread
private static final class Xyz extends ColorSpace {
private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
super(name, Model.XYZ, id);
@@ -1765,6 +1770,7 @@
* Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
* with a white point of D50.
*/
+ @AnyThread
private static final class Lab extends ColorSpace {
private static final float A = 216.0f / 24389.0f;
private static final float B = 841.0f / 108.0f;
@@ -1949,6 +1955,7 @@
* <p>To learn more about the white point adaptation process, refer to the
* documentation of {@link Adaptation}.</p>
*/
+ @AnyThread
public static class Rgb extends ColorSpace {
@NonNull private final float[] mWhitePoint;
@NonNull private final float[] mPrimaries;
@@ -2337,7 +2344,7 @@
* to "gamma space" (gamma encoded). The terms gamma space and gamma encoded
* are frequently used because many OETFs can be closely approximated using
* a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the
- * approximation of the {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\)
+ * approximation of the {@link Named#SRGB sRGB} OETF uses \(\gamma=2.2\)
* for instance).</p>
*
* @return A transfer function that converts from linear space to "gamma space"
@@ -2346,7 +2353,7 @@
*/
@NonNull
public DoubleUnaryOperator getOetf() {
- return mOetf;
+ return mClampedOetf;
}
/**
@@ -2369,7 +2376,7 @@
*/
@NonNull
public DoubleUnaryOperator getEotf() {
- return mEotf;
+ return mClampedEotf;
}
@Override
@@ -2924,6 +2931,7 @@
* @see ColorSpace#connect(ColorSpace, RenderIntent)
* @see ColorSpace#connect(ColorSpace)
*/
+ @AnyThread
public static class Connector {
@NonNull private final ColorSpace mSource;
@NonNull private final ColorSpace mDestination;
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 5e72a0d..0a814f3 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -18,7 +18,8 @@
# Build the legacy-test library
# =============================
-# This contains the junit.framework classes that were in Android API level 25.
+# This contains the junit.framework and android.test classes that were in
+# Android API level 25.
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -28,6 +29,18 @@
include $(BUILD_JAVA_LIBRARY)
+# Build the legacy-android-test library
+# =============================
+# This contains the android.test classes that were in Android API level 25.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/android)
+LOCAL_MODULE := legacy-android-test
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework junit
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
ifeq ($(HOST_OS),linux)
# Build the legacy-performance-test-hostdex library
# =================================================
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index d501d25..fb89835 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -24,10 +24,14 @@
"-Wunreachable-code",
],
srcs: [
+ "ApkAssets.cpp",
"Asset.cpp",
"AssetDir.cpp",
"AssetManager.cpp",
+ "AssetManager2.cpp",
"AttributeResolution.cpp",
+ "ChunkIterator.cpp",
+ "LoadedArsc.cpp",
"LocaleData.cpp",
"misc.cpp",
"ObbFile.cpp",
@@ -65,7 +69,16 @@
shared: {
enabled: false,
},
- shared_libs: ["libz-host"],
+ static_libs: [
+ "libziparchive",
+ "libbase",
+ "liblog",
+ "libcutils",
+ "libutils",
+ ],
+ shared_libs: [
+ "libz-host",
+ ],
},
windows: {
enabled: true,
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
new file mode 100644
index 0000000..55f4c3c
--- /dev/null
+++ b/libs/androidfw/ApkAssets.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include "androidfw/ApkAssets.h"
+
+#include "android-base/logging.h"
+#include "utils/Trace.h"
+#include "ziparchive/zip_archive.h"
+
+#include "androidfw/Asset.h"
+#include "androidfw/Util.h"
+
+namespace android {
+
+std::unique_ptr<ApkAssets> ApkAssets::Load(const std::string& path) {
+ ATRACE_NAME("ApkAssets::Load");
+ ::ZipArchiveHandle unmanaged_handle;
+ int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
+ if (result != 0) {
+ LOG(ERROR) << ::ErrorCodeString(result);
+ return {};
+ }
+
+ // Wrap the handle in a unique_ptr so it gets automatically closed.
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets());
+ loaded_apk->zip_handle_.reset(unmanaged_handle);
+
+ ::ZipString entry_name("resources.arsc");
+ ::ZipEntry entry;
+ result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);
+ if (result != 0) {
+ LOG(ERROR) << ::ErrorCodeString(result);
+ return {};
+ }
+
+ if (entry.method == kCompressDeflated) {
+ LOG(WARNING) << "resources.arsc is compressed.";
+ }
+
+ loaded_apk->resources_asset_ =
+ loaded_apk->Open("resources.arsc", Asset::AccessMode::ACCESS_BUFFER);
+ if (loaded_apk->resources_asset_ == nullptr) {
+ return {};
+ }
+
+ loaded_apk->path_ = path;
+ loaded_apk->loaded_arsc_ =
+ LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/),
+ loaded_apk->resources_asset_->getLength());
+ if (loaded_apk->loaded_arsc_ == nullptr) {
+ return {};
+ }
+ return loaded_apk;
+}
+
+std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode /*mode*/) const {
+ ATRACE_NAME("ApkAssets::Open");
+ CHECK(zip_handle_ != nullptr);
+
+ ::ZipString name(path.c_str());
+ ::ZipEntry entry;
+ int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
+ if (result != 0) {
+ LOG(ERROR) << "No entry '" << path << "' found in APK.";
+ return {};
+ }
+
+ if (entry.method == kCompressDeflated) {
+ auto compressed_asset = util::make_unique<_CompressedAsset>();
+ if (compressed_asset->openChunk(::GetFileDescriptor(zip_handle_.get()), entry.offset,
+ entry.method, entry.uncompressed_length,
+ entry.compressed_length) != NO_ERROR) {
+ LOG(ERROR) << "Failed to decompress '" << path << "'.";
+ return {};
+ }
+ return std::move(compressed_asset);
+ } else {
+ auto uncompressed_asset = util::make_unique<_FileAsset>();
+ if (uncompressed_asset->openChunk(path.c_str(), ::GetFileDescriptor(zip_handle_.get()),
+ entry.offset, entry.uncompressed_length) != NO_ERROR) {
+ LOG(ERROR) << "Failed to mmap '" << path << "'.";
+ return {};
+ }
+ return std::move(uncompressed_asset);
+ }
+ return {};
+}
+
+} // namespace android
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
new file mode 100644
index 0000000..8d65925
--- /dev/null
+++ b/libs/androidfw/AssetManager2.cpp
@@ -0,0 +1,576 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include "androidfw/AssetManager2.h"
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "utils/ByteOrder.h"
+#include "utils/Trace.h"
+
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+namespace android {
+
+AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); }
+
+bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
+ bool invalidate_caches) {
+ apk_assets_ = apk_assets;
+ if (invalidate_caches) {
+ InvalidateCaches(static_cast<uint32_t>(-1));
+ }
+ return true;
+}
+
+const std::vector<const ApkAssets*> AssetManager2::GetApkAssets() const { return apk_assets_; }
+
+const ResStringPool* AssetManager2::GetStringPoolForCookie(ApkAssetsCookie cookie) const {
+ if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
+ return nullptr;
+ }
+ return apk_assets_[cookie]->GetLoadedArsc()->GetStringPool();
+}
+
+void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
+ const int diff = configuration_.diff(configuration);
+ configuration_ = configuration;
+
+ if (diff) {
+ InvalidateCaches(static_cast<uint32_t>(diff));
+ }
+}
+
+const ResTable_config& AssetManager2::GetConfiguration() const { return configuration_; }
+
+std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) {
+ const std::string new_path = "assets/" + filename;
+ return OpenNonAsset(new_path, mode);
+}
+
+std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie,
+ Asset::AccessMode mode) {
+ const std::string new_path = "assets/" + filename;
+ return OpenNonAsset(new_path, cookie, mode);
+}
+
+// Search in reverse because that's how we used to do it and we need to preserve behaviour.
+// This is unfortunate, because ClassLoaders delegate to the parent first, so the order
+// is inconsistent for split APKs.
+std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
+ Asset::AccessMode mode,
+ ApkAssetsCookie* out_cookie) {
+ ATRACE_CALL();
+ for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
+ std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
+ if (asset) {
+ if (out_cookie != nullptr) {
+ *out_cookie = i;
+ }
+ return asset;
+ }
+ }
+
+ if (out_cookie != nullptr) {
+ *out_cookie = kInvalidCookie;
+ }
+ return {};
+}
+
+std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
+ ApkAssetsCookie cookie, Asset::AccessMode mode) {
+ ATRACE_CALL();
+ if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
+ return {};
+ }
+ return apk_assets_[cookie]->Open(filename, mode);
+}
+
+ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
+ bool stop_at_first_match, LoadedArsc::Entry* out_entry,
+ ResTable_config* out_selected_config,
+ uint32_t* out_flags) {
+ ATRACE_CALL();
+
+ // Might use this if density_override != 0.
+ ResTable_config density_override_config;
+
+ // Select our configuration or generate a density override configuration.
+ ResTable_config* desired_config = &configuration_;
+ if (density_override != 0 && density_override != configuration_.density) {
+ density_override_config = configuration_;
+ density_override_config.density = density_override;
+ desired_config = &density_override_config;
+ }
+
+ LoadedArsc::Entry best_entry;
+ ResTable_config best_config;
+ int32_t best_index = -1;
+ uint32_t cumulated_flags = 0;
+
+ const size_t apk_asset_count = apk_assets_.size();
+ for (size_t i = 0; i < apk_asset_count; i++) {
+ const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();
+
+ LoadedArsc::Entry current_entry;
+ ResTable_config current_config;
+ uint32_t flags = 0;
+ if (!loaded_arsc->FindEntry(resid, *desired_config, ¤t_entry, ¤t_config, &flags)) {
+ continue;
+ }
+
+ cumulated_flags |= flags;
+
+ if (best_index == -1 || current_config.isBetterThan(best_config, desired_config)) {
+ best_entry = current_entry;
+ best_config = current_config;
+ best_index = static_cast<int32_t>(i);
+ if (stop_at_first_match) {
+ break;
+ }
+ }
+ }
+
+ if (best_index == -1) {
+ return kInvalidCookie;
+ }
+
+ *out_entry = best_entry;
+ *out_selected_config = best_config;
+ *out_flags = cumulated_flags;
+ return best_index;
+}
+
+bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
+ ATRACE_CALL();
+
+ LoadedArsc::Entry entry;
+ ResTable_config config;
+ uint32_t flags = 0u;
+ ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
+ true /* stop_at_first_match */, &entry, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ return false;
+ }
+
+ const std::string* package_name =
+ apk_assets_[cookie]->GetLoadedArsc()->GetPackageNameForId(resid);
+ if (package_name == nullptr) {
+ return false;
+ }
+
+ out_name->package = package_name->data();
+ out_name->package_len = package_name->size();
+
+ out_name->type = entry.type_string_ref.string8(&out_name->type_len);
+ out_name->type16 = nullptr;
+ if (out_name->type == nullptr) {
+ out_name->type16 = entry.type_string_ref.string16(&out_name->type_len);
+ if (out_name->type16 == nullptr) {
+ return false;
+ }
+ }
+
+ out_name->entry = entry.entry_string_ref.string8(&out_name->entry_len);
+ out_name->entry16 = nullptr;
+ if (out_name->entry == nullptr) {
+ out_name->entry16 = entry.entry_string_ref.string16(&out_name->entry_len);
+ if (out_name->entry16 == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
+ LoadedArsc::Entry entry;
+ ResTable_config config;
+ ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
+ false /* stop_at_first_match */, &entry, &config, out_flags);
+ return cookie != kInvalidCookie;
+}
+
+ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
+ uint16_t density_override, Res_value* out_value,
+ ResTable_config* out_selected_config,
+ uint32_t* out_flags) {
+ ATRACE_CALL();
+
+ LoadedArsc::Entry entry;
+ ResTable_config config;
+ uint32_t flags = 0u;
+ ApkAssetsCookie cookie =
+ FindEntry(resid, density_override, false /* stop_at_first_match */, &entry, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ return kInvalidCookie;
+ }
+
+ if (dtohl(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) {
+ if (!may_be_bag) {
+ LOG(ERROR) << base::StringPrintf("Resource %08x is a complex map type.", resid);
+ }
+ return kInvalidCookie;
+ }
+
+ const Res_value* device_value = reinterpret_cast<const Res_value*>(
+ reinterpret_cast<const uint8_t*>(entry.entry) + dtohs(entry.entry->size));
+ out_value->copyFrom_dtoh(*device_value);
+ *out_selected_config = config;
+ *out_flags = flags;
+ return cookie;
+}
+
+const ResolvedBag* AssetManager2::GetBag(uint32_t resid) {
+ ATRACE_CALL();
+
+ auto cached_iter = cached_bags_.find(resid);
+ if (cached_iter != cached_bags_.end()) {
+ return cached_iter->second.get();
+ }
+
+ LoadedArsc::Entry entry;
+ ResTable_config config;
+ uint32_t flags = 0u;
+ ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
+ false /* stop_at_first_match */, &entry, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ return nullptr;
+ }
+
+ // Check that the size of the entry header is at least as big as
+ // the desired ResTable_map_entry. Also verify that the entry
+ // was intended to be a map.
+ if (dtohs(entry.entry->size) < sizeof(ResTable_map_entry) ||
+ (dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) == 0) {
+ // Not a bag, nothing to do.
+ return nullptr;
+ }
+
+ const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry.entry);
+ const ResTable_map* map_entry =
+ reinterpret_cast<const ResTable_map*>(reinterpret_cast<const uint8_t*>(map) + map->size);
+ const ResTable_map* const map_entry_end = map_entry + dtohl(map->count);
+
+ const uint32_t parent = dtohl(map->parent.ident);
+ if (parent == 0) {
+ // There is no parent, meaning there is nothing to inherit and we can do a simple
+ // copy of the entries in the map.
+ const size_t entry_count = map_entry_end - map_entry;
+ util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>(
+ malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))};
+ ResolvedBag::Entry* new_entry = new_bag->entries;
+ for (; map_entry != map_entry_end; ++map_entry) {
+ new_entry->cookie = cookie;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ new_entry->key = dtohl(map_entry->name.ident);
+ new_entry->key_pool = nullptr;
+ new_entry->type_pool = nullptr;
+ ++new_entry;
+ }
+ new_bag->type_spec_flags = flags;
+ new_bag->entry_count = static_cast<uint32_t>(entry_count);
+ ResolvedBag* result = new_bag.get();
+ cached_bags_[resid] = std::move(new_bag);
+ return result;
+ }
+
+ // Get the parent and do a merge of the keys.
+ const ResolvedBag* parent_bag = GetBag(parent);
+ if (parent_bag == nullptr) {
+ // Failed to get the parent that should exist.
+ return nullptr;
+ }
+
+ // Combine flags from the parent and our own bag.
+ flags |= parent_bag->type_spec_flags;
+
+ // Create the max possible entries we can make. Once we construct the bag,
+ // we will realloc to fit to size.
+ const size_t max_count = parent_bag->entry_count + dtohl(map->count);
+ ResolvedBag* new_bag = reinterpret_cast<ResolvedBag*>(
+ malloc(sizeof(ResolvedBag) + (max_count * sizeof(ResolvedBag::Entry))));
+ ResolvedBag::Entry* new_entry = new_bag->entries;
+
+ const ResolvedBag::Entry* parent_entry = parent_bag->entries;
+ const ResolvedBag::Entry* const parent_entry_end = parent_entry + parent_bag->entry_count;
+
+ // The keys are expected to be in sorted order. Merge the two bags.
+ while (map_entry != map_entry_end && parent_entry != parent_entry_end) {
+ const uint32_t child_key = dtohl(map_entry->name.ident);
+ if (child_key <= parent_entry->key) {
+ // Use the child key if it comes before the parent
+ // or is equal to the parent (overrides).
+ new_entry->cookie = cookie;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ new_entry->key = child_key;
+ new_entry->key_pool = nullptr;
+ new_entry->type_pool = nullptr;
+ ++map_entry;
+ } else {
+ // Take the parent entry as-is.
+ memcpy(new_entry, parent_entry, sizeof(*new_entry));
+ }
+
+ if (child_key >= parent_entry->key) {
+ // Move to the next parent entry if we used it or it was overridden.
+ ++parent_entry;
+ }
+ // Increment to the next entry to fill.
+ ++new_entry;
+ }
+
+ // Finish the child entries if they exist.
+ while (map_entry != map_entry_end) {
+ new_entry->cookie = cookie;
+ new_entry->value.copyFrom_dtoh(map_entry->value);
+ new_entry->key = dtohl(map_entry->name.ident);
+ new_entry->key_pool = nullptr;
+ new_entry->type_pool = nullptr;
+ ++map_entry;
+ ++new_entry;
+ }
+
+ // Finish the parent entries if they exist.
+ if (parent_entry != parent_entry_end) {
+ // Take the rest of the parent entries as-is.
+ const size_t num_entries_to_copy = parent_entry_end - parent_entry;
+ memcpy(new_entry, parent_entry, num_entries_to_copy * sizeof(*new_entry));
+ new_entry += num_entries_to_copy;
+ }
+
+ // Resize the resulting array to fit.
+ const size_t actual_count = new_entry - new_bag->entries;
+ if (actual_count != max_count) {
+ new_bag = reinterpret_cast<ResolvedBag*>(
+ realloc(new_bag, sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry))));
+ }
+
+ util::unique_cptr<ResolvedBag> final_bag{new_bag};
+ final_bag->type_spec_flags = flags;
+ final_bag->entry_count = static_cast<uint32_t>(actual_count);
+ ResolvedBag* result = final_bag.get();
+ cached_bags_[resid] = std::move(final_bag);
+ return result;
+}
+
+void AssetManager2::InvalidateCaches(uint32_t diff) {
+ if (diff == 0xffffffffu) {
+ // Everything must go.
+ cached_bags_.clear();
+ return;
+ }
+
+ // Be more conservative with what gets purged. Only if the bag has other possible
+ // variations with respect to what changed (diff) should we remove it.
+ for (auto iter = cached_bags_.cbegin(); iter != cached_bags_.cend();) {
+ if (diff & iter->second->type_spec_flags) {
+ iter = cached_bags_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); }
+
+bool Theme::ApplyStyle(uint32_t resid, bool force) {
+ ATRACE_CALL();
+
+ const ResolvedBag* bag = asset_manager_->GetBag(resid);
+ if (bag == nullptr) {
+ return false;
+ }
+
+ // Merge the flags from this style.
+ type_spec_flags_ |= bag->type_spec_flags;
+
+ // On the first iteration, verify the attribute IDs and
+ // update the entry count in each type.
+ const auto bag_iter_end = end(bag);
+ for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
+ const uint32_t attr_resid = bag_iter->key;
+
+ // If the resource ID passed in is not a style, the key can be
+ // some other identifier that is not a resource ID.
+ if (!util::is_valid_resid(attr_resid)) {
+ return false;
+ }
+
+ const uint32_t package_idx = util::get_package_id(attr_resid);
+
+ // The type ID is 1-based, so subtract 1 to get an index.
+ const uint32_t type_idx = util::get_type_id(attr_resid) - 1;
+ const uint32_t entry_idx = util::get_entry_id(attr_resid);
+
+ std::unique_ptr<Package>& package = packages_[package_idx];
+ if (package == nullptr) {
+ package.reset(new Package());
+ }
+
+ util::unique_cptr<Type>& type = package->types[type_idx];
+ if (type == nullptr) {
+ // Set the initial capacity to take up a total amount of 1024 bytes.
+ constexpr uint32_t kInitialCapacity = (1024u - sizeof(Type)) / sizeof(Entry);
+ const uint32_t initial_capacity = std::max(entry_idx, kInitialCapacity);
+ type.reset(
+ reinterpret_cast<Type*>(calloc(sizeof(Type) + (initial_capacity * sizeof(Entry)), 1)));
+ type->entry_capacity = initial_capacity;
+ }
+
+ // Set the entry_count to include this entry. We will populate
+ // and resize the array as necessary in the next pass.
+ if (entry_idx + 1 > type->entry_count) {
+ // Increase the entry count to include this.
+ type->entry_count = entry_idx + 1;
+ }
+ }
+
+ // On the second pass, we will realloc to fit the entry counts
+ // and populate the structures.
+ for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
+ const uint32_t attr_resid = bag_iter->key;
+ const uint32_t package_idx = util::get_package_id(attr_resid);
+ const uint32_t type_idx = util::get_type_id(attr_resid) - 1;
+ const uint32_t entry_idx = util::get_entry_id(attr_resid);
+ Package* package = packages_[package_idx].get();
+ util::unique_cptr<Type>& type = package->types[type_idx];
+ if (type->entry_count != type->entry_capacity) {
+ // Resize to fit the actual entries that will be included.
+ Type* type_ptr = type.release();
+ type.reset(reinterpret_cast<Type*>(
+ realloc(type_ptr, sizeof(Type) + (type_ptr->entry_count * sizeof(Entry)))));
+ if (type->entry_capacity < type->entry_count) {
+ // Clear the newly allocated memory (which does not get zero initialized).
+ // We need to do this because we |= type_spec_flags.
+ memset(type->entries + type->entry_capacity, 0,
+ sizeof(Entry) * (type->entry_count - type->entry_capacity));
+ }
+ type->entry_capacity = type->entry_count;
+ }
+ Entry& entry = type->entries[entry_idx];
+ if (force || entry.value.dataType == Res_value::TYPE_NULL) {
+ entry.cookie = bag_iter->cookie;
+ entry.type_spec_flags |= bag->type_spec_flags;
+ entry.value = bag_iter->value;
+ }
+ }
+ return true;
+}
+
+ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value,
+ uint32_t* out_flags) const {
+ constexpr const int kMaxIterations = 20;
+
+ uint32_t type_spec_flags = 0u;
+
+ for (int iterations_left = kMaxIterations; iterations_left > 0; iterations_left--) {
+ if (!util::is_valid_resid(resid)) {
+ return kInvalidCookie;
+ }
+
+ const uint32_t package_idx = util::get_package_id(resid);
+
+ // Type ID is 1-based, subtract 1 to get the index.
+ const uint32_t type_idx = util::get_type_id(resid) - 1;
+ const uint32_t entry_idx = util::get_entry_id(resid);
+
+ const Package* package = packages_[package_idx].get();
+ if (package == nullptr) {
+ return kInvalidCookie;
+ }
+
+ const Type* type = package->types[type_idx].get();
+ if (type == nullptr) {
+ return kInvalidCookie;
+ }
+
+ if (entry_idx >= type->entry_count) {
+ return kInvalidCookie;
+ }
+
+ const Entry& entry = type->entries[entry_idx];
+ type_spec_flags |= entry.type_spec_flags;
+
+ switch (entry.value.dataType) {
+ case Res_value::TYPE_ATTRIBUTE:
+ resid = entry.value.data;
+ break;
+
+ case Res_value::TYPE_NULL:
+ return kInvalidCookie;
+
+ default:
+ *out_value = entry.value;
+ if (out_flags != nullptr) {
+ *out_flags = type_spec_flags;
+ }
+ return entry.cookie;
+ }
+ }
+
+ LOG(WARNING) << base::StringPrintf("Too many (%d) attribute references, stopped at: 0x%08x",
+ kMaxIterations, resid);
+ return kInvalidCookie;
+}
+
+void Theme::Clear() {
+ type_spec_flags_ = 0u;
+ for (std::unique_ptr<Package>& package : packages_) {
+ package.reset();
+ }
+}
+
+bool Theme::SetTo(const Theme& o) {
+ if (this == &o) {
+ return true;
+ }
+
+ if (asset_manager_ != o.asset_manager_) {
+ return false;
+ }
+
+ type_spec_flags_ = o.type_spec_flags_;
+
+ for (size_t p = 0; p < arraysize(packages_); p++) {
+ const Package* package = o.packages_[p].get();
+ if (package == nullptr) {
+ packages_[p].reset();
+ continue;
+ }
+
+ for (size_t t = 0; t < arraysize(package->types); t++) {
+ const Type* type = package->types[t].get();
+ if (type == nullptr) {
+ packages_[p]->types[t].reset();
+ continue;
+ }
+
+ const size_t type_alloc_size = sizeof(Type) + (type->entry_capacity * sizeof(Entry));
+ void* copied_data = malloc(type_alloc_size);
+ memcpy(copied_data, type, type_alloc_size);
+ packages_[p]->types[t].reset(reinterpret_cast<Type*>(copied_data));
+ }
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/libs/androidfw/Chunk.h b/libs/androidfw/Chunk.h
new file mode 100644
index 0000000..e87b940
--- /dev/null
+++ b/libs/androidfw/Chunk.h
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#ifndef CHUNK_H_
+#define CHUNK_H_
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "utils/ByteOrder.h"
+
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+#include "androidfw/ResourceTypes.h"
+
+namespace android {
+
+// Helpful wrapper around a ResChunk_header that provides getter methods
+// that handle endianness conversions and provide access to the data portion
+// of the chunk.
+class Chunk {
+ public:
+ explicit Chunk(const ResChunk_header* chunk) : device_chunk_(chunk) {}
+
+ // Returns the type of the chunk. Caller need not worry about endianness.
+ inline int type() const { return dtohs(device_chunk_->type); }
+
+ // Returns the size of the entire chunk. This can be useful for skipping
+ // over the entire chunk. Caller need not worry about endianness.
+ inline size_t size() const { return dtohl(device_chunk_->size); }
+
+ // Returns the size of the header. Caller need not worry about endianness.
+ inline size_t header_size() const { return dtohs(device_chunk_->headerSize); }
+
+ template <typename T>
+ inline const T* header() const {
+ if (header_size() >= sizeof(T)) {
+ return reinterpret_cast<const T*>(device_chunk_);
+ }
+ return nullptr;
+ }
+
+ inline const void* data_ptr() const {
+ return reinterpret_cast<const uint8_t*>(device_chunk_) + header_size();
+ }
+
+ inline size_t data_size() const { return size() - header_size(); }
+
+ private:
+ const ResChunk_header* device_chunk_;
+};
+
+// Provides a Java style iterator over an array of ResChunk_header's.
+// Validation is performed while iterating.
+// The caller should check if there was an error during chunk validation
+// by calling HadError() and GetLastError() to get the reason for failure.
+// Example:
+//
+// ChunkIterator iter(data_ptr, data_len);
+// while (iter.HasNext()) {
+// const Chunk chunk = iter.Next();
+// ...
+// }
+//
+// if (iter.HadError()) {
+// LOG(ERROR) << iter.GetLastError();
+// }
+//
+class ChunkIterator {
+ public:
+ ChunkIterator(const void* data, size_t len)
+ : next_chunk_(reinterpret_cast<const ResChunk_header*>(data)),
+ len_(len),
+ last_error_(nullptr) {
+ CHECK(next_chunk_ != nullptr) << "data can't be nullptr";
+ VerifyNextChunk();
+ }
+
+ Chunk Next();
+ inline bool HasNext() const { return !HadError() && len_ != 0; };
+ inline bool HadError() const { return last_error_ != nullptr; }
+ inline std::string GetLastError() const { return last_error_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChunkIterator);
+
+ // Returns false if there was an error.
+ bool VerifyNextChunk();
+
+ const ResChunk_header* next_chunk_;
+ size_t len_;
+ const char* last_error_;
+};
+
+} // namespace android
+
+#endif /* CHUNK_H_ */
diff --git a/libs/androidfw/ChunkIterator.cpp b/libs/androidfw/ChunkIterator.cpp
new file mode 100644
index 0000000..747aa4a
--- /dev/null
+++ b/libs/androidfw/ChunkIterator.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 "Chunk.h"
+
+#include "android-base/logging.h"
+
+namespace android {
+
+Chunk ChunkIterator::Next() {
+ CHECK(len_ != 0) << "called Next() after last chunk";
+
+ const ResChunk_header* this_chunk = next_chunk_;
+
+ // We've already checked the values of this_chunk, so safely increment.
+ next_chunk_ = reinterpret_cast<const ResChunk_header*>(
+ reinterpret_cast<const uint8_t*>(this_chunk) + dtohl(this_chunk->size));
+ len_ -= dtohl(this_chunk->size);
+
+ if (len_ != 0) {
+ // Prepare the next chunk.
+ VerifyNextChunk();
+ }
+ return Chunk(this_chunk);
+}
+
+// Returns false if there was an error.
+bool ChunkIterator::VerifyNextChunk() {
+ const uintptr_t header_start = reinterpret_cast<uintptr_t>(next_chunk_);
+
+ // This data must be 4-byte aligned, since we directly
+ // access 32-bit words, which must be aligned on
+ // certain architectures.
+ if (header_start & 0x03) {
+ last_error_ = "header not aligned on 4-byte boundary";
+ return false;
+ }
+
+ if (len_ < sizeof(ResChunk_header)) {
+ last_error_ = "not enough space for header";
+ return false;
+ }
+
+ const size_t header_size = dtohs(next_chunk_->headerSize);
+ const size_t size = dtohl(next_chunk_->size);
+ if (header_size < sizeof(ResChunk_header)) {
+ last_error_ = "header size too small";
+ return false;
+ }
+
+ if (header_size > size) {
+ last_error_ = "header size is larger than entire chunk";
+ return false;
+ }
+
+ if (size > len_) {
+ last_error_ = "chunk size is bigger than given data";
+ return false;
+ }
+
+ if ((size | header_size) & 0x03) {
+ last_error_ = "header sizes are not aligned on 4-byte boundary";
+ return false;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
new file mode 100644
index 0000000..94d0d46
--- /dev/null
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -0,0 +1,572 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include "androidfw/LoadedArsc.h"
+
+#include <cstddef>
+#include <limits>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "utils/ByteOrder.h"
+#include "utils/Trace.h"
+
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+#include "Chunk.h"
+#include "androidfw/ByteBucketArray.h"
+#include "androidfw/Util.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+
+namespace {
+
+// Element of a TypeSpec array. See TypeSpec.
+struct Type {
+ // The configuration for which this type defines entries.
+ // This is already converted to host endianness.
+ ResTable_config configuration;
+
+ // Pointer to the mmapped data where entry definitions are kept.
+ const ResTable_type* type;
+};
+
+// TypeSpec is going to be immediately proceeded by
+// an array of Type structs, all in the same block of memory.
+struct TypeSpec {
+ // Pointer to the mmapped data where flags are kept.
+ // Flags denote whether the resource entry is public
+ // and under which configurations it varies.
+ const ResTable_typeSpec* type_spec;
+
+ // The number of types that follow this struct.
+ // There is a type for each configuration
+ // that entries are defined for.
+ size_t type_count;
+
+ // Trick to easily access a variable number of Type structs
+ // proceeding this struct, and to ensure their alignment.
+ const Type types[0];
+};
+
+// TypeSpecPtr points to the block of memory that holds
+// a TypeSpec struct, followed by an array of Type structs.
+// TypeSpecPtr is a managed pointer that knows how to delete
+// itself.
+using TypeSpecPtr = util::unique_cptr<TypeSpec>;
+
+// Builder that helps accumulate Type structs and then create a single
+// contiguous block of memory to store both the TypeSpec struct and
+// the Type structs.
+class TypeSpecPtrBuilder {
+ public:
+ TypeSpecPtrBuilder(const ResTable_typeSpec* header) : header_(header) {}
+
+ void AddType(const ResTable_type* type) {
+ ResTable_config config;
+ config.copyFromDtoH(type->config);
+ types_.push_back(Type{config, type});
+ }
+
+ TypeSpecPtr Build() {
+ // Check for overflow.
+ if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) {
+ return {};
+ }
+ TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
+ type_spec->type_spec = header_;
+ type_spec->type_count = types_.size();
+ memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
+ return TypeSpecPtr(type_spec);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);
+
+ const ResTable_typeSpec* header_;
+ std::vector<Type> types_;
+};
+
+} // namespace
+
+class LoadedPackage {
+ public:
+ LoadedPackage() = default;
+
+ bool FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable_config& config,
+ LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config,
+ uint32_t* out_flags) const;
+
+ ResStringPool type_string_pool_;
+ ResStringPool key_string_pool_;
+ std::string package_name_;
+ int package_id_ = -1;
+
+ ByteBucketArray<TypeSpecPtr> type_specs_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
+};
+
+bool LoadedPackage::FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable_config& config,
+ LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config,
+ uint32_t* out_flags) const {
+ ATRACE_NAME("LoadedPackage::FindEntry");
+ const TypeSpecPtr& ptr = type_specs_[type_id];
+ if (ptr == nullptr) {
+ return false;
+ }
+
+ // Don't bother checking if the entry ID is larger than
+ // the number of entries.
+ if (entry_id >= dtohl(ptr->type_spec->entryCount)) {
+ return false;
+ }
+
+ const ResTable_config* best_config = nullptr;
+ const ResTable_type* best_type = nullptr;
+ uint32_t best_offset = 0;
+
+ for (uint32_t i = 0; i < ptr->type_count; i++) {
+ const Type* type = &ptr->types[i];
+
+ if (type->configuration.match(config) &&
+ (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ size_t entry_count = dtohl(type->type->entryCount);
+ if (entry_id < entry_count) {
+ const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
+ const uint32_t offset = dtohl(entry_offsets[entry_id]);
+ if (offset != ResTable_type::NO_ENTRY) {
+ // There is an entry for this resource, record it.
+ best_config = &type->configuration;
+ best_type = type->type;
+ best_offset = offset + dtohl(type->type->entriesStart);
+ }
+ }
+ }
+ }
+
+ if (best_type == nullptr) {
+ return false;
+ }
+
+ const uint32_t* flags = reinterpret_cast<const uint32_t*>(ptr->type_spec + 1);
+ *out_flags = dtohl(flags[entry_id]);
+ *out_selected_config = *best_config;
+
+ const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(best_type) + best_offset);
+ out_entry->entry = best_entry;
+ out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
+ out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
+ return true;
+}
+
+// The destructor gets generated into arbitrary translation units
+// if left implicit, which causes the compiler to complain about
+// forward declarations and incomplete types.
+LoadedArsc::~LoadedArsc() {}
+
+bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config, Entry* out_entry,
+ ResTable_config* out_selected_config, uint32_t* out_flags) const {
+ ATRACE_NAME("LoadedArsc::FindEntry");
+ const uint8_t package_id = util::get_package_id(resid);
+ const uint8_t type_id = util::get_type_id(resid);
+ const uint16_t entry_id = util::get_entry_id(resid);
+
+ if (type_id == 0) {
+ LOG(ERROR) << "Invalid ID 0x" << std::hex << resid << std::dec << ".";
+ return false;
+ }
+
+ for (const auto& loaded_package : packages_) {
+ if (loaded_package->package_id_ == package_id) {
+ return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry,
+ out_selected_config, out_flags);
+ }
+ }
+ return false;
+}
+
+const std::string* LoadedArsc::GetPackageNameForId(uint32_t resid) const {
+ const uint8_t package_id = util::get_package_id(resid);
+ for (const auto& loaded_package : packages_) {
+ if (loaded_package->package_id_ == package_id) {
+ return &loaded_package->package_name_;
+ }
+ }
+ return nullptr;
+}
+
+static bool VerifyType(const Chunk& chunk) {
+ ATRACE_CALL();
+ const ResTable_type* header = chunk.header<ResTable_type>();
+
+ const size_t entry_count = dtohl(header->entryCount);
+ if (entry_count > std::numeric_limits<uint16_t>::max()) {
+ LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
+ return false;
+ }
+
+ // Make sure that there is enough room for the entry offsets.
+ const size_t offsets_offset = chunk.header_size();
+ const size_t entries_offset = dtohl(header->entriesStart);
+ const size_t offsets_length = sizeof(uint32_t) * entry_count;
+
+ if (offsets_offset + offsets_length > entries_offset) {
+ LOG(ERROR) << "Entry offsets overlap actual entry data.";
+ return false;
+ }
+
+ if (entries_offset > chunk.size()) {
+ LOG(ERROR) << "Entry offsets extend beyond chunk.";
+ return false;
+ }
+
+ if (entries_offset & 0x03) {
+ LOG(ERROR) << "Entries start at unaligned address.";
+ return false;
+ }
+
+ // Check each entry offset.
+ const uint32_t* offsets =
+ reinterpret_cast<const uint32_t*>(reinterpret_cast<const uint8_t*>(header) + offsets_offset);
+ for (size_t i = 0; i < entry_count; i++) {
+ uint32_t offset = dtohl(offsets[i]);
+ if (offset != ResTable_type::NO_ENTRY) {
+ // Check that the offset is aligned.
+ if (offset & 0x03) {
+ LOG(ERROR) << "Entry offset at index " << i << " is not 4-byte aligned.";
+ return false;
+ }
+
+ // Check that the offset doesn't overflow.
+ if (offset > std::numeric_limits<uint32_t>::max() - entries_offset) {
+ // Overflow in offset.
+ LOG(ERROR) << "Entry offset at index " << i << " is too large.";
+ return false;
+ }
+
+ offset += entries_offset;
+ if (offset > chunk.size() - sizeof(ResTable_entry)) {
+ LOG(ERROR) << "Entry offset at index " << i << " is too large. No room for ResTable_entry.";
+ return false;
+ }
+
+ const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(header) + offset);
+ const size_t entry_size = dtohs(entry->size);
+ if (entry_size < sizeof(*entry)) {
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " is too small.";
+ return false;
+ }
+
+ // Check the declared entrySize.
+ if (entry_size > chunk.size() || offset > chunk.size() - entry_size) {
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " is too large.";
+ return false;
+ }
+
+ // If this is a map entry, then keep validating.
+ if (entry_size >= sizeof(ResTable_map_entry)) {
+ const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry);
+ const size_t map_entry_count = dtohl(map->count);
+
+ size_t map_entries_start = offset + entry_size;
+ if (map_entries_start & 0x03) {
+ LOG(ERROR) << "Map entries start at unaligned offset.";
+ return false;
+ }
+
+ // Each entry is sizeof(ResTable_map) big.
+ if (map_entry_count > ((chunk.size() - map_entries_start) / sizeof(ResTable_map))) {
+ LOG(ERROR) << "Too many map entries in ResTable_map_entry.";
+ return false;
+ }
+
+ // Great, all the map entries fit!.
+ } else {
+ // There needs to be room for one Res_value struct.
+ if (offset + entry_size > chunk.size() - sizeof(Res_value)) {
+ LOG(ERROR) << "No room for Res_value after ResTable_entry.";
+ return false;
+ }
+
+ const Res_value* value = reinterpret_cast<const Res_value*>(
+ reinterpret_cast<const uint8_t*>(entry) + entry_size);
+ const size_t value_size = dtohs(value->size);
+ if (value_size < sizeof(Res_value)) {
+ LOG(ERROR) << "Res_value is too small.";
+ return false;
+ }
+
+ if (value_size > chunk.size() || offset + entry_size > chunk.size() - value_size) {
+ LOG(ERROR) << "Res_value size is too large.";
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) {
+ ATRACE_CALL();
+ const ResTable_package* header = chunk.header<ResTable_package>();
+ if (header == nullptr) {
+ LOG(ERROR) << "Chunk RES_TABLE_PACKAGE_TYPE is too small.";
+ return false;
+ }
+
+ loaded_package->package_id_ = dtohl(header->id);
+
+ // A TypeSpec builder. We use this to accumulate the set of Types
+ // available for a TypeSpec, and later build a single, contiguous block
+ // of memory that holds all the Types together with the TypeSpec.
+ std::unique_ptr<TypeSpecPtrBuilder> types_builder;
+
+ // Keep track of the last seen type index. Since type IDs are 1-based,
+ // this records their index, which is 0-based (type ID - 1).
+ uint8_t last_type_idx = 0;
+
+ ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
+ while (iter.HasNext()) {
+ const Chunk child_chunk = iter.Next();
+ switch (child_chunk.type()) {
+ case RES_STRING_POOL_TYPE: {
+ const uintptr_t pool_address =
+ reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>());
+ const uintptr_t header_address = reinterpret_cast<uintptr_t>(header);
+ if (pool_address == header_address + dtohl(header->typeStrings)) {
+ // This string pool is the type string pool.
+ status_t err = loaded_package->type_string_pool_.setTo(
+ child_chunk.header<ResStringPool_header>(), child_chunk.size());
+ if (err != NO_ERROR) {
+ LOG(ERROR) << "Corrupt package type string pool.";
+ return false;
+ }
+ } else if (pool_address == header_address + dtohl(header->keyStrings)) {
+ // This string pool is the key string pool.
+ status_t err = loaded_package->key_string_pool_.setTo(
+ child_chunk.header<ResStringPool_header>(), child_chunk.size());
+ if (err != NO_ERROR) {
+ LOG(ERROR) << "Corrupt package key string pool.";
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "Too many string pool chunks found in package.";
+ }
+ } break;
+
+ case RES_TABLE_TYPE_SPEC_TYPE: {
+ ATRACE_NAME("LoadTableTypeSpec");
+
+ // Starting a new TypeSpec, so finish the old one if there was one.
+ if (types_builder) {
+ TypeSpecPtr type_spec_ptr = types_builder->Build();
+ if (type_spec_ptr == nullptr) {
+ LOG(ERROR) << "Too many type configurations, overflow detected.";
+ return false;
+ }
+
+ loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+
+ types_builder = {};
+ last_type_idx = 0;
+ }
+
+ const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>();
+ if (type_spec == nullptr) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE is too small.";
+ return false;
+ }
+
+ if (type_spec->id == 0) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0.";
+ return false;
+ }
+
+ // The data portion of this chunk contains entry_count 32bit entries,
+ // each one representing a set of flags.
+ // Here we only validate that the chunk is well formed.
+ const size_t entry_count = dtohl(type_spec->entryCount);
+
+ // There can only be 2^16 entries in a type, because that is the ID
+ // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
+ if (entry_count > std::numeric_limits<uint16_t>::max()) {
+ LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_SPEC_TYPE: " << entry_count << ".";
+ return false;
+ }
+
+ if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
+ LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_TYPE_SPEC_TYPE.";
+ return false;
+ }
+
+ last_type_idx = type_spec->id - 1;
+ types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec);
+ } break;
+
+ case RES_TABLE_TYPE_TYPE: {
+ const ResTable_type* type = child_chunk.header<ResTable_type>();
+ if (type == nullptr) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE is too small.";
+ return false;
+ }
+
+ if (type->id == 0) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE has invalid ID 0.";
+ return false;
+ }
+
+ // Type chunks must be preceded by their TypeSpec chunks.
+ if (!types_builder || type->id - 1 != last_type_idx) {
+ LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without "
+ "RES_TABLE_TYPE_SPEC_TYPE.";
+ return false;
+ }
+
+ if (!VerifyType(child_chunk)) {
+ return false;
+ }
+
+ types_builder->AddType(type);
+ } break;
+
+ default:
+ LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ break;
+ }
+ }
+
+ // Finish the last TypeSpec.
+ if (types_builder) {
+ TypeSpecPtr type_spec_ptr = types_builder->Build();
+ if (type_spec_ptr == nullptr) {
+ LOG(ERROR) << "Too many type configurations, overflow detected.";
+ return false;
+ }
+ loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+ }
+
+ if (iter.HadError()) {
+ LOG(ERROR) << iter.GetLastError();
+ return false;
+ }
+ return true;
+}
+
+bool LoadedArsc::LoadTable(const Chunk& chunk) {
+ ATRACE_CALL();
+ const ResTable_header* header = chunk.header<ResTable_header>();
+ if (header == nullptr) {
+ LOG(ERROR) << "Chunk RES_TABLE_TYPE is too small.";
+ return false;
+ }
+
+ const size_t package_count = dtohl(header->packageCount);
+ size_t packages_seen = 0;
+
+ packages_.reserve(package_count);
+
+ ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
+ while (iter.HasNext()) {
+ const Chunk child_chunk = iter.Next();
+ switch (child_chunk.type()) {
+ case RES_STRING_POOL_TYPE:
+ // Only use the first string pool. Ignore others.
+ if (global_string_pool_.getError() == NO_INIT) {
+ status_t err = global_string_pool_.setTo(child_chunk.header<ResStringPool_header>(),
+ child_chunk.size());
+ if (err != NO_ERROR) {
+ LOG(ERROR) << "Corrupt string pool.";
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "Multiple string pool chunks found in resource table.";
+ }
+ break;
+
+ case RES_TABLE_PACKAGE_TYPE: {
+ if (packages_seen + 1 > package_count) {
+ LOG(ERROR) << "More package chunks were found than the " << package_count
+ << " declared in the "
+ "header.";
+ return false;
+ }
+ packages_seen++;
+
+ std::unique_ptr<LoadedPackage> loaded_package = util::make_unique<LoadedPackage>();
+ if (!LoadPackage(child_chunk, loaded_package.get())) {
+ return false;
+ }
+ packages_.push_back(std::move(loaded_package));
+ } break;
+
+ default:
+ LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ break;
+ }
+ }
+
+ if (iter.HadError()) {
+ LOG(ERROR) << iter.GetLastError();
+ return false;
+ }
+ return true;
+}
+
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(const void* data, size_t len) {
+ ATRACE_CALL();
+
+ // Not using make_unique because the constructor is private.
+ std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
+
+ ChunkIterator iter(data, len);
+ while (iter.HasNext()) {
+ const Chunk chunk = iter.Next();
+ switch (chunk.type()) {
+ case RES_TABLE_TYPE:
+ if (!loaded_arsc->LoadTable(chunk)) {
+ return {};
+ }
+ break;
+
+ default:
+ LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
+ break;
+ }
+ }
+
+ if (iter.HadError()) {
+ LOG(ERROR) << iter.GetLastError();
+ return {};
+ }
+ return loaded_arsc;
+}
+
+} // namespace android
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index 1ac5085..7c381ef 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -1,4 +1,4 @@
-// Auto-generated by frameworks/base/tools/localedata/extract_icu_data.py
+// Auto-generated by ./tools/localedata/extract_icu_data.py
const char SCRIPT_CODES[][4] = {
/* 0 */ {'A', 'h', 'o', 'm'},
@@ -39,27 +39,27 @@
/* 35 */ {'K', 'h', 'm', 'r'},
/* 36 */ {'K', 'n', 'd', 'a'},
/* 37 */ {'K', 'o', 'r', 'e'},
- /* 38 */ {'K', 't', 'h', 'i'},
- /* 39 */ {'L', 'a', 'n', 'a'},
- /* 40 */ {'L', 'a', 'o', 'o'},
- /* 41 */ {'L', 'a', 't', 'n'},
- /* 42 */ {'L', 'e', 'p', 'c'},
- /* 43 */ {'L', 'i', 'n', 'a'},
- /* 44 */ {'L', 'i', 's', 'u'},
- /* 45 */ {'L', 'y', 'c', 'i'},
- /* 46 */ {'L', 'y', 'd', 'i'},
- /* 47 */ {'M', 'a', 'n', 'd'},
- /* 48 */ {'M', 'a', 'n', 'i'},
- /* 49 */ {'M', 'e', 'r', 'c'},
- /* 50 */ {'M', 'l', 'y', 'm'},
- /* 51 */ {'M', 'o', 'n', 'g'},
- /* 52 */ {'M', 'r', 'o', 'o'},
- /* 53 */ {'M', 'y', 'm', 'r'},
- /* 54 */ {'N', 'a', 'r', 'b'},
- /* 55 */ {'N', 'k', 'o', 'o'},
- /* 56 */ {'O', 'g', 'a', 'm'},
- /* 57 */ {'O', 'r', 'k', 'h'},
- /* 58 */ {'O', 'r', 'y', 'a'},
+ /* 38 */ {'L', 'a', 'n', 'a'},
+ /* 39 */ {'L', 'a', 'o', 'o'},
+ /* 40 */ {'L', 'a', 't', 'n'},
+ /* 41 */ {'L', 'e', 'p', 'c'},
+ /* 42 */ {'L', 'i', 'n', 'a'},
+ /* 43 */ {'L', 'i', 's', 'u'},
+ /* 44 */ {'L', 'y', 'c', 'i'},
+ /* 45 */ {'L', 'y', 'd', 'i'},
+ /* 46 */ {'M', 'a', 'n', 'd'},
+ /* 47 */ {'M', 'a', 'n', 'i'},
+ /* 48 */ {'M', 'e', 'r', 'c'},
+ /* 49 */ {'M', 'l', 'y', 'm'},
+ /* 50 */ {'M', 'o', 'n', 'g'},
+ /* 51 */ {'M', 'r', 'o', 'o'},
+ /* 52 */ {'M', 'y', 'm', 'r'},
+ /* 53 */ {'N', 'a', 'r', 'b'},
+ /* 54 */ {'N', 'k', 'o', 'o'},
+ /* 55 */ {'O', 'g', 'a', 'm'},
+ /* 56 */ {'O', 'r', 'k', 'h'},
+ /* 57 */ {'O', 'r', 'y', 'a'},
+ /* 58 */ {'O', 's', 'g', 'e'},
/* 59 */ {'P', 'a', 'u', 'c'},
/* 60 */ {'P', 'h', 'l', 'i'},
/* 61 */ {'P', 'h', 'n', 'x'},
@@ -76,78 +76,147 @@
/* 72 */ {'T', 'a', 'l', 'e'},
/* 73 */ {'T', 'a', 'l', 'u'},
/* 74 */ {'T', 'a', 'm', 'l'},
- /* 75 */ {'T', 'a', 'v', 't'},
- /* 76 */ {'T', 'e', 'l', 'u'},
- /* 77 */ {'T', 'f', 'n', 'g'},
- /* 78 */ {'T', 'h', 'a', 'a'},
- /* 79 */ {'T', 'h', 'a', 'i'},
- /* 80 */ {'T', 'i', 'b', 't'},
- /* 81 */ {'U', 'g', 'a', 'r'},
- /* 82 */ {'V', 'a', 'i', 'i'},
- /* 83 */ {'X', 'p', 'e', 'o'},
- /* 84 */ {'X', 's', 'u', 'x'},
- /* 85 */ {'Y', 'i', 'i', 'i'},
- /* 86 */ {'~', '~', '~', 'A'},
- /* 87 */ {'~', '~', '~', 'B'},
+ /* 75 */ {'T', 'a', 'n', 'g'},
+ /* 76 */ {'T', 'a', 'v', 't'},
+ /* 77 */ {'T', 'e', 'l', 'u'},
+ /* 78 */ {'T', 'f', 'n', 'g'},
+ /* 79 */ {'T', 'h', 'a', 'a'},
+ /* 80 */ {'T', 'h', 'a', 'i'},
+ /* 81 */ {'T', 'i', 'b', 't'},
+ /* 82 */ {'U', 'g', 'a', 'r'},
+ /* 83 */ {'V', 'a', 'i', 'i'},
+ /* 84 */ {'X', 'p', 'e', 'o'},
+ /* 85 */ {'X', 's', 'u', 'x'},
+ /* 86 */ {'Y', 'i', 'i', 'i'},
+ /* 87 */ {'~', '~', '~', 'A'},
+ /* 88 */ {'~', '~', '~', 'B'},
};
const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
- {0x61610000u, 41u}, // aa -> Latn
+ {0x61610000u, 40u}, // aa -> Latn
+ {0xA0000000u, 40u}, // aai -> Latn
+ {0xA8000000u, 40u}, // aak -> Latn
+ {0xD0000000u, 40u}, // aau -> Latn
{0x61620000u, 15u}, // ab -> Cyrl
- {0xC4200000u, 41u}, // abr -> Latn
- {0x90400000u, 41u}, // ace -> Latn
- {0x9C400000u, 41u}, // ach -> Latn
- {0x80600000u, 41u}, // ada -> Latn
+ {0xA0200000u, 40u}, // abi -> Latn
+ {0xC4200000u, 40u}, // abr -> Latn
+ {0xCC200000u, 40u}, // abt -> Latn
+ {0xE0200000u, 40u}, // aby -> Latn
+ {0x8C400000u, 40u}, // acd -> Latn
+ {0x90400000u, 40u}, // ace -> Latn
+ {0x9C400000u, 40u}, // ach -> Latn
+ {0x80600000u, 40u}, // ada -> Latn
+ {0x90600000u, 40u}, // ade -> Latn
+ {0xA4600000u, 40u}, // adj -> Latn
{0xE0600000u, 15u}, // ady -> Cyrl
+ {0xE4600000u, 40u}, // adz -> Latn
{0x61650000u, 4u}, // ae -> Avst
{0x84800000u, 1u}, // aeb -> Arab
- {0x61660000u, 41u}, // af -> Latn
- {0xC0C00000u, 41u}, // agq -> Latn
+ {0xE0800000u, 40u}, // aey -> Latn
+ {0x61660000u, 40u}, // af -> Latn
+ {0x88C00000u, 40u}, // agc -> Latn
+ {0x8CC00000u, 40u}, // agd -> Latn
+ {0x98C00000u, 40u}, // agg -> Latn
+ {0xB0C00000u, 40u}, // agm -> Latn
+ {0xB8C00000u, 40u}, // ago -> Latn
+ {0xC0C00000u, 40u}, // agq -> Latn
+ {0x80E00000u, 40u}, // aha -> Latn
+ {0xACE00000u, 40u}, // ahl -> Latn
{0xB8E00000u, 0u}, // aho -> Ahom
- {0x616B0000u, 41u}, // ak -> Latn
- {0xA9400000u, 84u}, // akk -> Xsux
- {0xB5600000u, 41u}, // aln -> Latn
+ {0x99200000u, 40u}, // ajg -> Latn
+ {0x616B0000u, 40u}, // ak -> Latn
+ {0xA9400000u, 85u}, // akk -> Xsux
+ {0x81600000u, 40u}, // ala -> Latn
+ {0xA1600000u, 40u}, // ali -> Latn
+ {0xB5600000u, 40u}, // aln -> Latn
{0xCD600000u, 15u}, // alt -> Cyrl
{0x616D0000u, 18u}, // am -> Ethi
- {0xB9800000u, 41u}, // amo -> Latn
- {0xE5C00000u, 41u}, // aoz -> Latn
+ {0xB1800000u, 40u}, // amm -> Latn
+ {0xB5800000u, 40u}, // amn -> Latn
+ {0xB9800000u, 40u}, // amo -> Latn
+ {0xBD800000u, 40u}, // amp -> Latn
+ {0x89A00000u, 40u}, // anc -> Latn
+ {0xA9A00000u, 40u}, // ank -> Latn
+ {0xB5A00000u, 40u}, // ann -> Latn
+ {0xE1A00000u, 40u}, // any -> Latn
+ {0xA5C00000u, 40u}, // aoj -> Latn
+ {0xB1C00000u, 40u}, // aom -> Latn
+ {0xE5C00000u, 40u}, // aoz -> Latn
+ {0x89E00000u, 1u}, // apc -> Arab
+ {0x8DE00000u, 1u}, // apd -> Arab
+ {0x91E00000u, 40u}, // ape -> Latn
+ {0xC5E00000u, 40u}, // apr -> Latn
+ {0xC9E00000u, 40u}, // aps -> Latn
+ {0xE5E00000u, 40u}, // apz -> Latn
{0x61720000u, 1u}, // ar -> Arab
- {0x61725842u, 87u}, // ar-XB -> ~~~B
+ {0x61725842u, 88u}, // ar-XB -> ~~~B
{0x8A200000u, 2u}, // arc -> Armi
- {0xB6200000u, 41u}, // arn -> Latn
- {0xBA200000u, 41u}, // aro -> Latn
+ {0x9E200000u, 40u}, // arh -> Latn
+ {0xB6200000u, 40u}, // arn -> Latn
+ {0xBA200000u, 40u}, // aro -> Latn
{0xC2200000u, 1u}, // arq -> Arab
{0xE2200000u, 1u}, // ary -> Arab
{0xE6200000u, 1u}, // arz -> Arab
{0x61730000u, 7u}, // as -> Beng
- {0x82400000u, 41u}, // asa -> Latn
+ {0x82400000u, 40u}, // asa -> Latn
{0x92400000u, 68u}, // ase -> Sgnw
- {0xCE400000u, 41u}, // ast -> Latn
- {0xA6600000u, 41u}, // atj -> Latn
+ {0x9A400000u, 40u}, // asg -> Latn
+ {0xBA400000u, 40u}, // aso -> Latn
+ {0xCE400000u, 40u}, // ast -> Latn
+ {0x82600000u, 40u}, // ata -> Latn
+ {0x9A600000u, 40u}, // atg -> Latn
+ {0xA6600000u, 40u}, // atj -> Latn
+ {0xE2800000u, 40u}, // auy -> Latn
{0x61760000u, 15u}, // av -> Cyrl
+ {0xAEA00000u, 1u}, // avl -> Arab
+ {0xB6A00000u, 40u}, // avn -> Latn
+ {0xCEA00000u, 40u}, // avt -> Latn
+ {0xD2A00000u, 40u}, // avu -> Latn
{0x82C00000u, 16u}, // awa -> Deva
- {0x61790000u, 41u}, // ay -> Latn
- {0x617A0000u, 41u}, // az -> Latn
+ {0x86C00000u, 40u}, // awb -> Latn
+ {0xBAC00000u, 40u}, // awo -> Latn
+ {0xDEC00000u, 40u}, // awx -> Latn
+ {0x61790000u, 40u}, // ay -> Latn
+ {0x87000000u, 40u}, // ayb -> Latn
+ {0x617A0000u, 40u}, // az -> Latn
{0x617A4951u, 1u}, // az-IQ -> Arab
{0x617A4952u, 1u}, // az-IR -> Arab
{0x617A5255u, 15u}, // az-RU -> Cyrl
{0x62610000u, 15u}, // ba -> Cyrl
{0xAC010000u, 1u}, // bal -> Arab
- {0xB4010000u, 41u}, // ban -> Latn
+ {0xB4010000u, 40u}, // ban -> Latn
{0xBC010000u, 16u}, // bap -> Deva
- {0xC4010000u, 41u}, // bar -> Latn
- {0xC8010000u, 41u}, // bas -> Latn
+ {0xC4010000u, 40u}, // bar -> Latn
+ {0xC8010000u, 40u}, // bas -> Latn
+ {0xD4010000u, 40u}, // bav -> Latn
{0xDC010000u, 5u}, // bax -> Bamu
- {0x88210000u, 41u}, // bbc -> Latn
- {0xA4210000u, 41u}, // bbj -> Latn
- {0xA0410000u, 41u}, // bci -> Latn
+ {0x80210000u, 40u}, // bba -> Latn
+ {0x84210000u, 40u}, // bbb -> Latn
+ {0x88210000u, 40u}, // bbc -> Latn
+ {0x8C210000u, 40u}, // bbd -> Latn
+ {0xA4210000u, 40u}, // bbj -> Latn
+ {0xBC210000u, 40u}, // bbp -> Latn
+ {0xC4210000u, 40u}, // bbr -> Latn
+ {0x94410000u, 40u}, // bcf -> Latn
+ {0x9C410000u, 40u}, // bch -> Latn
+ {0xA0410000u, 40u}, // bci -> Latn
+ {0xB0410000u, 40u}, // bcm -> Latn
+ {0xB4410000u, 40u}, // bcn -> Latn
+ {0xB8410000u, 40u}, // bco -> Latn
+ {0xC0410000u, 18u}, // bcq -> Ethi
+ {0xD0410000u, 40u}, // bcu -> Latn
+ {0x8C610000u, 40u}, // bdd -> Latn
{0x62650000u, 15u}, // be -> Cyrl
+ {0x94810000u, 40u}, // bef -> Latn
+ {0x9C810000u, 40u}, // beh -> Latn
{0xA4810000u, 1u}, // bej -> Arab
- {0xB0810000u, 41u}, // bem -> Latn
- {0xD8810000u, 41u}, // bew -> Latn
- {0xE4810000u, 41u}, // bez -> Latn
- {0x8CA10000u, 41u}, // bfd -> Latn
+ {0xB0810000u, 40u}, // bem -> Latn
+ {0xCC810000u, 40u}, // bet -> Latn
+ {0xD8810000u, 40u}, // bew -> Latn
+ {0xDC810000u, 40u}, // bex -> Latn
+ {0xE4810000u, 40u}, // bez -> Latn
+ {0x8CA10000u, 40u}, // bfd -> Latn
{0xC0A10000u, 74u}, // bfq -> Taml
{0xCCA10000u, 1u}, // bft -> Arab
{0xE0A10000u, 16u}, // bfy -> Deva
@@ -155,663 +224,1202 @@
{0x88C10000u, 16u}, // bgc -> Deva
{0xB4C10000u, 1u}, // bgn -> Arab
{0xDCC10000u, 21u}, // bgx -> Grek
- {0x62680000u, 38u}, // bh -> Kthi
{0x84E10000u, 16u}, // bhb -> Deva
+ {0x98E10000u, 40u}, // bhg -> Latn
{0xA0E10000u, 16u}, // bhi -> Deva
- {0xA8E10000u, 41u}, // bhk -> Latn
+ {0xA8E10000u, 40u}, // bhk -> Latn
+ {0xACE10000u, 40u}, // bhl -> Latn
{0xB8E10000u, 16u}, // bho -> Deva
- {0x62690000u, 41u}, // bi -> Latn
- {0xA9010000u, 41u}, // bik -> Latn
- {0xB5010000u, 41u}, // bin -> Latn
+ {0xE0E10000u, 40u}, // bhy -> Latn
+ {0x62690000u, 40u}, // bi -> Latn
+ {0x85010000u, 40u}, // bib -> Latn
+ {0x99010000u, 40u}, // big -> Latn
+ {0xA9010000u, 40u}, // bik -> Latn
+ {0xB1010000u, 40u}, // bim -> Latn
+ {0xB5010000u, 40u}, // bin -> Latn
+ {0xB9010000u, 40u}, // bio -> Latn
+ {0xC1010000u, 40u}, // biq -> Latn
+ {0x9D210000u, 40u}, // bjh -> Latn
+ {0xA1210000u, 18u}, // bji -> Ethi
{0xA5210000u, 16u}, // bjj -> Deva
- {0xB5210000u, 41u}, // bjn -> Latn
- {0xB1410000u, 41u}, // bkm -> Latn
- {0xD1410000u, 41u}, // bku -> Latn
- {0xCD610000u, 75u}, // blt -> Tavt
- {0x626D0000u, 41u}, // bm -> Latn
- {0xC1810000u, 41u}, // bmq -> Latn
+ {0xB5210000u, 40u}, // bjn -> Latn
+ {0xB9210000u, 40u}, // bjo -> Latn
+ {0xC5210000u, 40u}, // bjr -> Latn
+ {0xE5210000u, 40u}, // bjz -> Latn
+ {0x89410000u, 40u}, // bkc -> Latn
+ {0xB1410000u, 40u}, // bkm -> Latn
+ {0xC1410000u, 40u}, // bkq -> Latn
+ {0xD1410000u, 40u}, // bku -> Latn
+ {0xD5410000u, 40u}, // bkv -> Latn
+ {0xCD610000u, 76u}, // blt -> Tavt
+ {0x626D0000u, 40u}, // bm -> Latn
+ {0x9D810000u, 40u}, // bmh -> Latn
+ {0xA9810000u, 40u}, // bmk -> Latn
+ {0xC1810000u, 40u}, // bmq -> Latn
+ {0xD1810000u, 40u}, // bmu -> Latn
{0x626E0000u, 7u}, // bn -> Beng
- {0x626F0000u, 80u}, // bo -> Tibt
+ {0x99A10000u, 40u}, // bng -> Latn
+ {0xB1A10000u, 40u}, // bnm -> Latn
+ {0xBDA10000u, 40u}, // bnp -> Latn
+ {0x626F0000u, 81u}, // bo -> Tibt
+ {0xA5C10000u, 40u}, // boj -> Latn
+ {0xB1C10000u, 40u}, // bom -> Latn
+ {0xB5C10000u, 40u}, // bon -> Latn
{0xE1E10000u, 7u}, // bpy -> Beng
+ {0x8A010000u, 40u}, // bqc -> Latn
{0xA2010000u, 1u}, // bqi -> Arab
- {0xD6010000u, 41u}, // bqv -> Latn
- {0x62720000u, 41u}, // br -> Latn
+ {0xBE010000u, 40u}, // bqp -> Latn
+ {0xD6010000u, 40u}, // bqv -> Latn
+ {0x62720000u, 40u}, // br -> Latn
{0x82210000u, 16u}, // bra -> Deva
{0x9E210000u, 1u}, // brh -> Arab
{0xDE210000u, 16u}, // brx -> Deva
- {0x62730000u, 41u}, // bs -> Latn
+ {0xE6210000u, 40u}, // brz -> Latn
+ {0x62730000u, 40u}, // bs -> Latn
+ {0xA6410000u, 40u}, // bsj -> Latn
{0xC2410000u, 6u}, // bsq -> Bass
- {0xCA410000u, 41u}, // bss -> Latn
- {0xBA610000u, 41u}, // bto -> Latn
+ {0xCA410000u, 40u}, // bss -> Latn
+ {0xCE410000u, 18u}, // bst -> Ethi
+ {0xBA610000u, 40u}, // bto -> Latn
+ {0xCE610000u, 40u}, // btt -> Latn
{0xD6610000u, 16u}, // btv -> Deva
{0x82810000u, 15u}, // bua -> Cyrl
- {0x8A810000u, 41u}, // buc -> Latn
- {0x9A810000u, 41u}, // bug -> Latn
- {0xB2810000u, 41u}, // bum -> Latn
- {0x86A10000u, 41u}, // bvb -> Latn
+ {0x8A810000u, 40u}, // buc -> Latn
+ {0x8E810000u, 40u}, // bud -> Latn
+ {0x9A810000u, 40u}, // bug -> Latn
+ {0xAA810000u, 40u}, // buk -> Latn
+ {0xB2810000u, 40u}, // bum -> Latn
+ {0xBA810000u, 40u}, // buo -> Latn
+ {0xCA810000u, 40u}, // bus -> Latn
+ {0xD2810000u, 40u}, // buu -> Latn
+ {0x86A10000u, 40u}, // bvb -> Latn
+ {0x8EC10000u, 40u}, // bwd -> Latn
+ {0xC6C10000u, 40u}, // bwr -> Latn
+ {0x9EE10000u, 40u}, // bxh -> Latn
+ {0x93010000u, 40u}, // bye -> Latn
{0xB7010000u, 18u}, // byn -> Ethi
- {0xD7010000u, 41u}, // byv -> Latn
- {0x93210000u, 41u}, // bze -> Latn
- {0x63610000u, 41u}, // ca -> Latn
- {0x9C420000u, 41u}, // cch -> Latn
+ {0xC7010000u, 40u}, // byr -> Latn
+ {0xCB010000u, 40u}, // bys -> Latn
+ {0xD7010000u, 40u}, // byv -> Latn
+ {0xDF010000u, 40u}, // byx -> Latn
+ {0x83210000u, 40u}, // bza -> Latn
+ {0x93210000u, 40u}, // bze -> Latn
+ {0x97210000u, 40u}, // bzf -> Latn
+ {0x9F210000u, 40u}, // bzh -> Latn
+ {0xDB210000u, 40u}, // bzw -> Latn
+ {0x63610000u, 40u}, // ca -> Latn
+ {0xB4020000u, 40u}, // can -> Latn
+ {0xA4220000u, 40u}, // cbj -> Latn
+ {0x9C420000u, 40u}, // cch -> Latn
{0xBC420000u, 7u}, // ccp -> Beng
{0x63650000u, 15u}, // ce -> Cyrl
- {0x84820000u, 41u}, // ceb -> Latn
- {0x98C20000u, 41u}, // cgg -> Latn
- {0x63680000u, 41u}, // ch -> Latn
- {0xA8E20000u, 41u}, // chk -> Latn
+ {0x84820000u, 40u}, // ceb -> Latn
+ {0x80A20000u, 40u}, // cfa -> Latn
+ {0x98C20000u, 40u}, // cgg -> Latn
+ {0x63680000u, 40u}, // ch -> Latn
+ {0xA8E20000u, 40u}, // chk -> Latn
{0xB0E20000u, 15u}, // chm -> Cyrl
- {0xB8E20000u, 41u}, // cho -> Latn
- {0xBCE20000u, 41u}, // chp -> Latn
+ {0xB8E20000u, 40u}, // cho -> Latn
+ {0xBCE20000u, 40u}, // chp -> Latn
{0xC4E20000u, 12u}, // chr -> Cher
{0x81220000u, 1u}, // cja -> Arab
{0xB1220000u, 11u}, // cjm -> Cham
+ {0xD5220000u, 40u}, // cjv -> Latn
{0x85420000u, 1u}, // ckb -> Arab
- {0x636F0000u, 41u}, // co -> Latn
+ {0xAD420000u, 40u}, // ckl -> Latn
+ {0xB9420000u, 40u}, // cko -> Latn
+ {0xE1420000u, 40u}, // cky -> Latn
+ {0x81620000u, 40u}, // cla -> Latn
+ {0x91820000u, 40u}, // cme -> Latn
+ {0x636F0000u, 40u}, // co -> Latn
{0xBDC20000u, 13u}, // cop -> Copt
- {0xC9E20000u, 41u}, // cps -> Latn
+ {0xC9E20000u, 40u}, // cps -> Latn
{0x63720000u, 9u}, // cr -> Cans
{0xA6220000u, 9u}, // crj -> Cans
{0xAA220000u, 9u}, // crk -> Cans
{0xAE220000u, 9u}, // crl -> Cans
{0xB2220000u, 9u}, // crm -> Cans
- {0xCA220000u, 41u}, // crs -> Latn
- {0x63730000u, 41u}, // cs -> Latn
- {0x86420000u, 41u}, // csb -> Latn
+ {0xCA220000u, 40u}, // crs -> Latn
+ {0x63730000u, 40u}, // cs -> Latn
+ {0x86420000u, 40u}, // csb -> Latn
{0xDA420000u, 9u}, // csw -> Cans
{0x8E620000u, 59u}, // ctd -> Pauc
{0x63750000u, 15u}, // cu -> Cyrl
{0x63760000u, 15u}, // cv -> Cyrl
- {0x63790000u, 41u}, // cy -> Latn
- {0x64610000u, 41u}, // da -> Latn
- {0xA8030000u, 41u}, // dak -> Latn
+ {0x63790000u, 40u}, // cy -> Latn
+ {0x64610000u, 40u}, // da -> Latn
+ {0x8C030000u, 40u}, // dad -> Latn
+ {0x94030000u, 40u}, // daf -> Latn
+ {0x98030000u, 40u}, // dag -> Latn
+ {0x9C030000u, 40u}, // dah -> Latn
+ {0xA8030000u, 40u}, // dak -> Latn
{0xC4030000u, 15u}, // dar -> Cyrl
- {0xD4030000u, 41u}, // dav -> Latn
+ {0xD4030000u, 40u}, // dav -> Latn
+ {0x8C230000u, 40u}, // dbd -> Latn
+ {0xC0230000u, 40u}, // dbq -> Latn
{0x88430000u, 1u}, // dcc -> Arab
- {0x64650000u, 41u}, // de -> Latn
- {0xB4830000u, 41u}, // den -> Latn
- {0xC4C30000u, 41u}, // dgr -> Latn
- {0x91230000u, 41u}, // dje -> Latn
- {0xA5A30000u, 41u}, // dnj -> Latn
+ {0xB4630000u, 40u}, // ddn -> Latn
+ {0x64650000u, 40u}, // de -> Latn
+ {0x8C830000u, 40u}, // ded -> Latn
+ {0xB4830000u, 40u}, // den -> Latn
+ {0x80C30000u, 40u}, // dga -> Latn
+ {0x9CC30000u, 40u}, // dgh -> Latn
+ {0xA0C30000u, 40u}, // dgi -> Latn
+ {0xACC30000u, 1u}, // dgl -> Arab
+ {0xC4C30000u, 40u}, // dgr -> Latn
+ {0xE4C30000u, 40u}, // dgz -> Latn
+ {0x81030000u, 40u}, // dia -> Latn
+ {0x91230000u, 40u}, // dje -> Latn
+ {0xA5A30000u, 40u}, // dnj -> Latn
+ {0x85C30000u, 40u}, // dob -> Latn
{0xA1C30000u, 1u}, // doi -> Arab
- {0x86430000u, 41u}, // dsb -> Latn
- {0xB2630000u, 41u}, // dtm -> Latn
- {0xBE630000u, 41u}, // dtp -> Latn
- {0x82830000u, 41u}, // dua -> Latn
- {0x64760000u, 78u}, // dv -> Thaa
- {0xBB030000u, 41u}, // dyo -> Latn
- {0xD3030000u, 41u}, // dyu -> Latn
- {0x647A0000u, 80u}, // dz -> Tibt
- {0xD0240000u, 41u}, // ebu -> Latn
- {0x65650000u, 41u}, // ee -> Latn
- {0xA0A40000u, 41u}, // efi -> Latn
- {0xACC40000u, 41u}, // egl -> Latn
+ {0xBDC30000u, 40u}, // dop -> Latn
+ {0xD9C30000u, 40u}, // dow -> Latn
+ {0xA2230000u, 40u}, // dri -> Latn
+ {0xCA230000u, 18u}, // drs -> Ethi
+ {0x86430000u, 40u}, // dsb -> Latn
+ {0xB2630000u, 40u}, // dtm -> Latn
+ {0xBE630000u, 40u}, // dtp -> Latn
+ {0xCA630000u, 40u}, // dts -> Latn
+ {0xE2630000u, 16u}, // dty -> Deva
+ {0x82830000u, 40u}, // dua -> Latn
+ {0x8A830000u, 40u}, // duc -> Latn
+ {0x8E830000u, 40u}, // dud -> Latn
+ {0x9A830000u, 40u}, // dug -> Latn
+ {0x64760000u, 79u}, // dv -> Thaa
+ {0x82A30000u, 40u}, // dva -> Latn
+ {0xDAC30000u, 40u}, // dww -> Latn
+ {0xBB030000u, 40u}, // dyo -> Latn
+ {0xD3030000u, 40u}, // dyu -> Latn
+ {0x647A0000u, 81u}, // dz -> Tibt
+ {0x9B230000u, 40u}, // dzg -> Latn
+ {0xD0240000u, 40u}, // ebu -> Latn
+ {0x65650000u, 40u}, // ee -> Latn
+ {0xA0A40000u, 40u}, // efi -> Latn
+ {0xACC40000u, 40u}, // egl -> Latn
{0xE0C40000u, 17u}, // egy -> Egyp
{0xE1440000u, 32u}, // eky -> Kali
{0x656C0000u, 21u}, // el -> Grek
- {0x656E0000u, 41u}, // en -> Latn
- {0x656E5841u, 86u}, // en-XA -> ~~~A
- {0x656F0000u, 41u}, // eo -> Latn
- {0x65730000u, 41u}, // es -> Latn
- {0xD2440000u, 41u}, // esu -> Latn
- {0x65740000u, 41u}, // et -> Latn
+ {0x81840000u, 40u}, // ema -> Latn
+ {0xA1840000u, 40u}, // emi -> Latn
+ {0x656E0000u, 40u}, // en -> Latn
+ {0x656E5841u, 87u}, // en-XA -> ~~~A
+ {0xB5A40000u, 40u}, // enn -> Latn
+ {0xC1A40000u, 40u}, // enq -> Latn
+ {0x656F0000u, 40u}, // eo -> Latn
+ {0xA2240000u, 40u}, // eri -> Latn
+ {0x65730000u, 40u}, // es -> Latn
+ {0xD2440000u, 40u}, // esu -> Latn
+ {0x65740000u, 40u}, // et -> Latn
+ {0xC6640000u, 40u}, // etr -> Latn
{0xCE640000u, 30u}, // ett -> Ital
- {0x65750000u, 41u}, // eu -> Latn
- {0xBAC40000u, 41u}, // ewo -> Latn
- {0xCEE40000u, 41u}, // ext -> Latn
+ {0xD2640000u, 40u}, // etu -> Latn
+ {0xDE640000u, 40u}, // etx -> Latn
+ {0x65750000u, 40u}, // eu -> Latn
+ {0xBAC40000u, 40u}, // ewo -> Latn
+ {0xCEE40000u, 40u}, // ext -> Latn
{0x66610000u, 1u}, // fa -> Arab
- {0xB4050000u, 41u}, // fan -> Latn
- {0x66660000u, 41u}, // ff -> Latn
- {0xB0A50000u, 41u}, // ffm -> Latn
- {0x66690000u, 41u}, // fi -> Latn
+ {0x80050000u, 40u}, // faa -> Latn
+ {0x84050000u, 40u}, // fab -> Latn
+ {0x98050000u, 40u}, // fag -> Latn
+ {0xA0050000u, 40u}, // fai -> Latn
+ {0xB4050000u, 40u}, // fan -> Latn
+ {0x66660000u, 40u}, // ff -> Latn
+ {0xA0A50000u, 40u}, // ffi -> Latn
+ {0xB0A50000u, 40u}, // ffm -> Latn
+ {0x66690000u, 40u}, // fi -> Latn
{0x81050000u, 1u}, // fia -> Arab
- {0xAD050000u, 41u}, // fil -> Latn
- {0xCD050000u, 41u}, // fit -> Latn
- {0x666A0000u, 41u}, // fj -> Latn
- {0x666F0000u, 41u}, // fo -> Latn
- {0xB5C50000u, 41u}, // fon -> Latn
- {0x66720000u, 41u}, // fr -> Latn
- {0x8A250000u, 41u}, // frc -> Latn
- {0xBE250000u, 41u}, // frp -> Latn
- {0xC6250000u, 41u}, // frr -> Latn
- {0xCA250000u, 41u}, // frs -> Latn
- {0x8E850000u, 41u}, // fud -> Latn
- {0xC2850000u, 41u}, // fuq -> Latn
- {0xC6850000u, 41u}, // fur -> Latn
- {0xD6850000u, 41u}, // fuv -> Latn
- {0xC6A50000u, 41u}, // fvr -> Latn
- {0x66790000u, 41u}, // fy -> Latn
- {0x67610000u, 41u}, // ga -> Latn
- {0x80060000u, 41u}, // gaa -> Latn
- {0x98060000u, 41u}, // gag -> Latn
+ {0xAD050000u, 40u}, // fil -> Latn
+ {0xCD050000u, 40u}, // fit -> Latn
+ {0x666A0000u, 40u}, // fj -> Latn
+ {0xC5650000u, 40u}, // flr -> Latn
+ {0xBD850000u, 40u}, // fmp -> Latn
+ {0x666F0000u, 40u}, // fo -> Latn
+ {0x8DC50000u, 40u}, // fod -> Latn
+ {0xB5C50000u, 40u}, // fon -> Latn
+ {0xC5C50000u, 40u}, // for -> Latn
+ {0x91E50000u, 40u}, // fpe -> Latn
+ {0xCA050000u, 40u}, // fqs -> Latn
+ {0x66720000u, 40u}, // fr -> Latn
+ {0x8A250000u, 40u}, // frc -> Latn
+ {0xBE250000u, 40u}, // frp -> Latn
+ {0xC6250000u, 40u}, // frr -> Latn
+ {0xCA250000u, 40u}, // frs -> Latn
+ {0x86850000u, 1u}, // fub -> Arab
+ {0x8E850000u, 40u}, // fud -> Latn
+ {0x92850000u, 40u}, // fue -> Latn
+ {0x96850000u, 40u}, // fuf -> Latn
+ {0x9E850000u, 40u}, // fuh -> Latn
+ {0xC2850000u, 40u}, // fuq -> Latn
+ {0xC6850000u, 40u}, // fur -> Latn
+ {0xD6850000u, 40u}, // fuv -> Latn
+ {0xE2850000u, 40u}, // fuy -> Latn
+ {0xC6A50000u, 40u}, // fvr -> Latn
+ {0x66790000u, 40u}, // fy -> Latn
+ {0x67610000u, 40u}, // ga -> Latn
+ {0x80060000u, 40u}, // gaa -> Latn
+ {0x94060000u, 40u}, // gaf -> Latn
+ {0x98060000u, 40u}, // gag -> Latn
+ {0x9C060000u, 40u}, // gah -> Latn
+ {0xA4060000u, 40u}, // gaj -> Latn
+ {0xB0060000u, 40u}, // gam -> Latn
{0xB4060000u, 24u}, // gan -> Hans
- {0xE0060000u, 41u}, // gay -> Latn
+ {0xD8060000u, 40u}, // gaw -> Latn
+ {0xE0060000u, 40u}, // gay -> Latn
+ {0x94260000u, 40u}, // gbf -> Latn
{0xB0260000u, 16u}, // gbm -> Deva
+ {0xE0260000u, 40u}, // gby -> Latn
{0xE4260000u, 1u}, // gbz -> Arab
- {0xC4460000u, 41u}, // gcr -> Latn
- {0x67640000u, 41u}, // gd -> Latn
+ {0xC4460000u, 40u}, // gcr -> Latn
+ {0x67640000u, 40u}, // gd -> Latn
+ {0x90660000u, 40u}, // gde -> Latn
+ {0xB4660000u, 40u}, // gdn -> Latn
+ {0xC4660000u, 40u}, // gdr -> Latn
+ {0x84860000u, 40u}, // geb -> Latn
+ {0xA4860000u, 40u}, // gej -> Latn
+ {0xAC860000u, 40u}, // gel -> Latn
{0xE4860000u, 18u}, // gez -> Ethi
+ {0xA8A60000u, 40u}, // gfk -> Latn
{0xB4C60000u, 16u}, // ggn -> Deva
- {0xAD060000u, 41u}, // gil -> Latn
+ {0xC8E60000u, 40u}, // ghs -> Latn
+ {0xAD060000u, 40u}, // gil -> Latn
+ {0xB1060000u, 40u}, // gim -> Latn
{0xA9260000u, 1u}, // gjk -> Arab
+ {0xB5260000u, 40u}, // gjn -> Latn
{0xD1260000u, 1u}, // gju -> Arab
- {0x676C0000u, 41u}, // gl -> Latn
+ {0xB5460000u, 40u}, // gkn -> Latn
+ {0xBD460000u, 40u}, // gkp -> Latn
+ {0x676C0000u, 40u}, // gl -> Latn
{0xA9660000u, 1u}, // glk -> Arab
- {0x676E0000u, 41u}, // gn -> Latn
+ {0xB1860000u, 40u}, // gmm -> Latn
+ {0xD5860000u, 18u}, // gmv -> Ethi
+ {0x676E0000u, 40u}, // gn -> Latn
+ {0x8DA60000u, 40u}, // gnd -> Latn
+ {0x99A60000u, 40u}, // gng -> Latn
+ {0x8DC60000u, 40u}, // god -> Latn
+ {0x95C60000u, 18u}, // gof -> Ethi
+ {0xA1C60000u, 40u}, // goi -> Latn
{0xB1C60000u, 16u}, // gom -> Deva
- {0xB5C60000u, 76u}, // gon -> Telu
- {0xC5C60000u, 41u}, // gor -> Latn
- {0xC9C60000u, 41u}, // gos -> Latn
+ {0xB5C60000u, 77u}, // gon -> Telu
+ {0xC5C60000u, 40u}, // gor -> Latn
+ {0xC9C60000u, 40u}, // gos -> Latn
{0xCDC60000u, 20u}, // got -> Goth
{0x8A260000u, 14u}, // grc -> Cprt
{0xCE260000u, 7u}, // grt -> Beng
- {0xDA460000u, 41u}, // gsw -> Latn
+ {0xDA260000u, 40u}, // grw -> Latn
+ {0xDA460000u, 40u}, // gsw -> Latn
{0x67750000u, 22u}, // gu -> Gujr
- {0x86860000u, 41u}, // gub -> Latn
- {0x8A860000u, 41u}, // guc -> Latn
- {0xC6860000u, 41u}, // gur -> Latn
- {0xE6860000u, 41u}, // guz -> Latn
- {0x67760000u, 41u}, // gv -> Latn
+ {0x86860000u, 40u}, // gub -> Latn
+ {0x8A860000u, 40u}, // guc -> Latn
+ {0x8E860000u, 40u}, // gud -> Latn
+ {0xC6860000u, 40u}, // gur -> Latn
+ {0xDA860000u, 40u}, // guw -> Latn
+ {0xDE860000u, 40u}, // gux -> Latn
+ {0xE6860000u, 40u}, // guz -> Latn
+ {0x67760000u, 40u}, // gv -> Latn
+ {0x96A60000u, 40u}, // gvf -> Latn
{0xC6A60000u, 16u}, // gvr -> Deva
- {0xA2C60000u, 41u}, // gwi -> Latn
- {0x68610000u, 41u}, // ha -> Latn
+ {0xCAA60000u, 40u}, // gvs -> Latn
+ {0x8AC60000u, 1u}, // gwc -> Arab
+ {0xA2C60000u, 40u}, // gwi -> Latn
+ {0xCEC60000u, 1u}, // gwt -> Arab
+ {0xA3060000u, 40u}, // gyi -> Latn
+ {0x68610000u, 40u}, // ha -> Latn
{0x6861434Du, 1u}, // ha-CM -> Arab
{0x68615344u, 1u}, // ha-SD -> Arab
+ {0x98070000u, 40u}, // hag -> Latn
{0xA8070000u, 24u}, // hak -> Hans
- {0xD8070000u, 41u}, // haw -> Latn
+ {0xB0070000u, 40u}, // ham -> Latn
+ {0xD8070000u, 40u}, // haw -> Latn
{0xE4070000u, 1u}, // haz -> Arab
+ {0x84270000u, 40u}, // hbb -> Latn
+ {0xE0670000u, 18u}, // hdy -> Ethi
{0x68650000u, 27u}, // he -> Hebr
+ {0xE0E70000u, 40u}, // hhy -> Latn
{0x68690000u, 16u}, // hi -> Deva
- {0x95070000u, 41u}, // hif -> Latn
- {0xAD070000u, 41u}, // hil -> Latn
+ {0x81070000u, 40u}, // hia -> Latn
+ {0x95070000u, 40u}, // hif -> Latn
+ {0x99070000u, 40u}, // hig -> Latn
+ {0x9D070000u, 40u}, // hih -> Latn
+ {0xAD070000u, 40u}, // hil -> Latn
+ {0x81670000u, 40u}, // hla -> Latn
{0xD1670000u, 28u}, // hlu -> Hluw
{0x8D870000u, 62u}, // hmd -> Plrd
+ {0xCD870000u, 40u}, // hmt -> Latn
{0x8DA70000u, 1u}, // hnd -> Arab
{0x91A70000u, 16u}, // hne -> Deva
{0xA5A70000u, 29u}, // hnj -> Hmng
- {0xB5A70000u, 41u}, // hnn -> Latn
+ {0xB5A70000u, 40u}, // hnn -> Latn
{0xB9A70000u, 1u}, // hno -> Arab
- {0x686F0000u, 41u}, // ho -> Latn
+ {0x686F0000u, 40u}, // ho -> Latn
{0x89C70000u, 16u}, // hoc -> Deva
{0xA5C70000u, 16u}, // hoj -> Deva
- {0x68720000u, 41u}, // hr -> Latn
- {0x86470000u, 41u}, // hsb -> Latn
+ {0xCDC70000u, 40u}, // hot -> Latn
+ {0x68720000u, 40u}, // hr -> Latn
+ {0x86470000u, 40u}, // hsb -> Latn
{0xB6470000u, 24u}, // hsn -> Hans
- {0x68740000u, 41u}, // ht -> Latn
- {0x68750000u, 41u}, // hu -> Latn
+ {0x68740000u, 40u}, // ht -> Latn
+ {0x68750000u, 40u}, // hu -> Latn
+ {0xA2870000u, 40u}, // hui -> Latn
{0x68790000u, 3u}, // hy -> Armn
- {0x687A0000u, 41u}, // hz -> Latn
- {0x69610000u, 41u}, // ia -> Latn
- {0x80280000u, 41u}, // iba -> Latn
- {0x84280000u, 41u}, // ibb -> Latn
- {0x69640000u, 41u}, // id -> Latn
- {0x69670000u, 41u}, // ig -> Latn
- {0x69690000u, 85u}, // ii -> Yiii
- {0x696B0000u, 41u}, // ik -> Latn
- {0xCD480000u, 41u}, // ikt -> Latn
- {0xB9680000u, 41u}, // ilo -> Latn
- {0x696E0000u, 41u}, // in -> Latn
+ {0x687A0000u, 40u}, // hz -> Latn
+ {0x69610000u, 40u}, // ia -> Latn
+ {0xB4080000u, 40u}, // ian -> Latn
+ {0xC4080000u, 40u}, // iar -> Latn
+ {0x80280000u, 40u}, // iba -> Latn
+ {0x84280000u, 40u}, // ibb -> Latn
+ {0xE0280000u, 40u}, // iby -> Latn
+ {0x80480000u, 40u}, // ica -> Latn
+ {0x9C480000u, 40u}, // ich -> Latn
+ {0x69640000u, 40u}, // id -> Latn
+ {0x8C680000u, 40u}, // idd -> Latn
+ {0xA0680000u, 40u}, // idi -> Latn
+ {0xD0680000u, 40u}, // idu -> Latn
+ {0x69670000u, 40u}, // ig -> Latn
+ {0x84C80000u, 40u}, // igb -> Latn
+ {0x90C80000u, 40u}, // ige -> Latn
+ {0x69690000u, 86u}, // ii -> Yiii
+ {0xA5280000u, 40u}, // ijj -> Latn
+ {0x696B0000u, 40u}, // ik -> Latn
+ {0xA9480000u, 40u}, // ikk -> Latn
+ {0xCD480000u, 40u}, // ikt -> Latn
+ {0xD9480000u, 40u}, // ikw -> Latn
+ {0xDD480000u, 40u}, // ikx -> Latn
+ {0xB9680000u, 40u}, // ilo -> Latn
+ {0xB9880000u, 40u}, // imo -> Latn
+ {0x696E0000u, 40u}, // in -> Latn
{0x9DA80000u, 15u}, // inh -> Cyrl
- {0x69730000u, 41u}, // is -> Latn
- {0x69740000u, 41u}, // it -> Latn
+ {0xD1C80000u, 40u}, // iou -> Latn
+ {0xA2280000u, 40u}, // iri -> Latn
+ {0x69730000u, 40u}, // is -> Latn
+ {0x69740000u, 40u}, // it -> Latn
{0x69750000u, 9u}, // iu -> Cans
{0x69770000u, 27u}, // iw -> Hebr
- {0x9F280000u, 41u}, // izh -> Latn
+ {0xB2C80000u, 40u}, // iwm -> Latn
+ {0xCAC80000u, 40u}, // iws -> Latn
+ {0x9F280000u, 40u}, // izh -> Latn
+ {0xA3280000u, 40u}, // izi -> Latn
{0x6A610000u, 31u}, // ja -> Jpan
- {0xB0090000u, 41u}, // jam -> Latn
- {0xB8C90000u, 41u}, // jgo -> Latn
+ {0x84090000u, 40u}, // jab -> Latn
+ {0xB0090000u, 40u}, // jam -> Latn
+ {0xD0290000u, 40u}, // jbu -> Latn
+ {0xB4890000u, 40u}, // jen -> Latn
+ {0xA8C90000u, 40u}, // jgk -> Latn
+ {0xB8C90000u, 40u}, // jgo -> Latn
{0x6A690000u, 27u}, // ji -> Hebr
- {0x89890000u, 41u}, // jmc -> Latn
+ {0x85090000u, 40u}, // jib -> Latn
+ {0x89890000u, 40u}, // jmc -> Latn
{0xAD890000u, 16u}, // jml -> Deva
- {0xCE890000u, 41u}, // jut -> Latn
- {0x6A760000u, 41u}, // jv -> Latn
- {0x6A770000u, 41u}, // jw -> Latn
+ {0x82290000u, 40u}, // jra -> Latn
+ {0xCE890000u, 40u}, // jut -> Latn
+ {0x6A760000u, 40u}, // jv -> Latn
+ {0x6A770000u, 40u}, // jw -> Latn
{0x6B610000u, 19u}, // ka -> Geor
{0x800A0000u, 15u}, // kaa -> Cyrl
- {0x840A0000u, 41u}, // kab -> Latn
- {0x880A0000u, 41u}, // kac -> Latn
- {0xA40A0000u, 41u}, // kaj -> Latn
- {0xB00A0000u, 41u}, // kam -> Latn
- {0xB80A0000u, 41u}, // kao -> Latn
+ {0x840A0000u, 40u}, // kab -> Latn
+ {0x880A0000u, 40u}, // kac -> Latn
+ {0x8C0A0000u, 40u}, // kad -> Latn
+ {0xA00A0000u, 40u}, // kai -> Latn
+ {0xA40A0000u, 40u}, // kaj -> Latn
+ {0xB00A0000u, 40u}, // kam -> Latn
+ {0xB80A0000u, 40u}, // kao -> Latn
{0x8C2A0000u, 15u}, // kbd -> Cyrl
- {0x984A0000u, 41u}, // kcg -> Latn
- {0xA84A0000u, 41u}, // kck -> Latn
- {0x906A0000u, 41u}, // kde -> Latn
- {0xCC6A0000u, 79u}, // kdt -> Thai
- {0x808A0000u, 41u}, // kea -> Latn
- {0xB48A0000u, 41u}, // ken -> Latn
- {0xB8AA0000u, 41u}, // kfo -> Latn
+ {0xB02A0000u, 40u}, // kbm -> Latn
+ {0xBC2A0000u, 40u}, // kbp -> Latn
+ {0xC02A0000u, 40u}, // kbq -> Latn
+ {0xDC2A0000u, 40u}, // kbx -> Latn
+ {0xE02A0000u, 1u}, // kby -> Arab
+ {0x984A0000u, 40u}, // kcg -> Latn
+ {0xA84A0000u, 40u}, // kck -> Latn
+ {0xAC4A0000u, 40u}, // kcl -> Latn
+ {0xCC4A0000u, 40u}, // kct -> Latn
+ {0x906A0000u, 40u}, // kde -> Latn
+ {0x9C6A0000u, 1u}, // kdh -> Arab
+ {0xAC6A0000u, 40u}, // kdl -> Latn
+ {0xCC6A0000u, 80u}, // kdt -> Thai
+ {0x808A0000u, 40u}, // kea -> Latn
+ {0xB48A0000u, 40u}, // ken -> Latn
+ {0xE48A0000u, 40u}, // kez -> Latn
+ {0xB8AA0000u, 40u}, // kfo -> Latn
{0xC4AA0000u, 16u}, // kfr -> Deva
{0xE0AA0000u, 16u}, // kfy -> Deva
- {0x6B670000u, 41u}, // kg -> Latn
- {0x90CA0000u, 41u}, // kge -> Latn
- {0xBCCA0000u, 41u}, // kgp -> Latn
- {0x80EA0000u, 41u}, // kha -> Latn
+ {0x6B670000u, 40u}, // kg -> Latn
+ {0x90CA0000u, 40u}, // kge -> Latn
+ {0x94CA0000u, 40u}, // kgf -> Latn
+ {0xBCCA0000u, 40u}, // kgp -> Latn
+ {0x80EA0000u, 40u}, // kha -> Latn
{0x84EA0000u, 73u}, // khb -> Talu
{0xB4EA0000u, 16u}, // khn -> Deva
- {0xC0EA0000u, 41u}, // khq -> Latn
- {0xCCEA0000u, 53u}, // kht -> Mymr
+ {0xC0EA0000u, 40u}, // khq -> Latn
+ {0xC8EA0000u, 40u}, // khs -> Latn
+ {0xCCEA0000u, 52u}, // kht -> Mymr
{0xD8EA0000u, 1u}, // khw -> Arab
- {0x6B690000u, 41u}, // ki -> Latn
- {0xD10A0000u, 41u}, // kiu -> Latn
- {0x6B6A0000u, 41u}, // kj -> Latn
- {0x992A0000u, 40u}, // kjg -> Laoo
+ {0xE4EA0000u, 40u}, // khz -> Latn
+ {0x6B690000u, 40u}, // ki -> Latn
+ {0xA50A0000u, 40u}, // kij -> Latn
+ {0xD10A0000u, 40u}, // kiu -> Latn
+ {0xD90A0000u, 40u}, // kiw -> Latn
+ {0x6B6A0000u, 40u}, // kj -> Latn
+ {0x8D2A0000u, 40u}, // kjd -> Latn
+ {0x992A0000u, 39u}, // kjg -> Laoo
+ {0xC92A0000u, 40u}, // kjs -> Latn
+ {0xE12A0000u, 40u}, // kjy -> Latn
{0x6B6B0000u, 15u}, // kk -> Cyrl
{0x6B6B4146u, 1u}, // kk-AF -> Arab
{0x6B6B434Eu, 1u}, // kk-CN -> Arab
{0x6B6B4952u, 1u}, // kk-IR -> Arab
{0x6B6B4D4Eu, 1u}, // kk-MN -> Arab
- {0xA54A0000u, 41u}, // kkj -> Latn
- {0x6B6C0000u, 41u}, // kl -> Latn
- {0xB56A0000u, 41u}, // kln -> Latn
+ {0x894A0000u, 40u}, // kkc -> Latn
+ {0xA54A0000u, 40u}, // kkj -> Latn
+ {0x6B6C0000u, 40u}, // kl -> Latn
+ {0xB56A0000u, 40u}, // kln -> Latn
+ {0xC16A0000u, 40u}, // klq -> Latn
+ {0xCD6A0000u, 40u}, // klt -> Latn
+ {0xDD6A0000u, 40u}, // klx -> Latn
{0x6B6D0000u, 35u}, // km -> Khmr
- {0x858A0000u, 41u}, // kmb -> Latn
+ {0x858A0000u, 40u}, // kmb -> Latn
+ {0x9D8A0000u, 40u}, // kmh -> Latn
+ {0xB98A0000u, 40u}, // kmo -> Latn
+ {0xC98A0000u, 40u}, // kms -> Latn
+ {0xD18A0000u, 40u}, // kmu -> Latn
+ {0xD98A0000u, 40u}, // kmw -> Latn
{0x6B6E0000u, 36u}, // kn -> Knda
+ {0xBDAA0000u, 40u}, // knp -> Latn
{0x6B6F0000u, 37u}, // ko -> Kore
{0xA1CA0000u, 15u}, // koi -> Cyrl
{0xA9CA0000u, 16u}, // kok -> Deva
- {0xC9CA0000u, 41u}, // kos -> Latn
- {0x91EA0000u, 41u}, // kpe -> Latn
+ {0xADCA0000u, 40u}, // kol -> Latn
+ {0xC9CA0000u, 40u}, // kos -> Latn
+ {0xE5CA0000u, 40u}, // koz -> Latn
+ {0x91EA0000u, 40u}, // kpe -> Latn
+ {0x95EA0000u, 40u}, // kpf -> Latn
+ {0xB9EA0000u, 40u}, // kpo -> Latn
+ {0xC5EA0000u, 40u}, // kpr -> Latn
+ {0xDDEA0000u, 40u}, // kpx -> Latn
+ {0x860A0000u, 40u}, // kqb -> Latn
+ {0x960A0000u, 40u}, // kqf -> Latn
+ {0xCA0A0000u, 40u}, // kqs -> Latn
+ {0xE20A0000u, 18u}, // kqy -> Ethi
{0x8A2A0000u, 15u}, // krc -> Cyrl
- {0xA22A0000u, 41u}, // kri -> Latn
- {0xA62A0000u, 41u}, // krj -> Latn
- {0xAE2A0000u, 41u}, // krl -> Latn
+ {0xA22A0000u, 40u}, // kri -> Latn
+ {0xA62A0000u, 40u}, // krj -> Latn
+ {0xAE2A0000u, 40u}, // krl -> Latn
+ {0xCA2A0000u, 40u}, // krs -> Latn
{0xD22A0000u, 16u}, // kru -> Deva
{0x6B730000u, 1u}, // ks -> Arab
- {0x864A0000u, 41u}, // ksb -> Latn
- {0x964A0000u, 41u}, // ksf -> Latn
- {0x9E4A0000u, 41u}, // ksh -> Latn
- {0x6B750000u, 41u}, // ku -> Latn
+ {0x864A0000u, 40u}, // ksb -> Latn
+ {0x8E4A0000u, 40u}, // ksd -> Latn
+ {0x964A0000u, 40u}, // ksf -> Latn
+ {0x9E4A0000u, 40u}, // ksh -> Latn
+ {0xA64A0000u, 40u}, // ksj -> Latn
+ {0xC64A0000u, 40u}, // ksr -> Latn
+ {0x866A0000u, 18u}, // ktb -> Ethi
+ {0xB26A0000u, 40u}, // ktm -> Latn
+ {0xBA6A0000u, 40u}, // kto -> Latn
+ {0x6B750000u, 40u}, // ku -> Latn
{0x6B754952u, 1u}, // ku-IR -> Arab
{0x6B754C42u, 1u}, // ku-LB -> Arab
+ {0x868A0000u, 40u}, // kub -> Latn
+ {0x8E8A0000u, 40u}, // kud -> Latn
+ {0x928A0000u, 40u}, // kue -> Latn
+ {0xA68A0000u, 40u}, // kuj -> Latn
{0xB28A0000u, 15u}, // kum -> Cyrl
+ {0xB68A0000u, 40u}, // kun -> Latn
+ {0xBE8A0000u, 40u}, // kup -> Latn
+ {0xCA8A0000u, 40u}, // kus -> Latn
{0x6B760000u, 15u}, // kv -> Cyrl
- {0xC6AA0000u, 41u}, // kvr -> Latn
+ {0x9AAA0000u, 40u}, // kvg -> Latn
+ {0xC6AA0000u, 40u}, // kvr -> Latn
{0xDEAA0000u, 1u}, // kvx -> Arab
- {0x6B770000u, 41u}, // kw -> Latn
- {0xB2EA0000u, 79u}, // kxm -> Thai
+ {0x6B770000u, 40u}, // kw -> Latn
+ {0xA6CA0000u, 40u}, // kwj -> Latn
+ {0xBACA0000u, 40u}, // kwo -> Latn
+ {0x82EA0000u, 40u}, // kxa -> Latn
+ {0x8AEA0000u, 18u}, // kxc -> Ethi
+ {0xB2EA0000u, 80u}, // kxm -> Thai
{0xBEEA0000u, 1u}, // kxp -> Arab
+ {0xDAEA0000u, 40u}, // kxw -> Latn
+ {0xE6EA0000u, 40u}, // kxz -> Latn
{0x6B790000u, 15u}, // ky -> Cyrl
{0x6B79434Eu, 1u}, // ky-CN -> Arab
- {0x6B795452u, 41u}, // ky-TR -> Latn
- {0x6C610000u, 41u}, // la -> Latn
- {0x840B0000u, 43u}, // lab -> Lina
+ {0x6B795452u, 40u}, // ky-TR -> Latn
+ {0x930A0000u, 40u}, // kye -> Latn
+ {0xDF0A0000u, 40u}, // kyx -> Latn
+ {0xC72A0000u, 40u}, // kzr -> Latn
+ {0x6C610000u, 40u}, // la -> Latn
+ {0x840B0000u, 42u}, // lab -> Lina
{0x8C0B0000u, 27u}, // lad -> Hebr
- {0x980B0000u, 41u}, // lag -> Latn
+ {0x980B0000u, 40u}, // lag -> Latn
{0x9C0B0000u, 1u}, // lah -> Arab
- {0xA40B0000u, 41u}, // laj -> Latn
- {0x6C620000u, 41u}, // lb -> Latn
+ {0xA40B0000u, 40u}, // laj -> Latn
+ {0xC80B0000u, 40u}, // las -> Latn
+ {0x6C620000u, 40u}, // lb -> Latn
{0x902B0000u, 15u}, // lbe -> Cyrl
- {0xD82B0000u, 41u}, // lbw -> Latn
- {0xBC4B0000u, 79u}, // lcp -> Thai
- {0xBC8B0000u, 42u}, // lep -> Lepc
+ {0xD02B0000u, 40u}, // lbu -> Latn
+ {0xD82B0000u, 40u}, // lbw -> Latn
+ {0xB04B0000u, 40u}, // lcm -> Latn
+ {0xBC4B0000u, 80u}, // lcp -> Thai
+ {0x846B0000u, 40u}, // ldb -> Latn
+ {0x8C8B0000u, 40u}, // led -> Latn
+ {0x908B0000u, 40u}, // lee -> Latn
+ {0xB08B0000u, 40u}, // lem -> Latn
+ {0xBC8B0000u, 41u}, // lep -> Lepc
+ {0xC08B0000u, 40u}, // leq -> Latn
+ {0xD08B0000u, 40u}, // leu -> Latn
{0xE48B0000u, 15u}, // lez -> Cyrl
- {0x6C670000u, 41u}, // lg -> Latn
- {0x6C690000u, 41u}, // li -> Latn
+ {0x6C670000u, 40u}, // lg -> Latn
+ {0x98CB0000u, 40u}, // lgg -> Latn
+ {0x6C690000u, 40u}, // li -> Latn
+ {0x810B0000u, 40u}, // lia -> Latn
+ {0x8D0B0000u, 40u}, // lid -> Latn
{0x950B0000u, 16u}, // lif -> Deva
- {0xA50B0000u, 41u}, // lij -> Latn
- {0xC90B0000u, 44u}, // lis -> Lisu
- {0xBD2B0000u, 41u}, // ljp -> Latn
+ {0x990B0000u, 40u}, // lig -> Latn
+ {0x9D0B0000u, 40u}, // lih -> Latn
+ {0xA50B0000u, 40u}, // lij -> Latn
+ {0xC90B0000u, 43u}, // lis -> Lisu
+ {0xBD2B0000u, 40u}, // ljp -> Latn
{0xA14B0000u, 1u}, // lki -> Arab
- {0xCD4B0000u, 41u}, // lkt -> Latn
- {0xB58B0000u, 76u}, // lmn -> Telu
- {0xB98B0000u, 41u}, // lmo -> Latn
- {0x6C6E0000u, 41u}, // ln -> Latn
- {0x6C6F0000u, 40u}, // lo -> Laoo
- {0xADCB0000u, 41u}, // lol -> Latn
- {0xE5CB0000u, 41u}, // loz -> Latn
+ {0xCD4B0000u, 40u}, // lkt -> Latn
+ {0x916B0000u, 40u}, // lle -> Latn
+ {0xB56B0000u, 40u}, // lln -> Latn
+ {0xB58B0000u, 77u}, // lmn -> Telu
+ {0xB98B0000u, 40u}, // lmo -> Latn
+ {0xBD8B0000u, 40u}, // lmp -> Latn
+ {0x6C6E0000u, 40u}, // ln -> Latn
+ {0xC9AB0000u, 40u}, // lns -> Latn
+ {0xD1AB0000u, 40u}, // lnu -> Latn
+ {0x6C6F0000u, 39u}, // lo -> Laoo
+ {0xA5CB0000u, 40u}, // loj -> Latn
+ {0xA9CB0000u, 40u}, // lok -> Latn
+ {0xADCB0000u, 40u}, // lol -> Latn
+ {0xC5CB0000u, 40u}, // lor -> Latn
+ {0xC9CB0000u, 40u}, // los -> Latn
+ {0xE5CB0000u, 40u}, // loz -> Latn
{0x8A2B0000u, 1u}, // lrc -> Arab
- {0x6C740000u, 41u}, // lt -> Latn
- {0x9A6B0000u, 41u}, // ltg -> Latn
- {0x6C750000u, 41u}, // lu -> Latn
- {0x828B0000u, 41u}, // lua -> Latn
- {0xBA8B0000u, 41u}, // luo -> Latn
- {0xE28B0000u, 41u}, // luy -> Latn
+ {0x6C740000u, 40u}, // lt -> Latn
+ {0x9A6B0000u, 40u}, // ltg -> Latn
+ {0x6C750000u, 40u}, // lu -> Latn
+ {0x828B0000u, 40u}, // lua -> Latn
+ {0xBA8B0000u, 40u}, // luo -> Latn
+ {0xE28B0000u, 40u}, // luy -> Latn
{0xE68B0000u, 1u}, // luz -> Arab
- {0x6C760000u, 41u}, // lv -> Latn
- {0xAECB0000u, 79u}, // lwl -> Thai
+ {0x6C760000u, 40u}, // lv -> Latn
+ {0xAECB0000u, 80u}, // lwl -> Thai
{0x9F2B0000u, 24u}, // lzh -> Hans
- {0xE72B0000u, 41u}, // lzz -> Latn
- {0x8C0C0000u, 41u}, // mad -> Latn
- {0x940C0000u, 41u}, // maf -> Latn
+ {0xE72B0000u, 40u}, // lzz -> Latn
+ {0x8C0C0000u, 40u}, // mad -> Latn
+ {0x940C0000u, 40u}, // maf -> Latn
{0x980C0000u, 16u}, // mag -> Deva
{0xA00C0000u, 16u}, // mai -> Deva
- {0xA80C0000u, 41u}, // mak -> Latn
- {0xB40C0000u, 41u}, // man -> Latn
- {0xB40C474Eu, 55u}, // man-GN -> Nkoo
- {0xC80C0000u, 41u}, // mas -> Latn
- {0xE40C0000u, 41u}, // maz -> Latn
+ {0xA80C0000u, 40u}, // mak -> Latn
+ {0xB40C0000u, 40u}, // man -> Latn
+ {0xB40C474Eu, 54u}, // man-GN -> Nkoo
+ {0xC80C0000u, 40u}, // mas -> Latn
+ {0xD80C0000u, 40u}, // maw -> Latn
+ {0xE40C0000u, 40u}, // maz -> Latn
+ {0x9C2C0000u, 40u}, // mbh -> Latn
+ {0xB82C0000u, 40u}, // mbo -> Latn
+ {0xC02C0000u, 40u}, // mbq -> Latn
+ {0xD02C0000u, 40u}, // mbu -> Latn
+ {0xD82C0000u, 40u}, // mbw -> Latn
+ {0xA04C0000u, 40u}, // mci -> Latn
+ {0xBC4C0000u, 40u}, // mcp -> Latn
+ {0xC04C0000u, 40u}, // mcq -> Latn
+ {0xC44C0000u, 40u}, // mcr -> Latn
+ {0xD04C0000u, 40u}, // mcu -> Latn
+ {0x806C0000u, 40u}, // mda -> Latn
+ {0x906C0000u, 1u}, // mde -> Arab
{0x946C0000u, 15u}, // mdf -> Cyrl
- {0x9C6C0000u, 41u}, // mdh -> Latn
- {0xC46C0000u, 41u}, // mdr -> Latn
- {0xB48C0000u, 41u}, // men -> Latn
- {0xC48C0000u, 41u}, // mer -> Latn
+ {0x9C6C0000u, 40u}, // mdh -> Latn
+ {0xA46C0000u, 40u}, // mdj -> Latn
+ {0xC46C0000u, 40u}, // mdr -> Latn
+ {0xDC6C0000u, 18u}, // mdx -> Ethi
+ {0x8C8C0000u, 40u}, // med -> Latn
+ {0x908C0000u, 40u}, // mee -> Latn
+ {0xA88C0000u, 40u}, // mek -> Latn
+ {0xB48C0000u, 40u}, // men -> Latn
+ {0xC48C0000u, 40u}, // mer -> Latn
+ {0xCC8C0000u, 40u}, // met -> Latn
+ {0xD08C0000u, 40u}, // meu -> Latn
{0x80AC0000u, 1u}, // mfa -> Arab
- {0x90AC0000u, 41u}, // mfe -> Latn
- {0x6D670000u, 41u}, // mg -> Latn
- {0x9CCC0000u, 41u}, // mgh -> Latn
- {0xB8CC0000u, 41u}, // mgo -> Latn
+ {0x90AC0000u, 40u}, // mfe -> Latn
+ {0xB4AC0000u, 40u}, // mfn -> Latn
+ {0xB8AC0000u, 40u}, // mfo -> Latn
+ {0xC0AC0000u, 40u}, // mfq -> Latn
+ {0x6D670000u, 40u}, // mg -> Latn
+ {0x9CCC0000u, 40u}, // mgh -> Latn
+ {0xACCC0000u, 40u}, // mgl -> Latn
+ {0xB8CC0000u, 40u}, // mgo -> Latn
{0xBCCC0000u, 16u}, // mgp -> Deva
- {0xE0CC0000u, 41u}, // mgy -> Latn
- {0x6D680000u, 41u}, // mh -> Latn
- {0x6D690000u, 41u}, // mi -> Latn
- {0xB50C0000u, 41u}, // min -> Latn
+ {0xE0CC0000u, 40u}, // mgy -> Latn
+ {0x6D680000u, 40u}, // mh -> Latn
+ {0xA0EC0000u, 40u}, // mhi -> Latn
+ {0xACEC0000u, 40u}, // mhl -> Latn
+ {0x6D690000u, 40u}, // mi -> Latn
+ {0x950C0000u, 40u}, // mif -> Latn
+ {0xB50C0000u, 40u}, // min -> Latn
{0xC90C0000u, 26u}, // mis -> Hatr
+ {0xD90C0000u, 40u}, // miw -> Latn
{0x6D6B0000u, 15u}, // mk -> Cyrl
- {0x6D6C0000u, 50u}, // ml -> Mlym
- {0xC96C0000u, 41u}, // mls -> Latn
+ {0xA14C0000u, 1u}, // mki -> Arab
+ {0xAD4C0000u, 40u}, // mkl -> Latn
+ {0xBD4C0000u, 40u}, // mkp -> Latn
+ {0xD94C0000u, 40u}, // mkw -> Latn
+ {0x6D6C0000u, 49u}, // ml -> Mlym
+ {0x916C0000u, 40u}, // mle -> Latn
+ {0xBD6C0000u, 40u}, // mlp -> Latn
+ {0xC96C0000u, 40u}, // mls -> Latn
+ {0xB98C0000u, 40u}, // mmo -> Latn
+ {0xD18C0000u, 40u}, // mmu -> Latn
+ {0xDD8C0000u, 40u}, // mmx -> Latn
{0x6D6E0000u, 15u}, // mn -> Cyrl
- {0x6D6E434Eu, 51u}, // mn-CN -> Mong
+ {0x6D6E434Eu, 50u}, // mn-CN -> Mong
+ {0x81AC0000u, 40u}, // mna -> Latn
+ {0x95AC0000u, 40u}, // mnf -> Latn
{0xA1AC0000u, 7u}, // mni -> Beng
- {0xD9AC0000u, 53u}, // mnw -> Mymr
- {0x91CC0000u, 41u}, // moe -> Latn
- {0x9DCC0000u, 41u}, // moh -> Latn
- {0xC9CC0000u, 41u}, // mos -> Latn
+ {0xD9AC0000u, 52u}, // mnw -> Mymr
+ {0x81CC0000u, 40u}, // moa -> Latn
+ {0x91CC0000u, 40u}, // moe -> Latn
+ {0x9DCC0000u, 40u}, // moh -> Latn
+ {0xC9CC0000u, 40u}, // mos -> Latn
+ {0xDDCC0000u, 40u}, // mox -> Latn
+ {0xBDEC0000u, 40u}, // mpp -> Latn
+ {0xC9EC0000u, 40u}, // mps -> Latn
+ {0xCDEC0000u, 40u}, // mpt -> Latn
+ {0xDDEC0000u, 40u}, // mpx -> Latn
+ {0xAE0C0000u, 40u}, // mql -> Latn
{0x6D720000u, 16u}, // mr -> Deva
{0x8E2C0000u, 16u}, // mrd -> Deva
{0xA62C0000u, 15u}, // mrj -> Cyrl
- {0xD22C0000u, 52u}, // mru -> Mroo
- {0x6D730000u, 41u}, // ms -> Latn
+ {0xBA2C0000u, 51u}, // mro -> Mroo
+ {0x6D730000u, 40u}, // ms -> Latn
{0x6D734343u, 1u}, // ms-CC -> Arab
{0x6D734944u, 1u}, // ms-ID -> Arab
- {0x6D740000u, 41u}, // mt -> Latn
+ {0x6D740000u, 40u}, // mt -> Latn
+ {0x8A6C0000u, 40u}, // mtc -> Latn
+ {0x966C0000u, 40u}, // mtf -> Latn
+ {0xA26C0000u, 40u}, // mti -> Latn
{0xC66C0000u, 16u}, // mtr -> Deva
- {0x828C0000u, 41u}, // mua -> Latn
- {0xCA8C0000u, 41u}, // mus -> Latn
+ {0x828C0000u, 40u}, // mua -> Latn
+ {0xC68C0000u, 40u}, // mur -> Latn
+ {0xCA8C0000u, 40u}, // mus -> Latn
+ {0x82AC0000u, 40u}, // mva -> Latn
+ {0xB6AC0000u, 40u}, // mvn -> Latn
{0xE2AC0000u, 1u}, // mvy -> Arab
- {0xAACC0000u, 41u}, // mwk -> Latn
+ {0xAACC0000u, 40u}, // mwk -> Latn
{0xC6CC0000u, 16u}, // mwr -> Deva
- {0xD6CC0000u, 41u}, // mwv -> Latn
- {0x8AEC0000u, 41u}, // mxc -> Latn
- {0x6D790000u, 53u}, // my -> Mymr
+ {0xD6CC0000u, 40u}, // mwv -> Latn
+ {0x8AEC0000u, 40u}, // mxc -> Latn
+ {0xB2EC0000u, 40u}, // mxm -> Latn
+ {0x6D790000u, 52u}, // my -> Mymr
+ {0xAB0C0000u, 40u}, // myk -> Latn
+ {0xB30C0000u, 18u}, // mym -> Ethi
{0xD70C0000u, 15u}, // myv -> Cyrl
- {0xDF0C0000u, 41u}, // myx -> Latn
- {0xE70C0000u, 47u}, // myz -> Mand
+ {0xDB0C0000u, 40u}, // myw -> Latn
+ {0xDF0C0000u, 40u}, // myx -> Latn
+ {0xE70C0000u, 46u}, // myz -> Mand
+ {0xAB2C0000u, 40u}, // mzk -> Latn
+ {0xB32C0000u, 40u}, // mzm -> Latn
{0xB72C0000u, 1u}, // mzn -> Arab
- {0x6E610000u, 41u}, // na -> Latn
+ {0xBF2C0000u, 40u}, // mzp -> Latn
+ {0xDB2C0000u, 40u}, // mzw -> Latn
+ {0xE72C0000u, 40u}, // mzz -> Latn
+ {0x6E610000u, 40u}, // na -> Latn
+ {0x880D0000u, 40u}, // nac -> Latn
+ {0x940D0000u, 40u}, // naf -> Latn
+ {0xA80D0000u, 40u}, // nak -> Latn
{0xB40D0000u, 24u}, // nan -> Hans
- {0xBC0D0000u, 41u}, // nap -> Latn
- {0xC00D0000u, 41u}, // naq -> Latn
- {0x6E620000u, 41u}, // nb -> Latn
- {0x9C4D0000u, 41u}, // nch -> Latn
- {0x6E640000u, 41u}, // nd -> Latn
- {0x886D0000u, 41u}, // ndc -> Latn
- {0xC86D0000u, 41u}, // nds -> Latn
+ {0xBC0D0000u, 40u}, // nap -> Latn
+ {0xC00D0000u, 40u}, // naq -> Latn
+ {0xC80D0000u, 40u}, // nas -> Latn
+ {0x6E620000u, 40u}, // nb -> Latn
+ {0x804D0000u, 40u}, // nca -> Latn
+ {0x904D0000u, 40u}, // nce -> Latn
+ {0x944D0000u, 40u}, // ncf -> Latn
+ {0x9C4D0000u, 40u}, // nch -> Latn
+ {0xB84D0000u, 40u}, // nco -> Latn
+ {0xD04D0000u, 40u}, // ncu -> Latn
+ {0x6E640000u, 40u}, // nd -> Latn
+ {0x886D0000u, 40u}, // ndc -> Latn
+ {0xC86D0000u, 40u}, // nds -> Latn
{0x6E650000u, 16u}, // ne -> Deva
+ {0x848D0000u, 40u}, // neb -> Latn
{0xD88D0000u, 16u}, // new -> Deva
- {0x6E670000u, 41u}, // ng -> Latn
- {0xACCD0000u, 41u}, // ngl -> Latn
- {0x90ED0000u, 41u}, // nhe -> Latn
- {0xD8ED0000u, 41u}, // nhw -> Latn
- {0xA50D0000u, 41u}, // nij -> Latn
- {0xD10D0000u, 41u}, // niu -> Latn
- {0xB92D0000u, 41u}, // njo -> Latn
- {0x6E6C0000u, 41u}, // nl -> Latn
- {0x998D0000u, 41u}, // nmg -> Latn
- {0x6E6E0000u, 41u}, // nn -> Latn
- {0x9DAD0000u, 41u}, // nnh -> Latn
- {0x6E6F0000u, 41u}, // no -> Latn
- {0x8DCD0000u, 39u}, // nod -> Lana
+ {0xDC8D0000u, 40u}, // nex -> Latn
+ {0xC4AD0000u, 40u}, // nfr -> Latn
+ {0x6E670000u, 40u}, // ng -> Latn
+ {0x80CD0000u, 40u}, // nga -> Latn
+ {0x84CD0000u, 40u}, // ngb -> Latn
+ {0xACCD0000u, 40u}, // ngl -> Latn
+ {0x84ED0000u, 40u}, // nhb -> Latn
+ {0x90ED0000u, 40u}, // nhe -> Latn
+ {0xD8ED0000u, 40u}, // nhw -> Latn
+ {0x950D0000u, 40u}, // nif -> Latn
+ {0xA10D0000u, 40u}, // nii -> Latn
+ {0xA50D0000u, 40u}, // nij -> Latn
+ {0xB50D0000u, 40u}, // nin -> Latn
+ {0xD10D0000u, 40u}, // niu -> Latn
+ {0xE10D0000u, 40u}, // niy -> Latn
+ {0xE50D0000u, 40u}, // niz -> Latn
+ {0xB92D0000u, 40u}, // njo -> Latn
+ {0x994D0000u, 40u}, // nkg -> Latn
+ {0xB94D0000u, 40u}, // nko -> Latn
+ {0x6E6C0000u, 40u}, // nl -> Latn
+ {0x998D0000u, 40u}, // nmg -> Latn
+ {0xE58D0000u, 40u}, // nmz -> Latn
+ {0x6E6E0000u, 40u}, // nn -> Latn
+ {0x95AD0000u, 40u}, // nnf -> Latn
+ {0x9DAD0000u, 40u}, // nnh -> Latn
+ {0xA9AD0000u, 40u}, // nnk -> Latn
+ {0xB1AD0000u, 40u}, // nnm -> Latn
+ {0x6E6F0000u, 40u}, // no -> Latn
+ {0x8DCD0000u, 38u}, // nod -> Lana
{0x91CD0000u, 16u}, // noe -> Deva
{0xB5CD0000u, 64u}, // non -> Runr
- {0xBA0D0000u, 55u}, // nqo -> Nkoo
- {0x6E720000u, 41u}, // nr -> Latn
+ {0xBDCD0000u, 40u}, // nop -> Latn
+ {0xD1CD0000u, 40u}, // nou -> Latn
+ {0xBA0D0000u, 54u}, // nqo -> Nkoo
+ {0x6E720000u, 40u}, // nr -> Latn
+ {0x862D0000u, 40u}, // nrb -> Latn
{0xAA4D0000u, 9u}, // nsk -> Cans
- {0xBA4D0000u, 41u}, // nso -> Latn
- {0xCA8D0000u, 41u}, // nus -> Latn
- {0x6E760000u, 41u}, // nv -> Latn
- {0xC2ED0000u, 41u}, // nxq -> Latn
- {0x6E790000u, 41u}, // ny -> Latn
- {0xB30D0000u, 41u}, // nym -> Latn
- {0xB70D0000u, 41u}, // nyn -> Latn
- {0xA32D0000u, 41u}, // nzi -> Latn
- {0x6F630000u, 41u}, // oc -> Latn
- {0x6F6D0000u, 41u}, // om -> Latn
- {0x6F720000u, 58u}, // or -> Orya
+ {0xB64D0000u, 40u}, // nsn -> Latn
+ {0xBA4D0000u, 40u}, // nso -> Latn
+ {0xCA4D0000u, 40u}, // nss -> Latn
+ {0xB26D0000u, 40u}, // ntm -> Latn
+ {0xC66D0000u, 40u}, // ntr -> Latn
+ {0xA28D0000u, 40u}, // nui -> Latn
+ {0xBE8D0000u, 40u}, // nup -> Latn
+ {0xCA8D0000u, 40u}, // nus -> Latn
+ {0xD68D0000u, 40u}, // nuv -> Latn
+ {0xDE8D0000u, 40u}, // nux -> Latn
+ {0x6E760000u, 40u}, // nv -> Latn
+ {0x86CD0000u, 40u}, // nwb -> Latn
+ {0xC2ED0000u, 40u}, // nxq -> Latn
+ {0xC6ED0000u, 40u}, // nxr -> Latn
+ {0x6E790000u, 40u}, // ny -> Latn
+ {0xB30D0000u, 40u}, // nym -> Latn
+ {0xB70D0000u, 40u}, // nyn -> Latn
+ {0xA32D0000u, 40u}, // nzi -> Latn
+ {0x6F630000u, 40u}, // oc -> Latn
+ {0x88CE0000u, 40u}, // ogc -> Latn
+ {0xC54E0000u, 40u}, // okr -> Latn
+ {0xD54E0000u, 40u}, // okv -> Latn
+ {0x6F6D0000u, 40u}, // om -> Latn
+ {0x99AE0000u, 40u}, // ong -> Latn
+ {0xB5AE0000u, 40u}, // onn -> Latn
+ {0xC9AE0000u, 40u}, // ons -> Latn
+ {0xB1EE0000u, 40u}, // opm -> Latn
+ {0x6F720000u, 57u}, // or -> Orya
+ {0xBA2E0000u, 40u}, // oro -> Latn
+ {0xD22E0000u, 1u}, // oru -> Arab
{0x6F730000u, 15u}, // os -> Cyrl
- {0xAA6E0000u, 57u}, // otk -> Orkh
+ {0x824E0000u, 58u}, // osa -> Osge
+ {0x826E0000u, 1u}, // ota -> Arab
+ {0xAA6E0000u, 56u}, // otk -> Orkh
+ {0xB32E0000u, 40u}, // ozm -> Latn
{0x70610000u, 23u}, // pa -> Guru
{0x7061504Bu, 1u}, // pa-PK -> Arab
- {0x980F0000u, 41u}, // pag -> Latn
+ {0x980F0000u, 40u}, // pag -> Latn
{0xAC0F0000u, 60u}, // pal -> Phli
- {0xB00F0000u, 41u}, // pam -> Latn
- {0xBC0F0000u, 41u}, // pap -> Latn
- {0xD00F0000u, 41u}, // pau -> Latn
- {0x8C4F0000u, 41u}, // pcd -> Latn
- {0xB04F0000u, 41u}, // pcm -> Latn
- {0x886F0000u, 41u}, // pdc -> Latn
- {0xCC6F0000u, 41u}, // pdt -> Latn
- {0xB88F0000u, 83u}, // peo -> Xpeo
- {0xACAF0000u, 41u}, // pfl -> Latn
+ {0xB00F0000u, 40u}, // pam -> Latn
+ {0xBC0F0000u, 40u}, // pap -> Latn
+ {0xD00F0000u, 40u}, // pau -> Latn
+ {0xA02F0000u, 40u}, // pbi -> Latn
+ {0x8C4F0000u, 40u}, // pcd -> Latn
+ {0xB04F0000u, 40u}, // pcm -> Latn
+ {0x886F0000u, 40u}, // pdc -> Latn
+ {0xCC6F0000u, 40u}, // pdt -> Latn
+ {0x8C8F0000u, 40u}, // ped -> Latn
+ {0xB88F0000u, 84u}, // peo -> Xpeo
+ {0xDC8F0000u, 40u}, // pex -> Latn
+ {0xACAF0000u, 40u}, // pfl -> Latn
+ {0xACEF0000u, 1u}, // phl -> Arab
{0xB4EF0000u, 61u}, // phn -> Phnx
+ {0xAD0F0000u, 40u}, // pil -> Latn
+ {0xBD0F0000u, 40u}, // pip -> Latn
{0x814F0000u, 8u}, // pka -> Brah
- {0xB94F0000u, 41u}, // pko -> Latn
- {0x706C0000u, 41u}, // pl -> Latn
- {0xC98F0000u, 41u}, // pms -> Latn
+ {0xB94F0000u, 40u}, // pko -> Latn
+ {0x706C0000u, 40u}, // pl -> Latn
+ {0x816F0000u, 40u}, // pla -> Latn
+ {0xC98F0000u, 40u}, // pms -> Latn
+ {0x99AF0000u, 40u}, // png -> Latn
+ {0xB5AF0000u, 40u}, // pnn -> Latn
{0xCDAF0000u, 21u}, // pnt -> Grek
- {0xB5CF0000u, 41u}, // pon -> Latn
+ {0xB5CF0000u, 40u}, // pon -> Latn
+ {0xB9EF0000u, 40u}, // ppo -> Latn
{0x822F0000u, 34u}, // pra -> Khar
{0x8E2F0000u, 1u}, // prd -> Arab
- {0x9A2F0000u, 41u}, // prg -> Latn
+ {0x9A2F0000u, 40u}, // prg -> Latn
{0x70730000u, 1u}, // ps -> Arab
- {0x70740000u, 41u}, // pt -> Latn
- {0xD28F0000u, 41u}, // puu -> Latn
- {0x71750000u, 41u}, // qu -> Latn
- {0x8A900000u, 41u}, // quc -> Latn
- {0x9A900000u, 41u}, // qug -> Latn
+ {0xCA4F0000u, 40u}, // pss -> Latn
+ {0x70740000u, 40u}, // pt -> Latn
+ {0xBE6F0000u, 40u}, // ptp -> Latn
+ {0xD28F0000u, 40u}, // puu -> Latn
+ {0x82CF0000u, 40u}, // pwa -> Latn
+ {0x71750000u, 40u}, // qu -> Latn
+ {0x8A900000u, 40u}, // quc -> Latn
+ {0x9A900000u, 40u}, // qug -> Latn
+ {0xA0110000u, 40u}, // rai -> Latn
{0xA4110000u, 16u}, // raj -> Deva
- {0x94510000u, 41u}, // rcf -> Latn
- {0xA4910000u, 41u}, // rej -> Latn
- {0xB4D10000u, 41u}, // rgn -> Latn
- {0x81110000u, 41u}, // ria -> Latn
- {0x95110000u, 77u}, // rif -> Tfng
- {0x95114E4Cu, 41u}, // rif-NL -> Latn
+ {0xB8110000u, 40u}, // rao -> Latn
+ {0x94510000u, 40u}, // rcf -> Latn
+ {0xA4910000u, 40u}, // rej -> Latn
+ {0xAC910000u, 40u}, // rel -> Latn
+ {0xC8910000u, 40u}, // res -> Latn
+ {0xB4D10000u, 40u}, // rgn -> Latn
+ {0x98F10000u, 1u}, // rhg -> Arab
+ {0x81110000u, 40u}, // ria -> Latn
+ {0x95110000u, 78u}, // rif -> Tfng
+ {0x95114E4Cu, 40u}, // rif-NL -> Latn
{0xC9310000u, 16u}, // rjs -> Deva
{0xCD510000u, 7u}, // rkt -> Beng
- {0x726D0000u, 41u}, // rm -> Latn
- {0x95910000u, 41u}, // rmf -> Latn
- {0xB9910000u, 41u}, // rmo -> Latn
+ {0x726D0000u, 40u}, // rm -> Latn
+ {0x95910000u, 40u}, // rmf -> Latn
+ {0xB9910000u, 40u}, // rmo -> Latn
{0xCD910000u, 1u}, // rmt -> Arab
- {0xD1910000u, 41u}, // rmu -> Latn
- {0x726E0000u, 41u}, // rn -> Latn
- {0x99B10000u, 41u}, // rng -> Latn
- {0x726F0000u, 41u}, // ro -> Latn
- {0x85D10000u, 41u}, // rob -> Latn
- {0x95D10000u, 41u}, // rof -> Latn
- {0xB2710000u, 41u}, // rtm -> Latn
+ {0xD1910000u, 40u}, // rmu -> Latn
+ {0x726E0000u, 40u}, // rn -> Latn
+ {0x81B10000u, 40u}, // rna -> Latn
+ {0x99B10000u, 40u}, // rng -> Latn
+ {0x726F0000u, 40u}, // ro -> Latn
+ {0x85D10000u, 40u}, // rob -> Latn
+ {0x95D10000u, 40u}, // rof -> Latn
+ {0xB9D10000u, 40u}, // roo -> Latn
+ {0xBA310000u, 40u}, // rro -> Latn
+ {0xB2710000u, 40u}, // rtm -> Latn
{0x72750000u, 15u}, // ru -> Cyrl
{0x92910000u, 15u}, // rue -> Cyrl
- {0x9A910000u, 41u}, // rug -> Latn
- {0x72770000u, 41u}, // rw -> Latn
- {0xAAD10000u, 41u}, // rwk -> Latn
+ {0x9A910000u, 40u}, // rug -> Latn
+ {0x72770000u, 40u}, // rw -> Latn
+ {0xAAD10000u, 40u}, // rwk -> Latn
+ {0xBAD10000u, 40u}, // rwo -> Latn
{0xD3110000u, 33u}, // ryu -> Kana
{0x73610000u, 16u}, // sa -> Deva
- {0x94120000u, 41u}, // saf -> Latn
+ {0x94120000u, 40u}, // saf -> Latn
{0x9C120000u, 15u}, // sah -> Cyrl
- {0xC0120000u, 41u}, // saq -> Latn
- {0xC8120000u, 41u}, // sas -> Latn
- {0xCC120000u, 41u}, // sat -> Latn
+ {0xC0120000u, 40u}, // saq -> Latn
+ {0xC8120000u, 40u}, // sas -> Latn
+ {0xCC120000u, 40u}, // sat -> Latn
{0xE4120000u, 67u}, // saz -> Saur
- {0xBC320000u, 41u}, // sbp -> Latn
- {0x73630000u, 41u}, // sc -> Latn
+ {0x80320000u, 40u}, // sba -> Latn
+ {0x90320000u, 40u}, // sbe -> Latn
+ {0xBC320000u, 40u}, // sbp -> Latn
+ {0x73630000u, 40u}, // sc -> Latn
{0xA8520000u, 16u}, // sck -> Deva
- {0xB4520000u, 41u}, // scn -> Latn
- {0xB8520000u, 41u}, // sco -> Latn
- {0xC8520000u, 41u}, // scs -> Latn
+ {0xAC520000u, 1u}, // scl -> Arab
+ {0xB4520000u, 40u}, // scn -> Latn
+ {0xB8520000u, 40u}, // sco -> Latn
+ {0xC8520000u, 40u}, // scs -> Latn
{0x73640000u, 1u}, // sd -> Arab
- {0x88720000u, 41u}, // sdc -> Latn
+ {0x88720000u, 40u}, // sdc -> Latn
{0x9C720000u, 1u}, // sdh -> Arab
- {0x73650000u, 41u}, // se -> Latn
- {0x94920000u, 41u}, // sef -> Latn
- {0x9C920000u, 41u}, // seh -> Latn
- {0xA0920000u, 41u}, // sei -> Latn
- {0xC8920000u, 41u}, // ses -> Latn
- {0x73670000u, 41u}, // sg -> Latn
- {0x80D20000u, 56u}, // sga -> Ogam
- {0xC8D20000u, 41u}, // sgs -> Latn
- {0x73680000u, 41u}, // sh -> Latn
- {0xA0F20000u, 77u}, // shi -> Tfng
- {0xB4F20000u, 53u}, // shn -> Mymr
+ {0x73650000u, 40u}, // se -> Latn
+ {0x94920000u, 40u}, // sef -> Latn
+ {0x9C920000u, 40u}, // seh -> Latn
+ {0xA0920000u, 40u}, // sei -> Latn
+ {0xC8920000u, 40u}, // ses -> Latn
+ {0x73670000u, 40u}, // sg -> Latn
+ {0x80D20000u, 55u}, // sga -> Ogam
+ {0xC8D20000u, 40u}, // sgs -> Latn
+ {0xD8D20000u, 18u}, // sgw -> Ethi
+ {0xE4D20000u, 40u}, // sgz -> Latn
+ {0x73680000u, 40u}, // sh -> Latn
+ {0xA0F20000u, 78u}, // shi -> Tfng
+ {0xA8F20000u, 40u}, // shk -> Latn
+ {0xB4F20000u, 52u}, // shn -> Mymr
+ {0xD0F20000u, 1u}, // shu -> Arab
{0x73690000u, 69u}, // si -> Sinh
- {0x8D120000u, 41u}, // sid -> Latn
- {0x736B0000u, 41u}, // sk -> Latn
+ {0x8D120000u, 40u}, // sid -> Latn
+ {0x99120000u, 40u}, // sig -> Latn
+ {0xAD120000u, 40u}, // sil -> Latn
+ {0xB1120000u, 40u}, // sim -> Latn
+ {0xC5320000u, 40u}, // sjr -> Latn
+ {0x736B0000u, 40u}, // sk -> Latn
+ {0x89520000u, 40u}, // skc -> Latn
{0xC5520000u, 1u}, // skr -> Arab
- {0x736C0000u, 41u}, // sl -> Latn
- {0xA1720000u, 41u}, // sli -> Latn
- {0xE1720000u, 41u}, // sly -> Latn
- {0x736D0000u, 41u}, // sm -> Latn
- {0x81920000u, 41u}, // sma -> Latn
- {0xA5920000u, 41u}, // smj -> Latn
- {0xB5920000u, 41u}, // smn -> Latn
+ {0xC9520000u, 40u}, // sks -> Latn
+ {0x736C0000u, 40u}, // sl -> Latn
+ {0x8D720000u, 40u}, // sld -> Latn
+ {0xA1720000u, 40u}, // sli -> Latn
+ {0xAD720000u, 40u}, // sll -> Latn
+ {0xE1720000u, 40u}, // sly -> Latn
+ {0x736D0000u, 40u}, // sm -> Latn
+ {0x81920000u, 40u}, // sma -> Latn
+ {0xA5920000u, 40u}, // smj -> Latn
+ {0xB5920000u, 40u}, // smn -> Latn
{0xBD920000u, 65u}, // smp -> Samr
- {0xC9920000u, 41u}, // sms -> Latn
- {0x736E0000u, 41u}, // sn -> Latn
- {0xA9B20000u, 41u}, // snk -> Latn
- {0x736F0000u, 41u}, // so -> Latn
- {0xD1D20000u, 79u}, // sou -> Thai
- {0x73710000u, 41u}, // sq -> Latn
+ {0xC1920000u, 40u}, // smq -> Latn
+ {0xC9920000u, 40u}, // sms -> Latn
+ {0x736E0000u, 40u}, // sn -> Latn
+ {0x89B20000u, 40u}, // snc -> Latn
+ {0xA9B20000u, 40u}, // snk -> Latn
+ {0xBDB20000u, 40u}, // snp -> Latn
+ {0xDDB20000u, 40u}, // snx -> Latn
+ {0xE1B20000u, 40u}, // sny -> Latn
+ {0x736F0000u, 40u}, // so -> Latn
+ {0xA9D20000u, 40u}, // sok -> Latn
+ {0xC1D20000u, 40u}, // soq -> Latn
+ {0xD1D20000u, 80u}, // sou -> Thai
+ {0xE1D20000u, 40u}, // soy -> Latn
+ {0x8DF20000u, 40u}, // spd -> Latn
+ {0xADF20000u, 40u}, // spl -> Latn
+ {0xC9F20000u, 40u}, // sps -> Latn
+ {0x73710000u, 40u}, // sq -> Latn
{0x73720000u, 15u}, // sr -> Cyrl
- {0x73724D45u, 41u}, // sr-ME -> Latn
- {0x7372524Fu, 41u}, // sr-RO -> Latn
- {0x73725255u, 41u}, // sr-RU -> Latn
- {0x73725452u, 41u}, // sr-TR -> Latn
+ {0x73724D45u, 40u}, // sr-ME -> Latn
+ {0x7372524Fu, 40u}, // sr-RO -> Latn
+ {0x73725255u, 40u}, // sr-RU -> Latn
+ {0x73725452u, 40u}, // sr-TR -> Latn
{0x86320000u, 70u}, // srb -> Sora
- {0xB6320000u, 41u}, // srn -> Latn
- {0xC6320000u, 41u}, // srr -> Latn
+ {0xB6320000u, 40u}, // srn -> Latn
+ {0xC6320000u, 40u}, // srr -> Latn
{0xDE320000u, 16u}, // srx -> Deva
- {0x73730000u, 41u}, // ss -> Latn
- {0xE2520000u, 41u}, // ssy -> Latn
- {0x73740000u, 41u}, // st -> Latn
- {0xC2720000u, 41u}, // stq -> Latn
- {0x73750000u, 41u}, // su -> Latn
- {0xAA920000u, 41u}, // suk -> Latn
- {0xCA920000u, 41u}, // sus -> Latn
- {0x73760000u, 41u}, // sv -> Latn
- {0x73770000u, 41u}, // sw -> Latn
+ {0x73730000u, 40u}, // ss -> Latn
+ {0x8E520000u, 40u}, // ssd -> Latn
+ {0x9A520000u, 40u}, // ssg -> Latn
+ {0xE2520000u, 40u}, // ssy -> Latn
+ {0x73740000u, 40u}, // st -> Latn
+ {0xAA720000u, 40u}, // stk -> Latn
+ {0xC2720000u, 40u}, // stq -> Latn
+ {0x73750000u, 40u}, // su -> Latn
+ {0x82920000u, 40u}, // sua -> Latn
+ {0x92920000u, 40u}, // sue -> Latn
+ {0xAA920000u, 40u}, // suk -> Latn
+ {0xC6920000u, 40u}, // sur -> Latn
+ {0xCA920000u, 40u}, // sus -> Latn
+ {0x73760000u, 40u}, // sv -> Latn
+ {0x73770000u, 40u}, // sw -> Latn
{0x86D20000u, 1u}, // swb -> Arab
- {0x8AD20000u, 41u}, // swc -> Latn
- {0x9AD20000u, 41u}, // swg -> Latn
+ {0x8AD20000u, 40u}, // swc -> Latn
+ {0x9AD20000u, 40u}, // swg -> Latn
+ {0xBED20000u, 40u}, // swp -> Latn
{0xD6D20000u, 16u}, // swv -> Deva
- {0xB6F20000u, 41u}, // sxn -> Latn
+ {0xB6F20000u, 40u}, // sxn -> Latn
+ {0xDAF20000u, 40u}, // sxw -> Latn
{0xAF120000u, 7u}, // syl -> Beng
{0xC7120000u, 71u}, // syr -> Syrc
- {0xAF320000u, 41u}, // szl -> Latn
+ {0xAF320000u, 40u}, // szl -> Latn
{0x74610000u, 74u}, // ta -> Taml
{0xA4130000u, 16u}, // taj -> Deva
- {0xD8330000u, 41u}, // tbw -> Latn
+ {0xAC130000u, 40u}, // tal -> Latn
+ {0xB4130000u, 40u}, // tan -> Latn
+ {0xC0130000u, 40u}, // taq -> Latn
+ {0x88330000u, 40u}, // tbc -> Latn
+ {0x8C330000u, 40u}, // tbd -> Latn
+ {0x94330000u, 40u}, // tbf -> Latn
+ {0x98330000u, 40u}, // tbg -> Latn
+ {0xB8330000u, 40u}, // tbo -> Latn
+ {0xD8330000u, 40u}, // tbw -> Latn
+ {0xE4330000u, 40u}, // tbz -> Latn
+ {0xA0530000u, 40u}, // tci -> Latn
{0xE0530000u, 36u}, // tcy -> Knda
{0x8C730000u, 72u}, // tdd -> Tale
{0x98730000u, 16u}, // tdg -> Deva
{0x9C730000u, 16u}, // tdh -> Deva
- {0x74650000u, 76u}, // te -> Telu
- {0xB0930000u, 41u}, // tem -> Latn
- {0xB8930000u, 41u}, // teo -> Latn
- {0xCC930000u, 41u}, // tet -> Latn
+ {0x74650000u, 77u}, // te -> Telu
+ {0x8C930000u, 40u}, // ted -> Latn
+ {0xB0930000u, 40u}, // tem -> Latn
+ {0xB8930000u, 40u}, // teo -> Latn
+ {0xCC930000u, 40u}, // tet -> Latn
+ {0xA0B30000u, 40u}, // tfi -> Latn
{0x74670000u, 15u}, // tg -> Cyrl
{0x7467504Bu, 1u}, // tg-PK -> Arab
- {0x74680000u, 79u}, // th -> Thai
+ {0x88D30000u, 40u}, // tgc -> Latn
+ {0xB8D30000u, 40u}, // tgo -> Latn
+ {0xD0D30000u, 40u}, // tgu -> Latn
+ {0x74680000u, 80u}, // th -> Thai
{0xACF30000u, 16u}, // thl -> Deva
{0xC0F30000u, 16u}, // thq -> Deva
{0xC4F30000u, 16u}, // thr -> Deva
{0x74690000u, 18u}, // ti -> Ethi
+ {0x95130000u, 40u}, // tif -> Latn
{0x99130000u, 18u}, // tig -> Ethi
- {0xD5130000u, 41u}, // tiv -> Latn
- {0x746B0000u, 41u}, // tk -> Latn
- {0xAD530000u, 41u}, // tkl -> Latn
- {0xC5530000u, 41u}, // tkr -> Latn
+ {0xA9130000u, 40u}, // tik -> Latn
+ {0xB1130000u, 40u}, // tim -> Latn
+ {0xB9130000u, 40u}, // tio -> Latn
+ {0xD5130000u, 40u}, // tiv -> Latn
+ {0x746B0000u, 40u}, // tk -> Latn
+ {0xAD530000u, 40u}, // tkl -> Latn
+ {0xC5530000u, 40u}, // tkr -> Latn
{0xCD530000u, 16u}, // tkt -> Deva
- {0x746C0000u, 41u}, // tl -> Latn
- {0xE1730000u, 41u}, // tly -> Latn
- {0x9D930000u, 41u}, // tmh -> Latn
- {0x746E0000u, 41u}, // tn -> Latn
- {0x746F0000u, 41u}, // to -> Latn
- {0x99D30000u, 41u}, // tog -> Latn
- {0xA1F30000u, 41u}, // tpi -> Latn
- {0x74720000u, 41u}, // tr -> Latn
- {0xD2330000u, 41u}, // tru -> Latn
- {0xD6330000u, 41u}, // trv -> Latn
- {0x74730000u, 41u}, // ts -> Latn
+ {0x746C0000u, 40u}, // tl -> Latn
+ {0x95730000u, 40u}, // tlf -> Latn
+ {0xDD730000u, 40u}, // tlx -> Latn
+ {0xE1730000u, 40u}, // tly -> Latn
+ {0x9D930000u, 40u}, // tmh -> Latn
+ {0xE1930000u, 40u}, // tmy -> Latn
+ {0x746E0000u, 40u}, // tn -> Latn
+ {0x9DB30000u, 40u}, // tnh -> Latn
+ {0x746F0000u, 40u}, // to -> Latn
+ {0x95D30000u, 40u}, // tof -> Latn
+ {0x99D30000u, 40u}, // tog -> Latn
+ {0xC1D30000u, 40u}, // toq -> Latn
+ {0xA1F30000u, 40u}, // tpi -> Latn
+ {0xB1F30000u, 40u}, // tpm -> Latn
+ {0xE5F30000u, 40u}, // tpz -> Latn
+ {0xBA130000u, 40u}, // tqo -> Latn
+ {0x74720000u, 40u}, // tr -> Latn
+ {0xD2330000u, 40u}, // tru -> Latn
+ {0xD6330000u, 40u}, // trv -> Latn
+ {0xDA330000u, 1u}, // trw -> Arab
+ {0x74730000u, 40u}, // ts -> Latn
{0x8E530000u, 21u}, // tsd -> Grek
{0x96530000u, 16u}, // tsf -> Deva
- {0x9A530000u, 41u}, // tsg -> Latn
- {0xA6530000u, 80u}, // tsj -> Tibt
+ {0x9A530000u, 40u}, // tsg -> Latn
+ {0xA6530000u, 81u}, // tsj -> Tibt
+ {0xDA530000u, 40u}, // tsw -> Latn
{0x74740000u, 15u}, // tt -> Cyrl
- {0xA6730000u, 41u}, // ttj -> Latn
- {0xCA730000u, 79u}, // tts -> Thai
- {0xCE730000u, 41u}, // ttt -> Latn
- {0xB2930000u, 41u}, // tum -> Latn
- {0xAEB30000u, 41u}, // tvl -> Latn
- {0xC2D30000u, 41u}, // twq -> Latn
- {0x74790000u, 41u}, // ty -> Latn
+ {0x8E730000u, 40u}, // ttd -> Latn
+ {0x92730000u, 40u}, // tte -> Latn
+ {0xA6730000u, 40u}, // ttj -> Latn
+ {0xC6730000u, 40u}, // ttr -> Latn
+ {0xCA730000u, 80u}, // tts -> Thai
+ {0xCE730000u, 40u}, // ttt -> Latn
+ {0x9E930000u, 40u}, // tuh -> Latn
+ {0xAE930000u, 40u}, // tul -> Latn
+ {0xB2930000u, 40u}, // tum -> Latn
+ {0xC2930000u, 40u}, // tuq -> Latn
+ {0x8EB30000u, 40u}, // tvd -> Latn
+ {0xAEB30000u, 40u}, // tvl -> Latn
+ {0xD2B30000u, 40u}, // tvu -> Latn
+ {0x9ED30000u, 40u}, // twh -> Latn
+ {0xC2D30000u, 40u}, // twq -> Latn
+ {0x9AF30000u, 75u}, // txg -> Tang
+ {0x74790000u, 40u}, // ty -> Latn
+ {0x83130000u, 40u}, // tya -> Latn
{0xD7130000u, 15u}, // tyv -> Cyrl
- {0xB3330000u, 41u}, // tzm -> Latn
+ {0xB3330000u, 40u}, // tzm -> Latn
+ {0xD0340000u, 40u}, // ubu -> Latn
{0xB0740000u, 15u}, // udm -> Cyrl
{0x75670000u, 1u}, // ug -> Arab
{0x75674B5Au, 15u}, // ug-KZ -> Cyrl
{0x75674D4Eu, 15u}, // ug-MN -> Cyrl
- {0x80D40000u, 81u}, // uga -> Ugar
+ {0x80D40000u, 82u}, // uga -> Ugar
{0x756B0000u, 15u}, // uk -> Cyrl
- {0xA1740000u, 41u}, // uli -> Latn
- {0x85940000u, 41u}, // umb -> Latn
+ {0xA1740000u, 40u}, // uli -> Latn
+ {0x85940000u, 40u}, // umb -> Latn
{0xC5B40000u, 7u}, // unr -> Beng
{0xC5B44E50u, 16u}, // unr-NP -> Deva
{0xDDB40000u, 7u}, // unx -> Beng
{0x75720000u, 1u}, // ur -> Arab
- {0x757A0000u, 41u}, // uz -> Latn
+ {0xA2340000u, 40u}, // uri -> Latn
+ {0xCE340000u, 40u}, // urt -> Latn
+ {0xDA340000u, 40u}, // urw -> Latn
+ {0x82540000u, 40u}, // usa -> Latn
+ {0xC6740000u, 40u}, // utr -> Latn
+ {0x9EB40000u, 40u}, // uvh -> Latn
+ {0xAEB40000u, 40u}, // uvl -> Latn
+ {0x757A0000u, 40u}, // uz -> Latn
{0x757A4146u, 1u}, // uz-AF -> Arab
{0x757A434Eu, 15u}, // uz-CN -> Cyrl
- {0xA0150000u, 82u}, // vai -> Vaii
- {0x76650000u, 41u}, // ve -> Latn
- {0x88950000u, 41u}, // vec -> Latn
- {0xBC950000u, 41u}, // vep -> Latn
- {0x76690000u, 41u}, // vi -> Latn
- {0x89150000u, 41u}, // vic -> Latn
- {0xC9750000u, 41u}, // vls -> Latn
- {0x95950000u, 41u}, // vmf -> Latn
- {0xD9950000u, 41u}, // vmw -> Latn
- {0x766F0000u, 41u}, // vo -> Latn
- {0xCDD50000u, 41u}, // vot -> Latn
- {0xBA350000u, 41u}, // vro -> Latn
- {0xB6950000u, 41u}, // vun -> Latn
- {0x77610000u, 41u}, // wa -> Latn
- {0x90160000u, 41u}, // wae -> Latn
+ {0x98150000u, 40u}, // vag -> Latn
+ {0xA0150000u, 83u}, // vai -> Vaii
+ {0xB4150000u, 40u}, // van -> Latn
+ {0x76650000u, 40u}, // ve -> Latn
+ {0x88950000u, 40u}, // vec -> Latn
+ {0xBC950000u, 40u}, // vep -> Latn
+ {0x76690000u, 40u}, // vi -> Latn
+ {0x89150000u, 40u}, // vic -> Latn
+ {0xD5150000u, 40u}, // viv -> Latn
+ {0xC9750000u, 40u}, // vls -> Latn
+ {0x95950000u, 40u}, // vmf -> Latn
+ {0xD9950000u, 40u}, // vmw -> Latn
+ {0x766F0000u, 40u}, // vo -> Latn
+ {0xCDD50000u, 40u}, // vot -> Latn
+ {0xBA350000u, 40u}, // vro -> Latn
+ {0xB6950000u, 40u}, // vun -> Latn
+ {0xCE950000u, 40u}, // vut -> Latn
+ {0x77610000u, 40u}, // wa -> Latn
+ {0x90160000u, 40u}, // wae -> Latn
+ {0xA4160000u, 40u}, // waj -> Latn
{0xAC160000u, 18u}, // wal -> Ethi
- {0xC4160000u, 41u}, // war -> Latn
- {0xBC360000u, 41u}, // wbp -> Latn
- {0xC0360000u, 76u}, // wbq -> Telu
+ {0xB4160000u, 40u}, // wan -> Latn
+ {0xC4160000u, 40u}, // war -> Latn
+ {0xBC360000u, 40u}, // wbp -> Latn
+ {0xC0360000u, 77u}, // wbq -> Telu
{0xC4360000u, 16u}, // wbr -> Deva
- {0xC9760000u, 41u}, // wls -> Latn
+ {0xA0560000u, 40u}, // wci -> Latn
+ {0xC4960000u, 40u}, // wer -> Latn
+ {0xA0D60000u, 40u}, // wgi -> Latn
+ {0x98F60000u, 40u}, // whg -> Latn
+ {0x85160000u, 40u}, // wib -> Latn
+ {0xD1160000u, 40u}, // wiu -> Latn
+ {0xD5160000u, 40u}, // wiv -> Latn
+ {0x81360000u, 40u}, // wja -> Latn
+ {0xA1360000u, 40u}, // wji -> Latn
+ {0xC9760000u, 40u}, // wls -> Latn
+ {0xB9960000u, 40u}, // wmo -> Latn
+ {0x89B60000u, 40u}, // wnc -> Latn
{0xA1B60000u, 1u}, // wni -> Arab
- {0x776F0000u, 41u}, // wo -> Latn
+ {0xD1B60000u, 40u}, // wnu -> Latn
+ {0x776F0000u, 40u}, // wo -> Latn
+ {0x85D60000u, 40u}, // wob -> Latn
+ {0xC9D60000u, 40u}, // wos -> Latn
+ {0xCA360000u, 40u}, // wrs -> Latn
+ {0xAA560000u, 40u}, // wsk -> Latn
{0xB2760000u, 16u}, // wtm -> Deva
{0xD2960000u, 24u}, // wuu -> Hans
- {0xD4170000u, 41u}, // xav -> Latn
+ {0xD6960000u, 40u}, // wuv -> Latn
+ {0x82D60000u, 40u}, // wwa -> Latn
+ {0xD4170000u, 40u}, // xav -> Latn
+ {0xA0370000u, 40u}, // xbi -> Latn
{0xC4570000u, 10u}, // xcr -> Cari
- {0x78680000u, 41u}, // xh -> Latn
- {0x89770000u, 45u}, // xlc -> Lyci
- {0x8D770000u, 46u}, // xld -> Lydi
+ {0xC8970000u, 40u}, // xes -> Latn
+ {0x78680000u, 40u}, // xh -> Latn
+ {0x81770000u, 40u}, // xla -> Latn
+ {0x89770000u, 44u}, // xlc -> Lyci
+ {0x8D770000u, 45u}, // xld -> Lydi
{0x95970000u, 19u}, // xmf -> Geor
- {0xB5970000u, 48u}, // xmn -> Mani
- {0xC5970000u, 49u}, // xmr -> Merc
- {0x81B70000u, 54u}, // xna -> Narb
+ {0xB5970000u, 47u}, // xmn -> Mani
+ {0xC5970000u, 48u}, // xmr -> Merc
+ {0x81B70000u, 53u}, // xna -> Narb
{0xC5B70000u, 16u}, // xnr -> Deva
- {0x99D70000u, 41u}, // xog -> Latn
+ {0x99D70000u, 40u}, // xog -> Latn
+ {0xB5D70000u, 40u}, // xon -> Latn
{0xC5F70000u, 63u}, // xpr -> Prti
+ {0x86370000u, 40u}, // xrb -> Latn
{0x82570000u, 66u}, // xsa -> Sarb
+ {0xA2570000u, 40u}, // xsi -> Latn
+ {0xB2570000u, 40u}, // xsm -> Latn
{0xC6570000u, 16u}, // xsr -> Deva
- {0xB8180000u, 41u}, // yao -> Latn
- {0xBC180000u, 41u}, // yap -> Latn
- {0xD4180000u, 41u}, // yav -> Latn
- {0x84380000u, 41u}, // ybb -> Latn
+ {0x92D70000u, 40u}, // xwe -> Latn
+ {0xB0180000u, 40u}, // yam -> Latn
+ {0xB8180000u, 40u}, // yao -> Latn
+ {0xBC180000u, 40u}, // yap -> Latn
+ {0xC8180000u, 40u}, // yas -> Latn
+ {0xCC180000u, 40u}, // yat -> Latn
+ {0xD4180000u, 40u}, // yav -> Latn
+ {0xE0180000u, 40u}, // yay -> Latn
+ {0xE4180000u, 40u}, // yaz -> Latn
+ {0x80380000u, 40u}, // yba -> Latn
+ {0x84380000u, 40u}, // ybb -> Latn
+ {0xE0380000u, 40u}, // yby -> Latn
+ {0xC4980000u, 40u}, // yer -> Latn
+ {0xC4D80000u, 40u}, // ygr -> Latn
+ {0xD8D80000u, 40u}, // ygw -> Latn
{0x79690000u, 27u}, // yi -> Hebr
- {0x796F0000u, 41u}, // yo -> Latn
- {0xAE380000u, 41u}, // yrl -> Latn
- {0x82980000u, 41u}, // yua -> Latn
- {0x7A610000u, 41u}, // za -> Latn
- {0x98190000u, 41u}, // zag -> Latn
+ {0xB9580000u, 40u}, // yko -> Latn
+ {0x91780000u, 40u}, // yle -> Latn
+ {0x99780000u, 40u}, // ylg -> Latn
+ {0xAD780000u, 40u}, // yll -> Latn
+ {0xAD980000u, 40u}, // yml -> Latn
+ {0x796F0000u, 40u}, // yo -> Latn
+ {0xB5D80000u, 40u}, // yon -> Latn
+ {0x86380000u, 40u}, // yrb -> Latn
+ {0x92380000u, 40u}, // yre -> Latn
+ {0xAE380000u, 40u}, // yrl -> Latn
+ {0xCA580000u, 40u}, // yss -> Latn
+ {0x82980000u, 40u}, // yua -> Latn
+ {0x92980000u, 25u}, // yue -> Hant
+ {0x9298434Eu, 24u}, // yue-CN -> Hans
+ {0xA6980000u, 40u}, // yuj -> Latn
+ {0xCE980000u, 40u}, // yut -> Latn
+ {0xDA980000u, 40u}, // yuw -> Latn
+ {0x7A610000u, 40u}, // za -> Latn
+ {0x98190000u, 40u}, // zag -> Latn
{0xA4790000u, 1u}, // zdj -> Arab
- {0x80990000u, 41u}, // zea -> Latn
- {0x9CD90000u, 77u}, // zgh -> Tfng
+ {0x80990000u, 40u}, // zea -> Latn
+ {0x9CD90000u, 78u}, // zgh -> Tfng
{0x7A680000u, 24u}, // zh -> Hans
{0x7A684155u, 25u}, // zh-AU -> Hant
{0x7A68424Eu, 25u}, // zh-BN -> Hant
@@ -829,9 +1437,12 @@
{0x7A685457u, 25u}, // zh-TW -> Hant
{0x7A685553u, 25u}, // zh-US -> Hant
{0x7A68564Eu, 25u}, // zh-VN -> Hant
- {0xA1990000u, 41u}, // zmi -> Latn
- {0x7A750000u, 41u}, // zu -> Latn
- {0x83390000u, 41u}, // zza -> Latn
+ {0x81190000u, 40u}, // zia -> Latn
+ {0xB1790000u, 40u}, // zlm -> Latn
+ {0xA1990000u, 40u}, // zmi -> Latn
+ {0x91B90000u, 40u}, // zne -> Latn
+ {0x7A750000u, 40u}, // zu -> Latn
+ {0x83390000u, 40u}, // zza -> Latn
});
std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
@@ -854,6 +1465,7 @@
0x616D455445746869llu, // am_Ethi_ET
0xB9804E474C61746Ellu, // amo_Latn_NG
0xE5C049444C61746Ellu, // aoz_Latn_ID
+ 0x8DE0544741726162llu, // apd_Arab_TG
0x6172454741726162llu, // ar_Arab_EG
0x8A20495241726D69llu, // arc_Armi_IR
0x8A204A4F4E626174llu, // arc_Nbat_JO
@@ -896,7 +1508,6 @@
0x88C1494E44657661llu, // bgc_Deva_IN
0xB4C1504B41726162llu, // bgn_Arab_PK
0xDCC154524772656Bllu, // bgx_Grek_TR
- 0x6268494E4B746869llu, // bh_Kthi_IN
0x84E1494E44657661llu, // bhb_Deva_IN
0xA0E1494E44657661llu, // bhi_Deva_IN
0xA8E150484C61746Ellu, // bhk_Latn_PH
@@ -980,6 +1591,7 @@
0x864344454C61746Ellu, // dsb_Latn_DE
0xB2634D4C4C61746Ellu, // dtm_Latn_ML
0xBE634D594C61746Ellu, // dtp_Latn_MY
+ 0xE2634E5044657661llu, // dty_Deva_NP
0x8283434D4C61746Ellu, // dua_Latn_CM
0x64764D5654686161llu, // dv_Thaa_MV
0xBB03534E4C61746Ellu, // dyo_Latn_SN
@@ -1006,6 +1618,7 @@
0xCEE445534C61746Ellu, // ext_Latn_ES
0x6661495241726162llu, // fa_Arab_IR
0xB40547514C61746Ellu, // fan_Latn_GQ
+ 0x6666474E41646C6Dllu, // ff_Adlm_GN
0x6666534E4C61746Ellu, // ff_Latn_SN
0xB0A54D4C4C61746Ellu, // ffm_Latn_ML
0x666946494C61746Ellu, // fi_Latn_FI
@@ -1020,7 +1633,9 @@
0xBE2546524C61746Ellu, // frp_Latn_FR
0xC62544454C61746Ellu, // frr_Latn_DE
0xCA2544454C61746Ellu, // frs_Latn_DE
+ 0x8685434D41726162llu, // fub_Arab_CM
0x8E8557464C61746Ellu, // fud_Latn_WF
+ 0x9685474E4C61746Ellu, // fuf_Latn_GN
0xC2854E454C61746Ellu, // fuq_Latn_NE
0xC68549544C61746Ellu, // fur_Latn_IT
0xD6854E474C61746Ellu, // fuv_Latn_NG
@@ -1104,7 +1719,6 @@
0x6A614A504A70616Ellu, // ja_Jpan_JP
0xB0094A4D4C61746Ellu, // jam_Latn_JM
0xB8C9434D4C61746Ellu, // jgo_Latn_CM
- 0x6A69554148656272llu, // ji_Hebr_UA
0x8989545A4C61746Ellu, // jmc_Latn_TZ
0xAD894E5044657661llu, // jml_Deva_NP
0xCE89444B4C61746Ellu, // jut_Latn_DK
@@ -1118,9 +1732,11 @@
0xB00A4B454C61746Ellu, // kam_Latn_KE
0xB80A4D4C4C61746Ellu, // kao_Latn_ML
0x8C2A52554379726Cllu, // kbd_Cyrl_RU
+ 0xE02A4E4541726162llu, // kby_Arab_NE
0x984A4E474C61746Ellu, // kcg_Latn_NG
0xA84A5A574C61746Ellu, // kck_Latn_ZW
0x906A545A4C61746Ellu, // kde_Latn_TZ
+ 0x9C6A544741726162llu, // kdh_Arab_TG
0xCC6A544854686169llu, // kdt_Thai_TH
0x808A43564C61746Ellu, // kea_Latn_CV
0xB48A434D4C61746Ellu, // ken_Latn_CM
@@ -1251,7 +1867,7 @@
0x6D72494E44657661llu, // mr_Deva_IN
0x8E2C4E5044657661llu, // mrd_Deva_NP
0xA62C52554379726Cllu, // mrj_Cyrl_RU
- 0xD22C42444D726F6Fllu, // mru_Mroo_BD
+ 0xBA2C42444D726F6Fllu, // mro_Mroo_BD
0x6D734D594C61746Ellu, // ms_Latn_MY
0x6D744D544C61746Ellu, // mt_Latn_MT
0xC66C494E44657661llu, // mtr_Deva_IN
@@ -1308,6 +1924,7 @@
0x6F6D45544C61746Ellu, // om_Latn_ET
0x6F72494E4F727961llu, // or_Orya_IN
0x6F7347454379726Cllu, // os_Cyrl_GE
+ 0x824E55534F736765llu, // osa_Osge_US
0xAA6E4D4E4F726B68llu, // otk_Orkh_MN
0x7061504B41726162llu, // pa_Arab_PK
0x7061494E47757275llu, // pa_Guru_IN
@@ -1479,6 +2096,7 @@
0xB2934D574C61746Ellu, // tum_Latn_MW
0xAEB354564C61746Ellu, // tvl_Latn_TV
0xC2D34E454C61746Ellu, // twq_Latn_NE
+ 0x9AF3434E54616E67llu, // txg_Tang_CN
0x747950464C61746Ellu, // ty_Latn_PF
0xD71352554379726Cllu, // tyv_Cyrl_RU
0xB3334D414C61746Ellu, // tzm_Latn_MA
@@ -1540,14 +2158,18 @@
0x796F4E474C61746Ellu, // yo_Latn_NG
0xAE3842524C61746Ellu, // yrl_Latn_BR
0x82984D584C61746Ellu, // yua_Latn_MX
+ 0x9298434E48616E73llu, // yue_Hans_CN
+ 0x9298484B48616E74llu, // yue_Hant_HK
0x7A61434E4C61746Ellu, // za_Latn_CN
0x981953444C61746Ellu, // zag_Latn_SD
0xA4794B4D41726162llu, // zdj_Arab_KM
0x80994E4C4C61746Ellu, // zea_Latn_NL
0x9CD94D4154666E67llu, // zgh_Tfng_MA
0x7A685457426F706Fllu, // zh_Bopo_TW
+ 0x7A68545748616E62llu, // zh_Hanb_TW
0x7A68434E48616E73llu, // zh_Hans_CN
0x7A68545748616E74llu, // zh_Hant_TW
+ 0xB17954474C61746Ellu, // zlm_Latn_TG
0xA1994D594C61746Ellu, // zmi_Latn_MY
0x7A755A414C61746Ellu, // zu_Latn_ZA
0x833954524C61746Ellu, // zza_Latn_TR
@@ -1662,6 +2284,7 @@
{0x656E5A57u, 0x656E8400u}, // en-ZW -> en-001
{0x65734152u, 0x6573A424u}, // es-AR -> es-419
{0x6573424Fu, 0x6573A424u}, // es-BO -> es-419
+ {0x65734252u, 0x6573A424u}, // es-BR -> es-419
{0x6573434Cu, 0x6573A424u}, // es-CL -> es-419
{0x6573434Fu, 0x6573A424u}, // es-CO -> es-419
{0x65734352u, 0x6573A424u}, // es-CR -> es-419
@@ -1681,8 +2304,11 @@
{0x65735559u, 0x6573A424u}, // es-UY -> es-419
{0x65735645u, 0x6573A424u}, // es-VE -> es-419
{0x7074414Fu, 0x70745054u}, // pt-AO -> pt-PT
+ {0x70744348u, 0x70745054u}, // pt-CH -> pt-PT
{0x70744356u, 0x70745054u}, // pt-CV -> pt-PT
+ {0x70744751u, 0x70745054u}, // pt-GQ -> pt-PT
{0x70744757u, 0x70745054u}, // pt-GW -> pt-PT
+ {0x70744C55u, 0x70745054u}, // pt-LU -> pt-PT
{0x70744D4Fu, 0x70745054u}, // pt-MO -> pt-PT
{0x70744D5Au, 0x70745054u}, // pt-MZ -> pt-PT
{0x70745354u, 0x70745054u}, // pt-ST -> pt-PT
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 7fbfffe..a30c849 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -140,7 +140,7 @@
patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
}
-inline void Res_value::copyFrom_dtoh(const Res_value& src)
+void Res_value::copyFrom_dtoh(const Res_value& src)
{
size = dtohs(src.size);
res0 = src.res0;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
new file mode 100644
index 0000000..a3d67f0
--- /dev/null
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#ifndef APKASSETS_H_
+#define APKASSETS_H_
+
+#include <memory>
+#include <string>
+
+#include "android-base/macros.h"
+#include "ziparchive/zip_archive.h"
+
+#include "androidfw/Asset.h"
+#include "androidfw/LoadedArsc.h"
+
+namespace android {
+
+// Holds an APK.
+class ApkAssets {
+ public:
+ static std::unique_ptr<ApkAssets> Load(const std::string& path);
+
+ std::unique_ptr<Asset> Open(const std::string& path,
+ Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
+
+ inline const std::string& GetPath() const { return path_; }
+
+ inline const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ApkAssets);
+
+ ApkAssets() = default;
+
+ struct ZipArchivePtrCloser {
+ void operator()(::ZipArchiveHandle handle) { ::CloseArchive(handle); }
+ };
+
+ using ZipArchivePtr =
+ std::unique_ptr<typename std::remove_pointer<::ZipArchiveHandle>::type, ZipArchivePtrCloser>;
+ ZipArchivePtr zip_handle_;
+ std::string path_;
+ std::unique_ptr<Asset> resources_asset_;
+ std::unique_ptr<LoadedArsc> loaded_arsc_;
+};
+
+} // namespace android
+
+#endif /* APKASSETS_H_ */
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
new file mode 100644
index 0000000..66d5034
--- /dev/null
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -0,0 +1,298 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROIDFW_ASSETMANAGER2_H_
+#define ANDROIDFW_ASSETMANAGER2_H_
+
+#include "android-base/macros.h"
+
+#include <limits>
+#include <unordered_map>
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/Asset.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/Util.h"
+
+namespace android {
+
+class Theme;
+
+using ApkAssetsCookie = int32_t;
+
+enum : ApkAssetsCookie {
+ kInvalidCookie = -1,
+};
+
+// Holds a bag that has been merged with its parent, if one exists.
+struct ResolvedBag {
+ // A single key-value entry in a bag.
+ struct Entry {
+ // The key, as described in ResTable_map::name.
+ uint32_t key;
+
+ Res_value value;
+
+ // Which ApkAssets this entry came from.
+ ApkAssetsCookie cookie;
+
+ ResStringPool* key_pool;
+ ResStringPool* type_pool;
+ };
+
+ // Denotes the configuration axis that this bag varies with.
+ // If a configuration changes with respect to one of these axis,
+ // the bag should be reloaded.
+ uint32_t type_spec_flags;
+
+ // The number of entries in this bag. Access them by indexing into `entries`.
+ uint32_t entry_count;
+
+ // The array of entries for this bag. An empty array is a neat trick to force alignment
+ // of the Entry structs that follow this structure and avoids a bunch of casts.
+ Entry entries[0];
+};
+
+// AssetManager2 is the main entry point for accessing assets and resources.
+// AssetManager2 provides caching of resources retrieved via the underlying
+// ApkAssets.
+class AssetManager2 : public ::AAssetManager {
+ public:
+ struct ResourceName {
+ const char* package = nullptr;
+ size_t package_len = 0u;
+
+ const char* type = nullptr;
+ const char16_t* type16 = nullptr;
+ size_t type_len = 0u;
+
+ const char* entry = nullptr;
+ const char16_t* entry16 = nullptr;
+ size_t entry_len = 0u;
+ };
+
+ AssetManager2();
+
+ // Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
+ // are not owned by the AssetManager, and must have a longer lifetime.
+ //
+ // Only pass invalidate_caches=false when it is known that the structure
+ // change in ApkAssets is due to a safe addition of resources with completely
+ // new resource IDs.
+ bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
+
+ const std::vector<const ApkAssets*> GetApkAssets() const;
+
+ // Returns the string pool for the given asset cookie.
+ // Use the string pool returned here with a valid Res_value object of
+ // type Res_value::TYPE_STRING.
+ const ResStringPool* GetStringPoolForCookie(ApkAssetsCookie cookie) const;
+
+ // Sets/resets the configuration for this AssetManager. This will cause all
+ // caches that are related to the configuration change to be invalidated.
+ void SetConfiguration(const ResTable_config& configuration);
+
+ const ResTable_config& GetConfiguration() const;
+
+ // Searches the set of APKs loaded by this AssetManager and opens the first one found located
+ // in the assets/ directory.
+ // `mode` controls how the file is opened.
+ //
+ // NOTE: The loaded APKs are searched in reverse order.
+ std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode);
+
+ // Opens a file within the assets/ directory of the APK specified by `cookie`.
+ // `mode` controls how the file is opened.
+ std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie,
+ Asset::AccessMode mode);
+
+ // Searches the set of APKs loaded by this AssetManager and opens the first one found.
+ // `mode` controls how the file is opened.
+ // `out_cookie` is populated with the cookie of the APK this file was found in.
+ //
+ // NOTE: The loaded APKs are searched in reverse order.
+ std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode,
+ ApkAssetsCookie* out_cookie = nullptr);
+
+ // Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened.
+ // This is typically used to open a specific AndroidManifest.xml, or a binary XML file
+ // referenced by a resource lookup with GetResource().
+ std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
+ Asset::AccessMode mode);
+
+ // Populates the `out_name` parameter with resource name information.
+ // Utf8 strings are preferred, and only if they are unavailable are
+ // the Utf16 variants populated.
+ // Returns false if the resource was not found or the name was missing/corrupt.
+ bool GetResourceName(uint32_t resid, ResourceName* out_name);
+
+ // Populates `out_flags` with the bitmask of configuration axis that this resource varies with.
+ // See ResTable_config for the list of configuration axis.
+ // Returns false if the resource was not found.
+ bool GetResourceFlags(uint32_t resid, uint32_t* out_flags);
+
+ // Retrieves the best matching resource with ID `resid`. The resource value is filled into
+ // `out_value` and the configuration for the selected value is populated in `out_selected_config`.
+ // `out_flags` holds the same flags as retrieved with GetResourceFlags().
+ // If `density_override` is non-zero, the configuration to match against is overridden with that
+ // density.
+ //
+ // Returns a valid cookie if the resource was found. If the resource was not found, or if the
+ // resource was a map/bag type, then kInvalidCookie is returned. If `may_be_bag` is false,
+ // this function logs if the resource was a map/bag type before returning kInvalidCookie.
+ ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override,
+ Res_value* out_value, ResTable_config* out_selected_config,
+ uint32_t* out_flags);
+
+ // Retrieves the best matching bag/map resource with ID `resid`.
+ // This method will resolve all parent references for this bag and merge keys with the child.
+ // To iterate over the keys, use the following idiom:
+ //
+ // const AssetManager2::ResolvedBag* bag = asset_manager->GetBag(id);
+ // if (bag != nullptr) {
+ // for (auto iter = begin(bag); iter != end(bag); ++iter) {
+ // ...
+ // }
+ // }
+ const ResolvedBag* GetBag(uint32_t resid);
+
+ // Creates a new Theme from this AssetManager.
+ std::unique_ptr<Theme> NewTheme();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AssetManager2);
+
+ // Finds the best entry for `resid` amongst all the ApkAssets. The entry can be a simple
+ // Res_value, or a complex map/bag type.
+ //
+ // `density_override` overrides the density of the current configuration when doing a search.
+ //
+ // When `stop_at_first_match` is true, the first match found is selected and the search
+ // terminates. This is useful for methods that just look up the name of a resource and don't
+ // care about the value. In this case, the value of `out_flags` is incomplete and should not
+ // be used.
+ //
+ // `out_flags` stores the resulting bitmask of configuration axis with which the resource
+ // value varies.
+ ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match,
+ LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config,
+ uint32_t* out_flags);
+
+ // Purge all resources that are cached and vary by the configuration axis denoted by the
+ // bitmask `diff`.
+ void InvalidateCaches(uint32_t diff);
+
+ // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
+ // have a longer lifetime.
+ std::vector<const ApkAssets*> apk_assets_;
+
+ // The current configuration set for this AssetManager. When this changes, cached resources
+ // may need to be purged.
+ ResTable_config configuration_;
+
+ // Cached set of bags. These are cached because they can inherit keys from parent bags,
+ // which involves some calculation.
+ std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
+};
+
+class Theme {
+ friend class AssetManager2;
+
+ public:
+ // Applies the style identified by `resid` to this theme. This can be called
+ // multiple times with different styles. By default, any theme attributes that
+ // are already defined before this call are not overridden. If `force` is set
+ // to true, this behavior is changed and all theme attributes from the style at
+ // `resid` are applied.
+ // Returns false if the style failed to apply.
+ bool ApplyStyle(uint32_t resid, bool force = false);
+
+ // Sets this Theme to be a copy of `o` if `o` has the same AssetManager as this Theme.
+ // Returns false if the AssetManagers of the Themes were not compatible.
+ bool SetTo(const Theme& o);
+
+ void Clear();
+
+ inline const AssetManager2* GetAssetManager() const { return asset_manager_; }
+
+ // Returns a bit mask of configuration changes that will impact this
+ // theme (and thus require completely reloading it).
+ inline uint32_t GetChangingConfigurations() const { return type_spec_flags_; }
+
+ // Retrieve a value in the theme. If the theme defines this value,
+ // returns an asset cookie indicating which ApkAssets it came from
+ // and populates `out_value` with the value. If `out_flags` is non-null,
+ // populates it with a bitmask of the configuration axis the resource
+ // varies with.
+ //
+ // If the attribute is not found, returns kInvalidCookie.
+ //
+ // NOTE: This function does not do reference traversal. If you want
+ // to follow references to other resources to get the "real" value to
+ // use, you need to call ResolveReference() after this function.
+ ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value,
+ uint32_t* out_flags = nullptr) const;
+
+ // This is like AssetManager2::ResolveReference(), but also takes
+ // care of resolving attribute references to the theme.
+ ApkAssetsCookie ResolveAttributeReference(Res_value* in_out_value, ApkAssetsCookie src_cookie,
+ uint32_t* out_last_ref = nullptr,
+ uint32_t* in_out_type_spec_flags = nullptr,
+ ResTable_config* out_selected_config = nullptr) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Theme);
+
+ // Called by AssetManager2.
+ explicit inline Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {}
+
+ struct Entry {
+ ApkAssetsCookie cookie;
+ uint32_t type_spec_flags;
+ Res_value value;
+ };
+
+ struct Type {
+ // Use uint32_t for fewer cycles when loading from memory.
+ uint32_t entry_count;
+ uint32_t entry_capacity;
+ Entry entries[0];
+ };
+
+ static constexpr const size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
+ static constexpr const size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
+
+ struct Package {
+ // Each element of Type will be a dynamically sized object
+ // allocated to have the entries stored contiguously with the Type.
+ util::unique_cptr<Type> types[kTypeCount];
+ };
+
+ AssetManager2* asset_manager_;
+ uint32_t type_spec_flags_ = 0u;
+ std::unique_ptr<Package> packages_[kPackageCount];
+};
+
+inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { return bag->entries; }
+
+inline const ResolvedBag::Entry* end(const ResolvedBag* bag) {
+ return bag->entries + bag->entry_count;
+}
+
+} // namespace android
+
+#endif /* ANDROIDFW_ASSETMANAGER2_H_ */
diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h
index 87c6b12..d84a207 100644
--- a/libs/androidfw/include/androidfw/ByteBucketArray.h
+++ b/libs/androidfw/include/androidfw/ByteBucketArray.h
@@ -17,9 +17,10 @@
#ifndef __BYTE_BUCKET_ARRAY_H
#define __BYTE_BUCKET_ARRAY_H
-#include <utils/Log.h>
-#include <stdint.h>
-#include <string.h>
+#include <cstdint>
+#include <cstring>
+
+#include "android-base/logging.h"
namespace android {
@@ -27,71 +28,65 @@
* Stores a sparsely populated array. Has a fixed size of 256
* (number of entries that a byte can represent).
*/
-template<typename T>
+template <typename T>
class ByteBucketArray {
-public:
- ByteBucketArray() : mDefault() {
- memset(mBuckets, 0, sizeof(mBuckets));
+ public:
+ ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); }
+
+ ~ByteBucketArray() {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ if (buckets_[i] != NULL) {
+ delete[] buckets_[i];
+ }
+ }
+ memset(buckets_, 0, sizeof(buckets_));
+ }
+
+ inline size_t size() const { return kNumBuckets * kBucketSize; }
+
+ inline const T& get(size_t index) const { return (*this)[index]; }
+
+ const T& operator[](size_t index) const {
+ if (index >= size()) {
+ return default_;
}
- ~ByteBucketArray() {
- for (size_t i = 0; i < NUM_BUCKETS; i++) {
- if (mBuckets[i] != NULL) {
- delete [] mBuckets[i];
- }
- }
- memset(mBuckets, 0, sizeof(mBuckets));
+ uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
+ T* bucket = buckets_[bucket_index];
+ if (bucket == NULL) {
+ return default_;
+ }
+ return bucket[0x0f & static_cast<uint8_t>(index)];
+ }
+
+ T& editItemAt(size_t index) {
+ CHECK(index < size()) << "ByteBucketArray.getOrCreate(index=" << index
+ << ") with size=" << size();
+
+ uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
+ T* bucket = buckets_[bucket_index];
+ if (bucket == NULL) {
+ bucket = buckets_[bucket_index] = new T[kBucketSize]();
+ }
+ return bucket[0x0f & static_cast<uint8_t>(index)];
+ }
+
+ bool set(size_t index, const T& value) {
+ if (index >= size()) {
+ return false;
}
- inline size_t size() const {
- return NUM_BUCKETS * BUCKET_SIZE;
- }
+ editItemAt(index) = value;
+ return true;
+ }
- inline const T& get(size_t index) const {
- return (*this)[index];
- }
+ private:
+ enum { kNumBuckets = 16, kBucketSize = 16 };
- const T& operator[](size_t index) const {
- if (index >= size()) {
- return mDefault;
- }
-
- uint8_t bucketIndex = static_cast<uint8_t>(index) >> 4;
- T* bucket = mBuckets[bucketIndex];
- if (bucket == NULL) {
- return mDefault;
- }
- return bucket[0x0f & static_cast<uint8_t>(index)];
- }
-
- T& editItemAt(size_t index) {
- ALOG_ASSERT(index < size(), "ByteBucketArray.getOrCreate(index=%u) with size=%u",
- (uint32_t) index, (uint32_t) size());
-
- uint8_t bucketIndex = static_cast<uint8_t>(index) >> 4;
- T* bucket = mBuckets[bucketIndex];
- if (bucket == NULL) {
- bucket = mBuckets[bucketIndex] = new T[BUCKET_SIZE]();
- }
- return bucket[0x0f & static_cast<uint8_t>(index)];
- }
-
- bool set(size_t index, const T& value) {
- if (index >= size()) {
- return false;
- }
-
- editItemAt(index) = value;
- return true;
- }
-
-private:
- enum { NUM_BUCKETS = 16, BUCKET_SIZE = 16 };
-
- T* mBuckets[NUM_BUCKETS];
- T mDefault;
+ T* buckets_[kNumBuckets];
+ T default_;
};
-} // namespace android
+} // namespace android
-#endif // __BYTE_BUCKET_ARRAY_H
+#endif // __BYTE_BUCKET_ARRAY_H
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
new file mode 100644
index 0000000..e2e56c8
--- /dev/null
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#ifndef LOADEDARSC_H_
+#define LOADEDARSC_H_
+
+#include <memory>
+#include <vector>
+
+#include "android-base/macros.h"
+
+#include "androidfw/ResourceTypes.h"
+
+namespace android {
+
+class Chunk;
+class LoadedPackage;
+
+// Read-only view into a resource table. This class validates all data
+// when loading, including offsets and lengths.
+class LoadedArsc {
+ public:
+ // Load the resource table from memory. The data's lifetime must out-live the
+ // object returned from this method.
+ static std::unique_ptr<LoadedArsc> Load(const void* data, size_t len);
+
+ ~LoadedArsc();
+
+ // Returns the string pool where all string resource values
+ // (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
+ inline const ResStringPool* GetStringPool() const { return &global_string_pool_; }
+
+ struct Entry {
+ // A pointer to the resource table entry for this resource.
+ // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
+ // a ResTable_map_entry and processed as a bag/map.
+ const ResTable_entry* entry = nullptr;
+
+ // The string pool reference to the type's name. This uses a different string pool than
+ // the global string pool, but this is hidden from the caller.
+ StringPoolRef type_string_ref;
+
+ // The string pool reference to the entry's name. This uses a different string pool than
+ // the global string pool, but this is hidden from the caller.
+ StringPoolRef entry_string_ref;
+ };
+
+ // Finds the resource with ID `resid` with the best value for configuration `config`.
+ // The parameter `out_entry` will be filled with the resulting resource entry.
+ // The resource entry can be a simple entry (ResTable_entry) or a complex bag
+ // (ResTable_entry_map).
+ bool FindEntry(uint32_t resid, const ResTable_config& config, Entry* out_entry,
+ ResTable_config* selected_config, uint32_t* out_flags) const;
+
+ // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
+ const std::string* GetPackageNameForId(uint32_t resid) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
+
+ LoadedArsc() = default;
+ bool LoadTable(const Chunk& chunk);
+
+ ResStringPool global_string_pool_;
+ std::vector<std::unique_ptr<LoadedPackage>> packages_;
+};
+
+} // namespace android
+
+#endif /* LOADEDARSC_H_ */
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 33b91b9..c118b57 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -265,7 +265,7 @@
uint8_t res0;
// Type of the data value.
- enum {
+ enum : uint8_t {
// The 'data' is either 0 or 1, specifying this resource is either
// undefined or empty, respectively.
TYPE_NULL = 0x00,
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
new file mode 100644
index 0000000..5266d09
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+#ifndef UTIL_H_
+#define UTIL_H_
+
+#include <cstdlib>
+#include <memory>
+
+#include "android-base/macros.h"
+
+namespace android {
+namespace util {
+
+/**
+ * Makes a std::unique_ptr<> with the template parameter inferred by the
+ * compiler.
+ * This will be present in C++14 and can be removed then.
+ */
+template <typename T, class... Args>
+std::unique_ptr<T> make_unique(Args&&... args) {
+ return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
+}
+
+// Based on std::unique_ptr, but uses free() to release malloc'ed memory
+// without incurring the size increase of holding on to a custom deleter.
+template <typename T>
+class unique_cptr {
+ public:
+ using pointer = typename std::add_pointer<T>::type;
+
+ constexpr unique_cptr() : ptr_(nullptr) {}
+ constexpr unique_cptr(std::nullptr_t) : ptr_(nullptr) {}
+ explicit unique_cptr(pointer ptr) : ptr_(ptr) {}
+ unique_cptr(unique_cptr&& o) : ptr_(o.ptr_) { o.ptr_ = nullptr; }
+
+ ~unique_cptr() { std::free(reinterpret_cast<void*>(ptr_)); }
+
+ inline unique_cptr& operator=(unique_cptr&& o) {
+ if (&o == this) {
+ return *this;
+ }
+
+ std::free(reinterpret_cast<void*>(ptr_));
+ ptr_ = o.ptr_;
+ o.ptr_ = nullptr;
+ return *this;
+ }
+
+ inline unique_cptr& operator=(std::nullptr_t) {
+ std::free(reinterpret_cast<void*>(ptr_));
+ ptr_ = nullptr;
+ return *this;
+ }
+
+ pointer release() {
+ pointer result = ptr_;
+ ptr_ = nullptr;
+ return result;
+ }
+
+ inline pointer get() const { return ptr_; }
+
+ void reset(pointer ptr = pointer()) {
+ if (ptr == ptr_) {
+ return;
+ }
+
+ pointer old_ptr = ptr_;
+ ptr_ = ptr;
+ std::free(reinterpret_cast<void*>(old_ptr));
+ }
+
+ inline void swap(unique_cptr& o) { std::swap(ptr_, o.ptr_); }
+
+ inline explicit operator bool() const { return ptr_ != nullptr; }
+
+ inline typename std::add_lvalue_reference<T>::type operator*() const { return *ptr_; }
+
+ inline pointer operator->() const { return ptr_; }
+
+ inline bool operator==(const unique_cptr& o) const { return ptr_ == o.ptr_; }
+
+ inline bool operator==(std::nullptr_t) const { return ptr_ == nullptr; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(unique_cptr);
+
+ pointer ptr_;
+};
+
+inline uint8_t get_package_id(uint32_t resid) {
+ return static_cast<uint8_t>((resid >> 24) & 0x000000ffu);
+}
+
+// The type ID is 1-based, so if the returned value is 0 it is invalid.
+inline uint8_t get_type_id(uint32_t resid) {
+ return static_cast<uint8_t>((resid >> 16) & 0x000000ffu);
+}
+
+inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); }
+
+inline bool is_internal_id(uint32_t resid) {
+ return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0;
+}
+
+inline bool is_valid_resid(uint32_t resid) {
+ return (resid & 0x00ff0000u) != 0 && (resid & 0xff000000u) != 0;
+}
+
+} // namespace util
+} // namespace android
+
+#endif /* UTIL_H_ */
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index d91a133..6754cd8 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -21,22 +21,31 @@
LOCAL_PATH:= $(call my-dir)
testFiles := \
+ ApkAssets_test.cpp \
AppAsLib_test.cpp \
Asset_test.cpp \
+ AssetManager2_test.cpp \
AttributeFinder_test.cpp \
AttributeResolution_test.cpp \
ByteBucketArray_test.cpp \
Config_test.cpp \
ConfigLocale_test.cpp \
Idmap_test.cpp \
- Main.cpp \
+ LoadedArsc_test.cpp \
ResTable_test.cpp \
Split_test.cpp \
TestHelpers.cpp \
+ TestMain.cpp \
Theme_test.cpp \
TypeWrappers_test.cpp \
ZipUtils_test.cpp
+benchmarkFiles := \
+ AssetManager2_bench.cpp \
+ BenchMain.cpp \
+ TestHelpers.cpp \
+ Theme_bench.cpp
+
androidfw_test_cflags := \
-Wall \
-Werror \
@@ -89,5 +98,25 @@
LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data
include $(BUILD_NATIVE_TEST)
+
+# ==========================================================
+# Build the device benchmarks: libandroidfw_benchmarks
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libandroidfw_benchmarks
+LOCAL_CFLAGS := $(androidfw_test_cflags)
+LOCAL_SRC_FILES := $(benchmarkFiles)
+LOCAL_STATIC_LIBRARIES := \
+ libgoogle-benchmark
+LOCAL_SHARED_LIBRARIES := \
+ libandroidfw \
+ libbase \
+ libcutils \
+ libutils \
+ libziparchive
+LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data
+
+include $(BUILD_NATIVE_TEST)
endif # Not SDK_ONLY
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
new file mode 100644
index 0000000..3a1fc8f
--- /dev/null
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -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.
+ */
+
+#include "androidfw/ApkAssets.h"
+
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+
+using com::android::basic::R;
+
+namespace android {
+
+TEST(ApkAssetsTest, LoadApk) {
+ std::unique_ptr<ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ ASSERT_NE(nullptr, loaded_apk);
+
+ std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
+ ASSERT_NE(nullptr, asset);
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
new file mode 100644
index 0000000..9ff9478
--- /dev/null
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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 "benchmark/benchmark.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+#include "data/styles/R.h"
+
+namespace basic = com::android::basic;
+namespace app = com::android::app;
+
+namespace android {
+
+constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk";
+
+static void BM_AssetManagerLoadAssets(benchmark::State& state) {
+ std::string path = GetTestDataPath() + "/basic/basic.apk";
+ while (state.KeepRunning()) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(path);
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+ }
+}
+BENCHMARK(BM_AssetManagerLoadAssets);
+
+static void BM_AssetManagerLoadAssetsOld(benchmark::State& state) {
+ String8 path((GetTestDataPath() + "/basic/basic.apk").data());
+ while (state.KeepRunning()) {
+ AssetManager assets;
+ assets.addAssetPath(path, nullptr /* cookie */, false /* appAsLib */,
+ false /* isSystemAsset */);
+
+ // Force creation.
+ assets.getResources(true);
+ }
+}
+BENCHMARK(BM_AssetManagerLoadAssetsOld);
+
+static void BM_AssetManagerLoadFrameworkAssets(benchmark::State& state) {
+ std::string path = kFrameworkPath;
+ while (state.KeepRunning()) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(path);
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+ }
+}
+BENCHMARK(BM_AssetManagerLoadFrameworkAssets);
+
+static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) {
+ String8 path(kFrameworkPath);
+ while (state.KeepRunning()) {
+ AssetManager assets;
+ assets.addAssetPath(path, nullptr /* cookie */, false /* appAsLib */,
+ false /* isSystemAsset */);
+
+ // Force creation.
+ assets.getResources(true);
+ }
+}
+BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld);
+
+static void BM_AssetManagerGetResource(benchmark::State& state) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ assets.GetResource(basic::R::integer::number1, false /* may_be_bag */,
+ 0u /* density_override */, &value, &selected_config, &flags);
+ }
+}
+BENCHMARK(BM_AssetManagerGetResource);
+
+static void BM_AssetManagerGetResourceOld(benchmark::State& state) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8((GetTestDataPath() + "/basic/basic.apk").data()),
+ nullptr /* cookie */, false /* appAsLib */,
+ false /* isSystemAssets */)) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ const ResTable& table = assets.getResources(true);
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ table.getResource(basic::R::integer::number1, &value, false /* may_be_bag */,
+ 0u /* density_override */, &flags, &selected_config);
+ }
+}
+BENCHMARK(BM_AssetManagerGetResourceOld);
+
+constexpr static const uint32_t kStringOkId = 0x0104000au;
+
+static void BM_AssetManagerGetResourceFrameworkLocale(benchmark::State& state) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ memcpy(config.language, "fr", 2);
+ assets.SetConfiguration(config);
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ assets.GetResource(kStringOkId, false /* may_be_bag */, 0u /* density_override */, &value,
+ &selected_config, &flags);
+ }
+}
+BENCHMARK(BM_AssetManagerGetResourceFrameworkLocale);
+
+static void BM_AssetManagerGetResourceFrameworkLocaleOld(benchmark::State& state) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8((GetTestDataPath() + "/basic/basic.apk").data()),
+ nullptr /* cookie */, false /* appAsLib */,
+ false /* isSystemAssets */)) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ memcpy(config.language, "fr", 2);
+ assets.setConfiguration(config, nullptr);
+
+ const ResTable& table = assets.getResources(true);
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ table.getResource(kStringOkId, &value, false /* may_be_bag */, 0u /* density_override */,
+ &flags, &selected_config);
+ }
+}
+BENCHMARK(BM_AssetManagerGetResourceFrameworkLocaleOld);
+
+static void BM_AssetManagerGetBag(benchmark::State& state) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ while (state.KeepRunning()) {
+ const ResolvedBag* bag = assets.GetBag(app::R::style::StyleTwo);
+ const auto bag_end = end(bag);
+ for (auto iter = begin(bag); iter != bag_end; ++iter) {
+ uint32_t key = iter->key;
+ Res_value value = iter->value;
+ benchmark::DoNotOptimize(key);
+ benchmark::DoNotOptimize(value);
+ }
+ }
+}
+BENCHMARK(BM_AssetManagerGetBag);
+
+static void BM_AssetManagerGetBagOld(benchmark::State& state) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8((GetTestDataPath() + "/styles/styles.apk").data()),
+ nullptr /* cookie */, false /* appAsLib */,
+ false /* isSystemAssets */)) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ const ResTable& table = assets.getResources(true);
+
+ while (state.KeepRunning()) {
+ const ResTable::bag_entry* bag_begin;
+ const ssize_t N = table.lockBag(app::R::style::StyleTwo, &bag_begin);
+ const ResTable::bag_entry* const bag_end = bag_begin + N;
+ for (auto iter = bag_begin; iter != bag_end; ++iter) {
+ uint32_t key = iter->map.name.ident;
+ Res_value value = iter->map.value;
+ benchmark::DoNotOptimize(key);
+ benchmark::DoNotOptimize(value);
+ }
+ table.unlockBag(bag_begin);
+ }
+}
+BENCHMARK(BM_AssetManagerGetBagOld);
+
+} // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
new file mode 100644
index 0000000..39c5381
--- /dev/null
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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 "androidfw/AssetManager2.h"
+#include "androidfw/AssetManager.h"
+
+#include "android-base/logging.h"
+
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+#include "data/styles/R.h"
+
+namespace basic = com::android::basic;
+namespace app = com::android::app;
+
+namespace android {
+
+class AssetManager2Test : public ::testing::Test {
+ public:
+ void SetUp() override {
+ basic_assets_ = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ ASSERT_NE(nullptr, basic_assets_);
+
+ basic_de_fr_assets_ = ApkAssets::Load(GetTestDataPath() + "/basic/basic_de_fr.apk");
+ ASSERT_NE(nullptr, basic_de_fr_assets_);
+
+ style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ ASSERT_NE(nullptr, style_assets_);
+ }
+
+ protected:
+ std::unique_ptr<ApkAssets> basic_assets_;
+ std::unique_ptr<ApkAssets> basic_de_fr_assets_;
+ std::unique_ptr<ApkAssets> style_assets_;
+};
+
+TEST_F(AssetManager2Test, FindsResourcesFromSingleApkAssets) {
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+ desired_config.language[0] = 'd';
+ desired_config.language[1] = 'e';
+
+ AssetManager2 assetmanager;
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/,
+ 0 /*density_override*/, &value, &selected_config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+
+ // Came from our ApkAssets.
+ EXPECT_EQ(0, cookie);
+
+ // It is the default config.
+ EXPECT_EQ(0, selected_config.language[0]);
+ EXPECT_EQ(0, selected_config.language[1]);
+
+ // It is a string.
+ EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
+}
+
+TEST_F(AssetManager2Test, FindsResourcesFromMultipleApkAssets) {
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+ desired_config.language[0] = 'd';
+ desired_config.language[1] = 'e';
+
+ AssetManager2 assetmanager;
+ assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetApkAssets({basic_assets_.get(), basic_de_fr_assets_.get()});
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/,
+ 0 /*density_override*/, &value, &selected_config, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+
+ // Came from our de_fr ApkAssets.
+ EXPECT_EQ(1, cookie);
+
+ // The configuration is german.
+ EXPECT_EQ('d', selected_config.language[0]);
+ EXPECT_EQ('e', selected_config.language[1]);
+
+ // It is a string.
+ EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
+}
+
+TEST_F(AssetManager2Test, FindsBagResourcesFromSingleApkAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ const ResolvedBag* bag = assetmanager.GetBag(basic::R::array::integerArray1);
+ ASSERT_NE(nullptr, bag);
+ ASSERT_EQ(3u, bag->entry_count);
+
+ EXPECT_EQ(static_cast<uint8_t>(Res_value::TYPE_INT_DEC), bag->entries[0].value.dataType);
+ EXPECT_EQ(1u, bag->entries[0].value.data);
+ EXPECT_EQ(0, bag->entries[0].cookie);
+
+ EXPECT_EQ(static_cast<uint8_t>(Res_value::TYPE_INT_DEC), bag->entries[1].value.dataType);
+ EXPECT_EQ(2u, bag->entries[1].value.data);
+ EXPECT_EQ(0, bag->entries[1].cookie);
+
+ EXPECT_EQ(static_cast<uint8_t>(Res_value::TYPE_INT_DEC), bag->entries[2].value.dataType);
+ EXPECT_EQ(3u, bag->entries[2].value.data);
+ EXPECT_EQ(0, bag->entries[2].cookie);
+}
+
+TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ const ResolvedBag* bag_one = assetmanager.GetBag(app::R::style::StyleOne);
+ ASSERT_NE(nullptr, bag_one);
+ ASSERT_EQ(2u, bag_one->entry_count);
+
+ EXPECT_EQ(app::R::attr::attr_one, bag_one->entries[0].key);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_one->entries[0].value.dataType);
+ EXPECT_EQ(1u, bag_one->entries[0].value.data);
+ EXPECT_EQ(0, bag_one->entries[0].cookie);
+
+ EXPECT_EQ(app::R::attr::attr_two, bag_one->entries[1].key);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_one->entries[1].value.dataType);
+ EXPECT_EQ(2u, bag_one->entries[1].value.data);
+ EXPECT_EQ(0, bag_one->entries[1].cookie);
+
+ const ResolvedBag* bag_two = assetmanager.GetBag(app::R::style::StyleTwo);
+ ASSERT_NE(nullptr, bag_two);
+ ASSERT_EQ(5u, bag_two->entry_count);
+
+ // attr_one is inherited from StyleOne.
+ EXPECT_EQ(app::R::attr::attr_one, bag_two->entries[0].key);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[0].value.dataType);
+ EXPECT_EQ(1u, bag_two->entries[0].value.data);
+ EXPECT_EQ(0, bag_two->entries[0].cookie);
+
+ // attr_two should be overridden from StyleOne by StyleTwo.
+ EXPECT_EQ(app::R::attr::attr_two, bag_two->entries[1].key);
+ EXPECT_EQ(Res_value::TYPE_STRING, bag_two->entries[1].value.dataType);
+ EXPECT_EQ(0, bag_two->entries[1].cookie);
+ EXPECT_EQ(std::string("string"), GetStringFromPool(assetmanager.GetStringPoolForCookie(0),
+ bag_two->entries[1].value.data));
+
+ // The rest are new attributes.
+
+ EXPECT_EQ(app::R::attr::attr_three, bag_two->entries[2].key);
+ EXPECT_EQ(Res_value::TYPE_ATTRIBUTE, bag_two->entries[2].value.dataType);
+ EXPECT_EQ(app::R::attr::attr_indirect, bag_two->entries[2].value.data);
+ EXPECT_EQ(0, bag_two->entries[2].cookie);
+
+ EXPECT_EQ(app::R::attr::attr_five, bag_two->entries[3].key);
+ EXPECT_EQ(Res_value::TYPE_REFERENCE, bag_two->entries[3].value.dataType);
+ EXPECT_EQ(app::R::string::string_one, bag_two->entries[3].value.data);
+ EXPECT_EQ(0, bag_two->entries[3].cookie);
+
+ EXPECT_EQ(app::R::attr::attr_indirect, bag_two->entries[4].key);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[4].value.dataType);
+ EXPECT_EQ(3u, bag_two->entries[4].value.data);
+ EXPECT_EQ(0, bag_two->entries[4].cookie);
+}
+
+TEST_F(AssetManager2Test, FindsBagResourcesFromMultipleApkAssets) {}
+
+TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {}
+
+TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {}
+
+} // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
index 7550517..1ff2ed4 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -205,4 +205,5 @@
EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
}
-} // namespace android
+} // namespace android
+
diff --git a/libs/androidfw/tests/BenchMain.cpp b/libs/androidfw/tests/BenchMain.cpp
new file mode 100644
index 0000000..105c5f9
--- /dev/null
+++ b/libs/androidfw/tests/BenchMain.cpp
@@ -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.
+ */
+
+#include <iostream>
+
+#include "benchmark/benchmark.h"
+
+#include "TestHelpers.h"
+
+int main(int argc, char** argv) {
+ ::benchmark::Initialize(&argc, argv);
+ ::android::InitializeTest(&argc, argv);
+
+ std::cerr << "using --testdata=" << ::android::GetTestDataPath() << "\n";
+
+ size_t result = ::benchmark::RunSpecifiedBenchmarks();
+ return result == 0 ? 1 : 0;
+}
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
new file mode 100644
index 0000000..47b3894
--- /dev/null
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 "androidfw/LoadedArsc.h"
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+#include "data/styles/R.h"
+
+namespace app = com::android::app;
+namespace basic = com::android::basic;
+
+namespace android {
+
+TEST(LoadedArscTest, LoadSinglePackageArsc) {
+ base::ScopedLogSeverity _log(base::LogSeverity::DEBUG);
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", "resources.arsc",
+ &contents));
+
+ std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size());
+ ASSERT_NE(nullptr, loaded_arsc);
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.sdkVersion = 24;
+
+ LoadedArsc::Entry entry;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ASSERT_TRUE(
+ loaded_arsc->FindEntry(app::R::string::string_one, config, &entry, &selected_config, &flags));
+ ASSERT_NE(nullptr, entry.entry);
+}
+
+TEST(LoadedArscTest, FindDefaultEntry) {
+ base::ScopedLogSeverity _log(base::LogSeverity::DEBUG);
+ std::string contents;
+ ASSERT_TRUE(
+ ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
+
+ std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size());
+ ASSERT_NE(nullptr, loaded_arsc);
+
+ ResTable_config desired_config;
+ memset(&desired_config, 0, sizeof(desired_config));
+ desired_config.language[0] = 'd';
+ desired_config.language[1] = 'e';
+
+ LoadedArsc::Entry entry;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry,
+ &selected_config, &flags));
+ ASSERT_NE(nullptr, entry.entry);
+}
+
+// structs with size fields (like Res_value, ResTable_entry) should be
+// backwards and forwards compatible (aka checking the size field against
+// sizeof(Res_value) might not be backwards compatible.
+TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
+
+} // namespace android
diff --git a/libs/androidfw/tests/Main.cpp b/libs/androidfw/tests/Main.cpp
deleted file mode 100644
index 6a50691..0000000
--- a/libs/androidfw/tests/Main.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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 <libgen.h>
-
-#include <iostream>
-#include <memory>
-#include <string>
-
-#include "android-base/file.h"
-#include "android-base/strings.h"
-#include "gtest/gtest.h"
-
-#include "TestHelpers.h"
-
-// Extract the directory of the current executable path.
-static std::string GetExecutableDir() {
- const std::string path = android::base::GetExecutablePath();
- std::unique_ptr<char, decltype(&std::free)> mutable_path = {
- strdup(path.c_str()), std::free};
- std::string executable_dir = dirname(mutable_path.get());
- return executable_dir;
-}
-
-int main(int argc, char** argv) {
- ::testing::InitGoogleTest(&argc, argv);
-
- // Set the default test data path to be the executable path directory.
- android::SetTestDataPath(GetExecutableDir());
-
- const char* command = argv[0];
- ++argv;
- --argc;
-
- while (argc > 0) {
- const std::string arg = *argv;
- if (android::base::StartsWith(arg, "--testdata=")) {
- android::SetTestDataPath(arg.substr(strlen("--testdata=")));
- } else if (arg == "-h" || arg == "--help") {
- std::cerr
- << "\nAdditional options specific to this test:\n"
- " --testdata=[PATH]\n"
- " Specify the location of test data used within the tests.\n";
- return 1;
- } else {
- std::cerr << command << ": Unrecognized argument '" << *argv << "'.\n";
- return 1;
- }
-
- --argc;
- ++argv;
- }
-
- std::cerr << "using --testdata=" << android::GetTestDataPath() << "\n";
- return RUN_ALL_TESTS();
-}
diff --git a/libs/androidfw/tests/TestHelpers.cpp b/libs/androidfw/tests/TestHelpers.cpp
index 2c834b1..1e763a5 100644
--- a/libs/androidfw/tests/TestHelpers.cpp
+++ b/libs/androidfw/tests/TestHelpers.cpp
@@ -16,15 +16,51 @@
#include "TestHelpers.h"
+#include <libgen.h>
#include <unistd.h>
+#include <memory>
+#include <string>
+
+#include "android-base/file.h"
#include "android-base/logging.h"
+#include "android-base/strings.h"
#include "ziparchive/zip_archive.h"
namespace android {
static std::string sTestDataPath;
+// Extract the directory of the current executable path.
+static std::string GetExecutableDir() {
+ const std::string path = base::GetExecutablePath();
+ std::unique_ptr<char, decltype(&std::free)> mutable_path = {strdup(path.c_str()), std::free};
+ std::string executable_dir = dirname(mutable_path.get());
+ return executable_dir;
+}
+
+void InitializeTest(int* argc, char** argv) {
+ // Set the default test data path to be the executable path directory.
+ SetTestDataPath(GetExecutableDir());
+
+ for (int i = 1; i < *argc; i++) {
+ const std::string arg = argv[i];
+ if (base::StartsWith(arg, "--testdata=")) {
+ SetTestDataPath(arg.substr(strlen("--testdata=")));
+ for (int j = i; j != *argc; j++) {
+ argv[j] = argv[j + 1];
+ }
+ --(*argc);
+ --i;
+ } else if (arg == "-h" || arg == "--help") {
+ std::cerr << "\nAdditional options specific to this test:\n"
+ " --testdata=[PATH]\n"
+ " Specify the location of test data used within the tests.\n";
+ exit(1);
+ }
+ }
+}
+
void SetTestDataPath(const std::string& path) { sTestDataPath = path; }
const std::string& GetTestDataPath() {
@@ -90,4 +126,9 @@
return ::testing::AssertionSuccess() << actual_str.string();
}
+std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx) {
+ String8 str = pool->string8ObjectAt(idx);
+ return std::string(str.string(), str.length());
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index d9cee22..a11ea84 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -35,6 +35,8 @@
namespace android {
+void InitializeTest(int* argc, char** argv);
+
enum { MAY_NOT_BE_BAG = false };
void SetTestDataPath(const std::string& path);
@@ -56,6 +58,8 @@
::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id,
const char* expected_str);
+std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx);
+
} // namespace android
#endif // TEST_HELPERS_H_
diff --git a/libs/androidfw/tests/TestMain.cpp b/libs/androidfw/tests/TestMain.cpp
new file mode 100644
index 0000000..d1c0f60
--- /dev/null
+++ b/libs/androidfw/tests/TestMain.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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 <iostream>
+
+#include "TestHelpers.h"
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ::android::InitializeTest(&argc, argv);
+
+ std::cerr << "using --testdata=" << ::android::GetTestDataPath() << "\n";
+
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/androidfw/tests/Theme_bench.cpp b/libs/androidfw/tests/Theme_bench.cpp
new file mode 100644
index 0000000..c471be6
--- /dev/null
+++ b/libs/androidfw/tests/Theme_bench.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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 "benchmark/benchmark.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
+
+namespace android {
+
+constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk";
+constexpr const static uint32_t kStyleId = 0x01030237u; // android:style/Theme.Material.Light
+constexpr const static uint32_t kAttrId = 0x01010030u; // android:attr/colorForeground
+
+static void BM_ThemeApplyStyleFramework(benchmark::State& state) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ while (state.KeepRunning()) {
+ auto theme = assets.NewTheme();
+ theme->ApplyStyle(kStyleId, false /* force */);
+ }
+}
+BENCHMARK(BM_ThemeApplyStyleFramework);
+
+static void BM_ThemeApplyStyleFrameworkOld(benchmark::State& state) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /* cookie */, false /* appAsLib */,
+ true /* isSystemAsset */)) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ const ResTable& res_table = assets.getResources(true);
+
+ while (state.KeepRunning()) {
+ std::unique_ptr<ResTable::Theme> theme{new ResTable::Theme(res_table)};
+ theme->applyStyle(kStyleId, false /* force */);
+ }
+}
+BENCHMARK(BM_ThemeApplyStyleFrameworkOld);
+
+static void BM_ThemeGetAttribute(benchmark::State& state) {
+ std::unique_ptr<ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ auto theme = assets.NewTheme();
+ theme->ApplyStyle(kStyleId, false /* force */);
+
+ Res_value value;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ theme->GetAttribute(kAttrId, &value, &flags);
+ }
+}
+BENCHMARK(BM_ThemeGetAttribute);
+
+static void BM_ThemeGetAttributeOld(benchmark::State& state) {
+ AssetManager assets;
+ assets.addAssetPath(String8(kFrameworkPath), nullptr /* cookie */, false /* appAsLib */,
+ true /* isSystemAsset */);
+ const ResTable& res_table = assets.getResources(true);
+ std::unique_ptr<ResTable::Theme> theme{new ResTable::Theme(res_table)};
+ theme->applyStyle(kStyleId, false /* force */);
+
+ Res_value value;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ theme->getAttribute(kAttrId, &value, &flags);
+ }
+}
+BENCHMARK(BM_ThemeGetAttributeOld);
+
+} // namespace android
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index 3774657..c0011b6d 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,59 +14,221 @@
* limitations under the License.
*/
-#include "androidfw/ResourceTypes.h"
+#include "androidfw/AssetManager2.h"
-#include "utils/String16.h"
-#include "utils/String8.h"
+#include "android-base/logging.h"
#include "TestHelpers.h"
-#include "data/app/R.h"
-#include "data/system/R.h"
+#include "data/styles/R.h"
namespace app = com::android::app;
namespace android {
-/**
- * TODO(adamlesinski): Enable when fixed.
- */
-TEST(ThemeTest, DISABLED_shouldCopyThemeFromDifferentResTable) {
- ResTable table;
+class ThemeTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ ASSERT_NE(nullptr, style_assets_);
+ }
- std::string system_contents;
- ASSERT_TRUE(ReadFileFromZipToString("/system/system.apk", "resources.arsc",
- &system_contents));
- ASSERT_EQ(NO_ERROR,
- table.add(system_contents.data(), system_contents.size()));
+ protected:
+ std::unique_ptr<ApkAssets> style_assets_;
+};
- std::string app_contents;
- ASSERT_TRUE(ReadFileFromZipToString("/basic/basic.apk", "resources.arsc",
- &app_contents));
- ASSERT_EQ(NO_ERROR, table.add(app_contents.data(), app_contents.size()));
+TEST_F(ThemeTest, EmptyTheme) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
- ResTable::Theme theme1(table);
- ASSERT_EQ(NO_ERROR, theme1.applyStyle(app::R::style::Theme_One));
- Res_value val;
- ASSERT_GE(theme1.getAttribute(android::R::attr::background, &val), 0);
- ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
- ASSERT_EQ(uint32_t(0xffff0000), val.data);
- ASSERT_GE(theme1.getAttribute(app::R::attr::number, &val), 0);
- ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
- ASSERT_EQ(uint32_t(1), val.data);
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ EXPECT_EQ(0u, theme->GetChangingConfigurations());
+ EXPECT_EQ(&assetmanager, theme->GetAssetManager());
- ResTable table2;
- ASSERT_EQ(NO_ERROR,
- table2.add(system_contents.data(), system_contents.size()));
- ASSERT_EQ(NO_ERROR, table2.add(app_contents.data(), app_contents.size()));
+ Res_value value;
+ uint32_t flags;
+ EXPECT_EQ(kInvalidCookie, theme->GetAttribute(app::R::attr::attr_one, &value, &flags));
+}
- ResTable::Theme theme2(table2);
- ASSERT_EQ(NO_ERROR, theme2.setTo(theme1));
- ASSERT_GE(theme2.getAttribute(android::R::attr::background, &val), 0);
- ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
- ASSERT_EQ(uint32_t(0xffff0000), val.data);
- ASSERT_GE(theme2.getAttribute(app::R::attr::number, &val), 0);
- ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
- ASSERT_EQ(uint32_t(1), val.data);
+TEST_F(ThemeTest, SingleThemeNoParent) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleOne));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ cookie = theme->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ cookie = theme->GetAttribute(app::R::attr::attr_two, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(2u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, SingleThemeWithParent) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ cookie = theme->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ cookie = theme->GetAttribute(app::R::attr::attr_two, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
+ EXPECT_EQ(0, cookie);
+ EXPECT_EQ(std::string("string"),
+ GetStringFromPool(assetmanager.GetStringPoolForCookie(0), value.data));
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // This attribute should point to an attr_indirect, so the result should be 3.
+ cookie = theme->GetAttribute(app::R::attr::attr_three, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(3u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, MultipleThemesOverlaidNotForce) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleThree));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ // attr_one is still here from the base.
+ cookie = theme->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // check for the new attr_six
+ cookie = theme->GetAttribute(app::R::attr::attr_six, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(6u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // check for the old attr_five (force=true was not used).
+ cookie = theme->GetAttribute(app::R::attr::attr_five, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
+ EXPECT_EQ(app::R::string::string_one, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, MultipleThemesOverlaidForced) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+ ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleThree, true /* force */));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ // attr_one is still here from the base.
+ cookie = theme->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // check for the new attr_six
+ cookie = theme->GetAttribute(app::R::attr::attr_six, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(6u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // check for the new attr_five (force=true was used).
+ cookie = theme->GetAttribute(app::R::attr::attr_five, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(5u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, CopyThemeSameAssetManager) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({style_assets_.get()});
+
+ std::unique_ptr<Theme> theme_one = assetmanager.NewTheme();
+ ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne));
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie;
+
+ // attr_one is still here from the base.
+ cookie = theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(1u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+ // attr_six is not here.
+ EXPECT_EQ(kInvalidCookie, theme_one->GetAttribute(app::R::attr::attr_six, &value, &flags));
+
+ std::unique_ptr<Theme> theme_two = assetmanager.NewTheme();
+ ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleThree));
+
+ // Copy the theme to theme_one.
+ ASSERT_TRUE(theme_one->SetTo(*theme_two));
+
+ // Clear theme_two to make sure we test that there WAS a copy.
+ theme_two->Clear();
+
+ // attr_one is now not here.
+ EXPECT_EQ(kInvalidCookie, theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags));
+
+ // attr_six is now here because it was copied.
+ cookie = theme_one->GetAttribute(app::R::attr::attr_six, &value, &flags);
+ ASSERT_NE(kInvalidCookie, cookie);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+ EXPECT_EQ(6u, value.data);
+ EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+}
+
+TEST_F(ThemeTest, FailToCopyThemeWithDifferentAssetManager) {
+ AssetManager2 assetmanager_one;
+ assetmanager_one.SetApkAssets({style_assets_.get()});
+
+ AssetManager2 assetmanager_two;
+ assetmanager_two.SetApkAssets({style_assets_.get()});
+
+ auto theme_one = assetmanager_one.NewTheme();
+ ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne));
+
+ auto theme_two = assetmanager_two.NewTheme();
+ ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleTwo));
+
+ EXPECT_FALSE(theme_one->SetTo(*theme_two));
}
} // namespace android
diff --git a/libs/androidfw/tests/data/styles/R.h b/libs/androidfw/tests/data/styles/R.h
index 4127aa0..68527c7 100644
--- a/libs/androidfw/tests/data/styles/R.h
+++ b/libs/androidfw/tests/data/styles/R.h
@@ -32,6 +32,7 @@
attr_four = 0x7f010003u,
attr_five = 0x7f010004u,
attr_indirect = 0x7f010005u,
+ attr_six = 0x7f010006u,
};
};
@@ -45,6 +46,7 @@
enum : uint32_t {
StyleOne = 0x7f020000u,
StyleTwo = 0x7f020001u,
+ StyleThree = 0x7f020002u,
};
};
};
diff --git a/libs/androidfw/tests/data/styles/res/values/styles.xml b/libs/androidfw/tests/data/styles/res/values/styles.xml
index 70c54f6..da592f8 100644
--- a/libs/androidfw/tests/data/styles/res/values/styles.xml
+++ b/libs/androidfw/tests/data/styles/res/values/styles.xml
@@ -39,6 +39,7 @@
<public type="style" name="StyleOne" id="0x7f020000" />
<style name="StyleOne">
<item name="attr_one">1</item>
+ <item name="attr_two">2</item>
</style>
<public type="style" name="StyleTwo" id="0x7f020001" />
@@ -48,5 +49,14 @@
<item name="attr_three">?attr/attr_indirect</item>
<item name="attr_five">@string/string_one</item>
</style>
+
+ <public type="attr" name="attr_six" id="0x7f010006" />
+ <attr name="attr_six" />
+
+ <public type="style" name="StyleThree" id="0x7f020002" />
+ <style name="StyleThree">
+ <item name="attr_six">6</item>
+ <item name="attr_five">5</item>
+ </style>
</resources>
diff --git a/libs/androidfw/tests/data/styles/styles.apk b/libs/androidfw/tests/data/styles/styles.apk
index 6064c48..d4ccb83 100644
--- a/libs/androidfw/tests/data/styles/styles.apk
+++ b/libs/androidfw/tests/data/styles/styles.apk
Binary files differ
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 3e8e8a1..0ae50e9 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -131,7 +131,7 @@
mLayer->getApi(), Layer::Api::OpenGL, Layer::Api::Vulkan);
static const mat4 identityMatrix;
- updateLayer(false, identityMatrix.data);
+ updateLayer(false, GL_NONE, identityMatrix.data);
VkLayer* vkLayer = static_cast<VkLayer*>(mLayer);
vkLayer->updateTexture();
@@ -139,26 +139,20 @@
void DeferredLayerUpdater::updateLayer(bool forceFilter, GLenum renderTarget,
const float* textureTransform) {
- LOG_ALWAYS_FATAL_IF(mLayer->getApi() != Layer::Api::OpenGL,
- "updateLayer non GL backend %x, GL %x, VK %x",
- mLayer->getApi(), Layer::Api::OpenGL, Layer::Api::Vulkan);
-
- updateLayer(forceFilter, textureTransform);
-
- GlLayer* glLayer = static_cast<GlLayer*>(mLayer);
- if (renderTarget != glLayer->getRenderTarget()) {
- glLayer->setRenderTarget(renderTarget);
- glLayer->bindTexture();
- glLayer->setFilter(GL_NEAREST, false, true);
- glLayer->setWrap(GL_CLAMP_TO_EDGE, false, true);
- }
-}
-
-void DeferredLayerUpdater::updateLayer(bool forceFilter, const float* textureTransform) {
mLayer->setBlend(mBlend);
mLayer->setForceFilter(forceFilter);
mLayer->setSize(mWidth, mHeight);
mLayer->getTexTransform().load(textureTransform);
+
+ if (mLayer->getApi() == Layer::Api::OpenGL) {
+ GlLayer* glLayer = static_cast<GlLayer*>(mLayer);
+ if (renderTarget != glLayer->getRenderTarget()) {
+ glLayer->setRenderTarget(renderTarget);
+ glLayer->bindTexture();
+ glLayer->setFilter(GL_NEAREST, false, true);
+ glLayer->setWrap(GL_CLAMP_TO_EDGE, false, true);
+ }
+ }
}
void DeferredLayerUpdater::detachSurfaceTexture() {
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index ead8314..3814be2 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -114,7 +114,6 @@
void doUpdateTexImage();
void doUpdateVkTexImage();
- void updateLayer(bool forceFilter, const float* textureTransform);
};
} /* namespace uirenderer */
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 0702010..09e34bf 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -222,6 +222,12 @@
return sRenderPipelineType;
}
+#ifdef HWUI_GLES_WRAP_ENABLED
+void Properties::overrideRenderPipelineType(RenderPipelineType type) {
+ sRenderPipelineType = type;
+}
+#endif
+
bool Properties::isSkiaEnabled() {
auto renderType = getRenderPipelineType();
return RenderPipelineType::SkiaGL == renderType
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index b4a3118..6dc0cb3 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -318,11 +318,15 @@
// any overhead they add
static bool filterOutTestOverhead;
+ // Used for testing only to change the render pipeline.
+#ifdef HWUI_GLES_WRAP_ENABLED
+ static void overrideRenderPipelineType(RenderPipelineType);
+#endif
+
private:
static ProfileType sProfileType;
static bool sDisableProfileBars;
static RenderPipelineType sRenderPipelineType;
-
}; // class Caches
}; // namespace uirenderer
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 5f6bcb3..275ce16 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -21,6 +21,9 @@
#include <renderthread/EglManager.h>
#include <renderthread/OpenGLPipeline.h>
+#include <pipeline/skia/SkiaOpenGLPipeline.h>
+#include <pipeline/skia/SkiaVulkanPipeline.h>
+#include <renderthread/VulkanManager.h>
#include <utils/Unicode.h>
#include <SkClipStack.h>
@@ -47,10 +50,24 @@
}
sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater(
+ renderthread::RenderThread& renderThread) {
+ android::uirenderer::renderthread::IRenderPipeline* pipeline;
+ if (Properties::getRenderPipelineType() == RenderPipelineType::OpenGL) {
+ pipeline = new renderthread::OpenGLPipeline(renderThread);
+ } else if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
+ pipeline = new skiapipeline::SkiaOpenGLPipeline(renderThread);
+ } else {
+ pipeline = new skiapipeline::SkiaVulkanPipeline(renderThread);
+ }
+ sp<DeferredLayerUpdater> layerUpdater = pipeline->createTextureLayer();
+ delete pipeline;
+ return layerUpdater;
+}
+
+sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater(
renderthread::RenderThread& renderThread, uint32_t width, uint32_t height,
const SkMatrix& transform) {
- renderthread::OpenGLPipeline pipeline(renderThread);
- sp<DeferredLayerUpdater> layerUpdater = pipeline.createTextureLayer();
+ sp<DeferredLayerUpdater> layerUpdater = createTextureLayerUpdater(renderThread);
layerUpdater->backingLayer()->getTransform().load(transform);
layerUpdater->setSize(width, height);
layerUpdater->setTransform(&transform);
@@ -111,12 +128,20 @@
void TestUtils::TestTask::run() {
// RenderState only valid once RenderThread is running, so queried here
renderthread::RenderThread& renderThread = renderthread::RenderThread::getInstance();
- renderThread.eglManager().initialize();
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ renderThread.vulkanManager().initialize();
+ } else {
+ renderThread.eglManager().initialize();
+ }
rtCallback(renderThread);
- renderThread.renderState().flush(Caches::FlushMode::Full);
- renderThread.eglManager().destroy();
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ renderThread.vulkanManager().destroy();
+ } else {
+ renderThread.renderState().flush(Caches::FlushMode::Full);
+ renderThread.eglManager().destroy();
+ }
}
std::unique_ptr<uint16_t[]> TestUtils::asciiToUtf16(const char* str) {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 80cbb24..8b287de 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -19,6 +19,7 @@
#include <DeviceInfo.h>
#include <DisplayList.h>
#include <Matrix.h>
+#include <Properties.h>
#include <Rect.h>
#include <RenderNode.h>
#include <hwui/Bitmap.h>
@@ -51,6 +52,31 @@
} else { \
ADD_FAILURE() << "ClipState not a rect"; \
}
+
+#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
+ TEST(test_case_name, test_name##_##pipeline) { \
+ RenderPipelineType oldType = Properties::getRenderPipelineType(); \
+ Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \
+ functionCall; \
+ Properties::overrideRenderPipelineType(oldType); \
+ };
+
+/**
+ * Like gtests' TEST, but only runs with the OpenGL RenderPipelineType
+ */
+#define OPENGL_PIPELINE_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_HwuiTest { \
+ public: \
+ static void doTheThing(); \
+ }; \
+ INNER_PIPELINE_TEST(test_case_name, test_name, OpenGL, \
+ test_case_name##_##test_name##_HwuiTest::doTheThing()) \
+ void test_case_name##_##test_name##_HwuiTest::doTheThing()
+
+#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
+ INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, \
+ TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing))
+
/**
* Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
* (for e.g. accessing its RenderState)
@@ -60,9 +86,32 @@
public: \
static void doTheThing(renderthread::RenderThread& renderThread); \
}; \
- TEST(test_case_name, test_name) { \
- TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, OpenGL); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \
+ void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
+
+/**
+ * Like RENDERTHREAD_TEST, but only runs with the OpenGL RenderPipelineType
+ */
+#define RENDERTHREAD_OPENGL_PIPELINE_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_RenderThreadTest { \
+ public: \
+ static void doTheThing(renderthread::RenderThread& renderThread); \
}; \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, OpenGL); \
+ void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
+
+/**
+ * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
+ */
+#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_RenderThreadTest { \
+ public: \
+ static void doTheThing(renderthread::RenderThread& renderThread); \
+ }; \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \
void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
/**
@@ -137,6 +186,9 @@
}
static sp<DeferredLayerUpdater> createTextureLayerUpdater(
+ renderthread::RenderThread& renderThread);
+
+ static sp<DeferredLayerUpdater> createTextureLayerUpdater(
renderthread::RenderThread& renderThread, uint32_t width, uint32_t height,
const SkMatrix& transform);
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
index d44be7d..9a3b81c 100644
--- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
@@ -80,7 +80,7 @@
<< "Glop(s) expected";
}
-RENDERTHREAD_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) {
SkPaint strokePaint;
strokePaint.setStyle(SkPaint::kStroke_Style);
strokePaint.setStrokeWidth(4);
@@ -113,7 +113,7 @@
testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier);
}
-RENDERTHREAD_TEST(BakedOpDispatcher, onLayerOp_bufferless) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, onLayerOp_bufferless) {
SkPaint layerPaint;
layerPaint.setAlpha(128);
OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case
@@ -131,7 +131,7 @@
return result;
}
-RENDERTHREAD_TEST(BakedOpDispatcher, offsetFlags) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, offsetFlags) {
Rect bounds(10, 15, 20, 25);
SkPaint paint;
SkPaint aaPaint;
@@ -157,7 +157,7 @@
<< "Expect an offset for non-AA lines.";
}
-RENDERTHREAD_TEST(BakedOpDispatcher, renderTextWithShadow) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, renderTextWithShadow) {
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
@@ -232,7 +232,7 @@
return c;
}
-RENDERTHREAD_TEST(BakedOpDispatcher, layerUpdateProperties) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, layerUpdateProperties) {
for (bool debugOverdraw : { false, true }) {
for (bool debugLayersUpdates : { false, true }) {
ScopedProperty<bool> ovdProp(Properties::debugOverdraw, debugOverdraw);
@@ -273,7 +273,7 @@
}
}
-RENDERTHREAD_TEST(BakedOpDispatcher, pathTextureSnapping) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTextureSnapping) {
Rect bounds(10, 15, 20, 25);
SkPaint paint;
SkPath path;
diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
index 59bd75e..380062a 100644
--- a/libs/hwui/tests/unit/BakedOpRendererTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
@@ -23,7 +23,7 @@
const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
-RENDERTHREAD_TEST(BakedOpRenderer, startRepaintLayer_clear) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, startRepaintLayer_clear) {
BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), true, sLightInfo);
OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200u, 200u);
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index d3d80a9..42ba3db 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -46,5 +46,10 @@
RENDERTHREAD_TEST(CanvasContext, invokeFunctor) {
TestUtils::MockFunctor functor;
CanvasContext::invokeFunctor(renderThread, &functor);
- ASSERT_EQ(functor.getLastMode(), DrawGlInfo::kModeProcess);
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ // we currently don't support OpenGL WebViews on the Vulkan backend
+ ASSERT_EQ(functor.getLastMode(), DrawGlInfo::kModeProcessNoContext);
+ } else {
+ ASSERT_EQ(functor.getLastMode(), DrawGlInfo::kModeProcess);
+ }
}
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index f1b8882..1ef9dba 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -16,8 +16,8 @@
#include "DeferredLayerUpdater.h"
#include "GlLayer.h"
+#include "Properties.h"
-#include "renderthread/OpenGLPipeline.h"
#include "tests/common/TestUtils.h"
#include <gtest/gtest.h>
@@ -26,12 +26,10 @@
using namespace android::uirenderer;
RENDERTHREAD_TEST(DeferredLayerUpdater, updateLayer) {
- renderthread::OpenGLPipeline pipeline(renderThread);
- sp<DeferredLayerUpdater> layerUpdater = pipeline.createTextureLayer();
+ sp<DeferredLayerUpdater> layerUpdater = TestUtils::createTextureLayerUpdater(renderThread);
layerUpdater->setSize(100, 100);
layerUpdater->setBlend(true);
-
// updates are deferred so the backing layer should still be in its default state
if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) {
GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer());
diff --git a/libs/hwui/tests/unit/DeviceInfoTests.cpp b/libs/hwui/tests/unit/DeviceInfoTests.cpp
index 17236bd..af37938 100644
--- a/libs/hwui/tests/unit/DeviceInfoTests.cpp
+++ b/libs/hwui/tests/unit/DeviceInfoTests.cpp
@@ -17,11 +17,12 @@
#include <DeviceInfo.h>
#include <gtest/gtest.h>
+#include "tests/common/TestUtils.h"
using namespace android;
using namespace android::uirenderer;
-TEST(DeviceInfo, basic) {
+OPENGL_PIPELINE_TEST(DeviceInfo, basic) {
// can't assert state before init - another test may have initialized the singleton
DeviceInfo::initialize();
const DeviceInfo* di = DeviceInfo::get();
diff --git a/libs/hwui/tests/unit/FontRendererTests.cpp b/libs/hwui/tests/unit/FontRendererTests.cpp
index 99080ac..ee20236 100644
--- a/libs/hwui/tests/unit/FontRendererTests.cpp
+++ b/libs/hwui/tests/unit/FontRendererTests.cpp
@@ -28,7 +28,7 @@
return true;
}
-RENDERTHREAD_TEST(FontRenderer, renderDropShadow) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FontRenderer, renderDropShadow) {
SkPaint paint;
paint.setTextSize(10);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 71c7516..6f3ed9c 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -109,7 +109,7 @@
class FailRenderer : public TestRendererBase {};
-RENDERTHREAD_TEST(FrameBuilder, simple) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simple) {
class SimpleTestRenderer : public TestRendererBase {
public:
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
@@ -143,7 +143,7 @@
EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
}
-RENDERTHREAD_TEST(FrameBuilder, simpleStroke) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleStroke) {
class SimpleStrokeTestRenderer : public TestRendererBase {
public:
void onPointsOp(const PointsOp& op, const BakedOpState& state) override {
@@ -171,7 +171,7 @@
EXPECT_EQ(1, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, simpleRejection) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleRejection) {
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
@@ -187,7 +187,7 @@
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
}
-RENDERTHREAD_TEST(FrameBuilder, simpleBatching) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleBatching) {
const int LOOPS = 5;
class SimpleBatchingTestRenderer : public TestRendererBase {
public:
@@ -225,7 +225,7 @@
<< "Expect number of ops = 2 * loop count";
}
-RENDERTHREAD_TEST(FrameBuilder, deferRenderNode_translateClip) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferRenderNode_translateClip) {
class DeferRenderNodeTranslateClipTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -251,7 +251,7 @@
EXPECT_EQ(1, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, deferRenderNodeScene) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferRenderNodeScene) {
class DeferRenderNodeSceneTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -320,7 +320,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, empty_noFbo0) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, empty_noFbo0) {
class EmptyNoFbo0TestRenderer : public TestRendererBase {
public:
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
@@ -338,7 +338,7 @@
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
}
-RENDERTHREAD_TEST(FrameBuilder, empty_withFbo0) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, empty_withFbo0) {
class EmptyWithFbo0TestRenderer : public TestRendererBase {
public:
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
@@ -364,7 +364,7 @@
" but fbo0 update lifecycle should still be observed";
}
-RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, avoidOverdraw_rects) {
class AvoidOverdrawRectsTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -394,7 +394,7 @@
EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op";
}
-RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
static sk_sp<Bitmap> opaqueBitmap(TestUtils::createBitmap(50, 50,
SkColorType::kRGB_565_SkColorType));
static sk_sp<Bitmap> transpBitmap(TestUtils::createBitmap(50, 50,
@@ -437,7 +437,7 @@
EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly two ops";
}
-RENDERTHREAD_TEST(FrameBuilder, clippedMerging) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clippedMerging) {
class ClippedMergingTestRenderer : public TestRendererBase {
public:
void onMergedBitmapOps(const MergedBakedOpList& opList) override {
@@ -479,7 +479,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, regionClipStopsMerge) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, regionClipStopsMerge) {
class RegionClipStopsMergeTestRenderer : public TestRendererBase {
public:
void onTextOp(const TextOp& op, const BakedOpState& state) override { mIndex++; }
@@ -508,7 +508,7 @@
EXPECT_EQ(2, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, textMerging) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textMerging) {
class TextMergingTestRenderer : public TestRendererBase {
public:
void onMergedTextOps(const MergedBakedOpList& opList) override {
@@ -538,7 +538,7 @@
EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
}
-RENDERTHREAD_TEST(FrameBuilder, textStrikethrough) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStrikethrough) {
const int LOOPS = 5;
class TextStrikethroughTestRenderer : public TestRendererBase {
public:
@@ -576,7 +576,7 @@
static auto styles = {
SkPaint::kFill_Style, SkPaint::kStroke_Style, SkPaint::kStrokeAndFill_Style };
-RENDERTHREAD_TEST(FrameBuilder, textStyle) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStyle) {
class TextStyleTestRenderer : public TestRendererBase {
public:
void onMergedTextOps(const MergedBakedOpList& opList) override {
@@ -630,7 +630,7 @@
EXPECT_EQ(3, renderer.getIndex()) << "Expect 3 ops";
}
-RENDERTHREAD_TEST(FrameBuilder, textureLayer_clipLocalMatrix) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_clipLocalMatrix) {
class TextureLayerClipLocalMatrixTestRenderer : public TestRendererBase {
public:
void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
@@ -664,7 +664,7 @@
EXPECT_EQ(1, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, textureLayer_combineMatrices) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_combineMatrices) {
class TextureLayerCombineMatricesTestRenderer : public TestRendererBase {
public:
void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
@@ -696,10 +696,10 @@
EXPECT_EQ(1, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, textureLayer_reject) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_reject) {
auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100,
SkMatrix::MakeTrans(5, 5));
- if (layerUpdater->backingLayer()->getApi() != Layer::Api::OpenGL) return;
+ EXPECT_EQ(Layer::Api::OpenGL, layerUpdater->backingLayer()->getApi());
GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer());
glLayer->setRenderTarget(GL_NONE); // Should be rejected
@@ -717,7 +717,7 @@
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
}
-RENDERTHREAD_TEST(FrameBuilder, functor_reject) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, functor_reject) {
class FunctorTestRenderer : public TestRendererBase {
public:
void onFunctorOp(const FunctorOp& op, const BakedOpState& state) override {
@@ -742,7 +742,7 @@
EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected";
}
-RENDERTHREAD_TEST(FrameBuilder, deferColorOp_unbounded) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferColorOp_unbounded) {
class ColorTestRenderer : public TestRendererBase {
public:
void onColorOp(const ColorOp& op, const BakedOpState& state) override {
@@ -767,7 +767,7 @@
EXPECT_EQ(1, renderer.getIndex()) << "ColorOp should not be rejected";
}
-TEST(FrameBuilder, renderNode) {
+OPENGL_PIPELINE_TEST(FrameBuilder, renderNode) {
class RenderNodeTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -814,7 +814,7 @@
EXPECT_EQ(2, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, clipped) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clipped) {
class ClippedTestRenderer : public TestRendererBase {
public:
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
@@ -840,7 +840,7 @@
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
}
-RENDERTHREAD_TEST(FrameBuilder, saveLayer_simple) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_simple) {
class SaveLayerSimpleTestRenderer : public TestRendererBase {
public:
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
@@ -890,7 +890,7 @@
EXPECT_EQ(5, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, saveLayer_nested) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_nested) {
/* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
* - startTemporaryLayer2, rect2 endLayer2
* - startTemporaryLayer1, rect1, drawLayer2, endLayer1
@@ -973,7 +973,7 @@
EXPECT_EQ(12, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, saveLayer_contentRejection) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_contentRejection) {
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
@@ -996,7 +996,7 @@
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
}
-RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_simple) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_simple) {
class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase {
public:
void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -1041,7 +1041,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_round) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_round) {
class SaveLayerUnclippedRoundTestRenderer : public TestRendererBase {
public:
void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -1075,7 +1075,7 @@
EXPECT_EQ(2, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_mergedClears) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_mergedClears) {
class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase {
public:
void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -1133,7 +1133,7 @@
<< "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect.";
}
-RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_clearClip) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_clearClip) {
class SaveLayerUnclippedClearClipTestRenderer : public TestRendererBase {
public:
void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -1175,7 +1175,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_reject) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_reject) {
auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
// unclipped savelayer + rect both in area that won't intersect with dirty
@@ -1197,7 +1197,7 @@
* - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer
* - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe
*/
-RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_complex) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_complex) {
class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase {
public:
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
@@ -1262,7 +1262,7 @@
EXPECT_EQ(13, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, hwLayer_simple) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, hwLayer_simple) {
class HwLayerSimpleTestRenderer : public TestRendererBase {
public:
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
@@ -1326,7 +1326,7 @@
*layerHandle = nullptr;
}
-RENDERTHREAD_TEST(FrameBuilder, hwLayer_complex) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, hwLayer_complex) {
/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
* - startRepaintLayer(child), rect(grey), endLayer
* - startTemporaryLayer, drawLayer(child), endLayer
@@ -1435,7 +1435,7 @@
}
-RENDERTHREAD_TEST(FrameBuilder, buildLayer) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, buildLayer) {
class BuildLayerTestRenderer : public TestRendererBase {
public:
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
@@ -1531,7 +1531,7 @@
} // end anonymous namespace
-RENDERTHREAD_TEST(FrameBuilder, zReorder) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, zReorder) {
auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
@@ -1566,7 +1566,7 @@
EXPECT_EQ(13, renderer.getIndex());
};
-RENDERTHREAD_TEST(FrameBuilder, projectionReorder) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorder) {
static const int scrollX = 5;
static const int scrollY = 10;
class ProjectionReorderTestRenderer : public TestRendererBase {
@@ -1659,7 +1659,7 @@
EXPECT_EQ(3, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, projectionHwLayer) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionHwLayer) {
static const int scrollX = 5;
static const int scrollY = 10;
class ProjectionHwLayerTestRenderer : public TestRendererBase {
@@ -1750,7 +1750,7 @@
*layerHandle = nullptr;
}
-RENDERTHREAD_TEST(FrameBuilder, projectionChildScroll) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionChildScroll) {
static const int scrollX = 500000;
static const int scrollY = 0;
class ProjectionChildScrollTestRenderer : public TestRendererBase {
@@ -1817,7 +1817,7 @@
});
}
-RENDERTHREAD_TEST(FrameBuilder, shadow) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadow) {
class ShadowTestRenderer : public TestRendererBase {
public:
void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
@@ -1850,7 +1850,7 @@
EXPECT_EQ(2, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, shadowSaveLayer) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowSaveLayer) {
class ShadowSaveLayerTestRenderer : public TestRendererBase {
public:
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
@@ -1896,7 +1896,7 @@
EXPECT_EQ(6, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, shadowHwLayer) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowHwLayer) {
class ShadowHwLayerTestRenderer : public TestRendererBase {
public:
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
@@ -1954,7 +1954,7 @@
*layerHandle = nullptr;
}
-RENDERTHREAD_TEST(FrameBuilder, shadowLayering) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowLayering) {
class ShadowLayeringTestRenderer : public TestRendererBase {
public:
void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
@@ -1981,7 +1981,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-RENDERTHREAD_TEST(FrameBuilder, shadowClipping) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowClipping) {
class ShadowClippingTestRenderer : public TestRendererBase {
public:
void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
@@ -2041,7 +2041,7 @@
EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
}
-RENDERTHREAD_TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) {
testProperty([](RenderProperties& properties) {
properties.setAlpha(0.5f);
properties.setHasOverlappingRendering(false);
@@ -2050,7 +2050,7 @@
});
}
-RENDERTHREAD_TEST(FrameBuilder, renderPropClipping) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropClipping) {
testProperty([](RenderProperties& properties) {
properties.setClipToBounds(true);
properties.setClipBounds(Rect(10, 20, 300, 400));
@@ -2060,7 +2060,7 @@
});
}
-RENDERTHREAD_TEST(FrameBuilder, renderPropRevealClip) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropRevealClip) {
testProperty([](RenderProperties& properties) {
properties.mutableRevealClip().set(true, 50, 50, 25);
}, [](const RectOp& op, const BakedOpState& state) {
@@ -2071,7 +2071,7 @@
});
}
-RENDERTHREAD_TEST(FrameBuilder, renderPropOutlineClip) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropOutlineClip) {
testProperty([](RenderProperties& properties) {
properties.mutableOutline().setShouldClip(true);
properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
@@ -2083,7 +2083,7 @@
});
}
-RENDERTHREAD_TEST(FrameBuilder, renderPropTransform) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropTransform) {
testProperty([](RenderProperties& properties) {
properties.setLeftTopRightBottom(10, 10, 110, 110);
@@ -2192,7 +2192,7 @@
ASSERT_EQ(5, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior.";
}
-RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) {
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
properties.setTranslationX(10); // offset rendering content
@@ -2211,7 +2211,7 @@
<< "expect drawLayer to be translated as part of being clipped";
}
-RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) {
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
// Translate and rotate the view so that the only visible part is the top left corner of
@@ -2230,7 +2230,7 @@
EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
}
-RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaScale) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropSaveLayerAlphaScale) {
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
properties.setPivotX(0);
@@ -2244,7 +2244,7 @@
EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
}
-RENDERTHREAD_TEST(FrameBuilder, clip_replace) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clip_replace) {
class ClipReplaceTestRenderer : public TestRendererBase {
public:
void onColorOp(const ColorOp& op, const BakedOpState& state) override {
@@ -2269,7 +2269,7 @@
EXPECT_EQ(1, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderProjectedInMiddle) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedInMiddle) {
/* R is backward projected on B
A
/ \
@@ -2299,7 +2299,7 @@
EXPECT_EQ(3, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderProjectLast) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectLast) {
/* R is backward projected on E
A
/ | \
@@ -2331,7 +2331,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderNoReceivable) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderNoReceivable) {
/* R is backward projected without receiver
A
/ \
@@ -2360,7 +2360,7 @@
EXPECT_EQ(2, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderParentReceivable) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderParentReceivable) {
/* R is backward projected on C
A
/ \
@@ -2389,7 +2389,7 @@
EXPECT_EQ(3, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderSameNodeReceivable) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderSameNodeReceivable) {
auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, nullptr); //nodeB
@@ -2412,7 +2412,7 @@
EXPECT_EQ(2, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderProjectedSibling) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedSibling) {
//TODO: this test together with the next "projectionReorderProjectedSibling2" likely expose a
//bug in HWUI. First test draws R, while the second test does not draw R for a nearly identical
//tree setup. The correct behaviour is to not draw R, because the receiver cannot be a sibling
@@ -2445,7 +2445,7 @@
EXPECT_EQ(3, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderProjectedSibling2) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedSibling2) {
/* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
A
|
@@ -2478,7 +2478,7 @@
EXPECT_EQ(3, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderGrandparentReceivable) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderGrandparentReceivable) {
/* R is backward projected on B
A
|
@@ -2510,7 +2510,7 @@
EXPECT_EQ(3, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderTwoReceivables) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivables) {
/* B and G are receivables, R is backward projected
A
/ \
@@ -2543,7 +2543,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderTwoReceivablesLikelyScenario) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivablesLikelyScenario) {
/* B and G are receivables, G is backward projected
A
/ \
@@ -2576,7 +2576,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-TEST(FrameBuilder, projectionReorderTwoReceivablesDeeper) {
+OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivablesDeeper) {
/* B and G are receivables, R is backward projected
A
/ \
diff --git a/libs/hwui/tests/unit/GlopBuilderTests.cpp b/libs/hwui/tests/unit/GlopBuilderTests.cpp
index ce1db05..caeb6bf 100644
--- a/libs/hwui/tests/unit/GlopBuilderTests.cpp
+++ b/libs/hwui/tests/unit/GlopBuilderTests.cpp
@@ -116,7 +116,7 @@
return glop;
}
-RENDERTHREAD_TEST(GlopBuilder, rectSnapTest) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(GlopBuilder, rectSnapTest) {
RenderState& renderState = renderThread.renderState();
Caches& caches = Caches::getInstance();
SkPaint paint;
diff --git a/libs/hwui/tests/unit/GradientCacheTests.cpp b/libs/hwui/tests/unit/GradientCacheTests.cpp
index 0ee9647..a3b346f 100644
--- a/libs/hwui/tests/unit/GradientCacheTests.cpp
+++ b/libs/hwui/tests/unit/GradientCacheTests.cpp
@@ -23,7 +23,7 @@
using namespace android;
using namespace android::uirenderer;
-RENDERTHREAD_TEST(GradientCache, addRemove) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(GradientCache, addRemove) {
Extensions extensions;
GradientCache cache(extensions);
ASSERT_LT(1000u, cache.getMaxSize()) << "Expect non-trivial size";
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
index 06599dd..6c42ca1 100644
--- a/libs/hwui/tests/unit/LeakCheckTests.cpp
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -29,7 +29,7 @@
const FrameBuilder::LightGeometry sLightGeometery = { {100, 100, 100}, 50};
const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
-RENDERTHREAD_TEST(LeakCheck, saveLayer_overdrawRejection) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(LeakCheck, saveLayer_overdrawRejection) {
auto node = TestUtils::createNode(0, 0, 100, 100,
[](RenderProperties& props, Canvas& canvas) {
canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
@@ -49,7 +49,7 @@
frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
}
-RENDERTHREAD_TEST(LeakCheck, saveLayerUnclipped_simple) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(LeakCheck, saveLayerUnclipped_simple) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, Canvas& canvas) {
canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
diff --git a/libs/hwui/tests/unit/MeshStateTests.cpp b/libs/hwui/tests/unit/MeshStateTests.cpp
index 0881fa2..511d6d2 100644
--- a/libs/hwui/tests/unit/MeshStateTests.cpp
+++ b/libs/hwui/tests/unit/MeshStateTests.cpp
@@ -24,7 +24,7 @@
using namespace android::uirenderer;
using namespace testing;
-RENDERTHREAD_TEST(MeshState, genOrUpdate) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(MeshState, genOrUpdate) {
debug::ScopedReplaceDriver<debug::MockGlesDriver> driverRef;
auto& mockGlDriver = driverRef.get();
EXPECT_CALL(mockGlDriver, glGenBuffers_(_, _)).WillOnce(SetArgPointee<1>(35));
@@ -33,4 +33,4 @@
GLuint buffer = 0;
renderThread.renderState().meshState().genOrUpdateMeshBuffer(&buffer, 10, nullptr, GL_DYNAMIC_DRAW);
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
index b7950aa..6cd595a 100644
--- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
+++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
@@ -30,7 +30,7 @@
EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000));
}
-RENDERTHREAD_TEST(OffscreenBuffer, construct) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, construct) {
OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u);
EXPECT_EQ(49u, layer.viewportWidth);
EXPECT_EQ(149u, layer.viewportHeight);
@@ -41,7 +41,7 @@
EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
}
-RENDERTHREAD_TEST(OffscreenBuffer, getTextureCoordinates) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, getTextureCoordinates) {
OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
EXPECT_EQ(Rect(0, 1, 1, 0),
layerAligned.getTextureCoordinates());
@@ -51,7 +51,7 @@
layerUnaligned.getTextureCoordinates());
}
-RENDERTHREAD_TEST(OffscreenBuffer, dirty) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, dirty) {
OffscreenBuffer buffer(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
buffer.dirty(Rect(-100, -100, 100, 100));
EXPECT_EQ(android::Rect(100, 100), buffer.region.getBounds());
@@ -65,7 +65,7 @@
<< "pool must read size from Properties";
}
-RENDERTHREAD_TEST(OffscreenBufferPool, getPutClear) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClear) {
OffscreenBufferPool pool;
auto layer = pool.get(renderThread.renderState(), 100u, 200u);
@@ -88,7 +88,7 @@
EXPECT_EQ(0u, pool.getCount());
}
-RENDERTHREAD_TEST(OffscreenBufferPool, resize) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resize) {
OffscreenBufferPool pool;
auto layer = pool.get(renderThread.renderState(), 64u, 64u);
@@ -123,7 +123,7 @@
pool.putOrDelete(layer2);
}
-RENDERTHREAD_TEST(OffscreenBufferPool, putAndDestroy) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, putAndDestroy) {
OffscreenBufferPool pool;
// layer too big to return to the pool
// Note: this relies on the fact that the pool won't reject based on max texture size
@@ -133,7 +133,7 @@
EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead)
}
-RENDERTHREAD_TEST(OffscreenBufferPool, clear) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, clear) {
EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
OffscreenBufferPool pool;
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index 4a73383..124f5fa 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -47,7 +47,13 @@
opValidator(*(dl->getOps()[0]));
}
-TEST(RecordingCanvas, emptyPlayback) {
+// The RecordingCanvas is only ever used by the OpenGL RenderPipeline and never when Skia is in use.
+// Thus, even though many of these test are not directly dependent on the current RenderPipeline, we
+// set them all to be OPENGL_PIPELINE_TESTs in case the underlying code in RecordingCanvas ever
+// changes to require the use of the OPENGL_PIPELINE. Currently the textureLayer test is the only
+// test that requires being an OPENGL_PIPELINE_TEST.
+
+OPENGL_PIPELINE_TEST(RecordingCanvas, emptyPlayback) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.restore();
@@ -55,7 +61,7 @@
playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
}
-TEST(RecordingCanvas, clipRect) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, clipRect) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect);
@@ -71,7 +77,7 @@
<< "Clip should be serialized once";
}
-TEST(RecordingCanvas, emptyClipRect) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, emptyClipRect) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect);
@@ -82,7 +88,7 @@
ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected.";
}
-TEST(RecordingCanvas, emptyPaintRejection) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, emptyPaintRejection) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkPaint emptyPaint;
emptyPaint.setColor(Color::Transparent);
@@ -103,7 +109,7 @@
EXPECT_EQ(0u, dl->getOps().size()) << "Op should be rejected";
}
-TEST(RecordingCanvas, drawArc) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawArc) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint());
canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint());
@@ -119,7 +125,7 @@
EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds);
}
-TEST(RecordingCanvas, drawLines) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawLines) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time
@@ -136,7 +142,7 @@
<< "unmapped bounds must be size of line, and not outset for stroke width";
}
-TEST(RecordingCanvas, drawRect) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawRect) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(10, 20, 90, 180, SkPaint());
});
@@ -148,7 +154,7 @@
EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
}
-TEST(RecordingCanvas, drawRoundRect) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawRoundRect) {
// Round case - stays rounded
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint());
@@ -165,7 +171,7 @@
<< "Non-rounded rects should be converted";
}
-TEST(RecordingCanvas, drawGlyphs) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
@@ -186,7 +192,7 @@
ASSERT_EQ(1, count);
}
-TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
@@ -218,7 +224,7 @@
EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
}
-TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
@@ -248,7 +254,7 @@
ASSERT_EQ(3, count);
}
-TEST(RecordingCanvas, drawColor) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawColor) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
});
@@ -260,7 +266,7 @@
EXPECT_TRUE(op.unmappedBounds.isEmpty()) << "Expect undefined recorded bounds";
}
-TEST(RecordingCanvas, backgroundAndImage) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, backgroundAndImage) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
SkPaint paint;
@@ -312,7 +318,7 @@
ASSERT_EQ(2, count);
}
-RENDERTHREAD_TEST(RecordingCanvas, textureLayer) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(RecordingCanvas, textureLayer) {
auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100,
SkMatrix::MakeTrans(5, 5));
@@ -327,7 +333,7 @@
});
}
-TEST(RecordingCanvas, saveLayer_simple) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simple) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer);
canvas.drawRect(10, 20, 190, 180, SkPaint());
@@ -361,7 +367,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayer_rounding) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rounding) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10.25f, 10.75f, 89.25f, 89.75f, 128, SaveFlags::ClipToLayer);
canvas.drawRect(20, 20, 80, 80, SkPaint());
@@ -391,7 +397,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayer_missingRestore) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_missingRestore) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
canvas.drawRect(0, 0, 200, 200, SkPaint());
@@ -406,7 +412,7 @@
EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
}
-TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
canvas.drawRect(10, 20, 190, 180, SkPaint());
@@ -438,7 +444,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayer_addClipFlag) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_addClipFlag) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(10, 20, 190, 180, SkClipOp::kIntersect);
@@ -457,7 +463,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayer_viewportCrop) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_viewportCrop) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
// shouldn't matter, since saveLayer will clip to its bounds
canvas.clipRect(-1000, -1000, 1000, 1000, SkClipOp::kReplace);
@@ -481,7 +487,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.translate(100, 100);
@@ -507,7 +513,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayer_rotateClipped) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateClipped) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.translate(100, 100);
@@ -545,7 +551,7 @@
EXPECT_EQ(3, count);
}
-TEST(RecordingCanvas, saveLayer_rejectBegin) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rejectBegin) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.translate(0, -20); // avoid identity case
@@ -560,7 +566,7 @@
ASSERT_EQ(0u, dl->getOps().size()) << "Begin/Rect/End should all be rejected.";
}
-TEST(RecordingCanvas, drawRenderNode_rejection) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_rejection) {
auto child = TestUtils::createNode(50, 50, 150, 150,
[](RenderProperties& props, Canvas& canvas) {
SkPaint paint;
@@ -575,7 +581,7 @@
ASSERT_TRUE(dl->isEmpty());
}
-TEST(RecordingCanvas, drawRenderNode_projection) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_projection) {
sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
[](RenderProperties& props, Canvas& canvas) {
SkPaint paint;
@@ -618,7 +624,7 @@
}
}
-TEST(RecordingCanvas, firstClipWillReplace) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, firstClipWillReplace) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
// since no explicit clip set on canvas, this should be the one observed on op:
@@ -635,7 +641,7 @@
EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
}
-TEST(RecordingCanvas, replaceClipIntersectWithRoot) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, replaceClipIntersectWithRoot) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
canvas.save(SaveFlags::MatrixClip);
canvas.clipRect(-10, -10, 110, 110, SkClipOp::kReplace);
@@ -648,7 +654,7 @@
EXPECT_TRUE(dl->getOps()[0]->localClip->intersectWithRoot);
}
-TEST(RecordingCanvas, insertReorderBarrier) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.insertReorderBarrier(true);
@@ -669,7 +675,7 @@
EXPECT_TRUE(chunks[1].reorderChildren);
}
-TEST(RecordingCanvas, insertReorderBarrier_clip) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier_clip) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
// first chunk: no recorded clip
canvas.insertReorderBarrier(true);
@@ -699,7 +705,7 @@
EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect);
}
-TEST(RecordingCanvas, refPaint) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, refPaint) {
SkPaint paint;
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
@@ -727,7 +733,7 @@
EXPECT_NE(&paint, ops[2]->paint);
}
-TEST(RecordingCanvas, refBitmap) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmap) {
sk_sp<Bitmap> bitmap(TestUtils::createBitmap(100, 100));
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
canvas.drawBitmap(*bitmap, 0, 0, nullptr);
@@ -736,7 +742,7 @@
EXPECT_EQ(1u, bitmaps.size());
}
-TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
SkPaint paint;
@@ -755,7 +761,7 @@
EXPECT_EQ(1u, bitmaps.size());
}
-TEST(RecordingCanvas, refBitmapInShader_composeShader) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_composeShader) {
sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
SkPaint paint;
@@ -785,7 +791,7 @@
EXPECT_EQ(1u, bitmaps.size());
}
-TEST(RecordingCanvas, drawText) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawText) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
Paint paint;
paint.setAntiAlias(true);
@@ -807,7 +813,7 @@
ASSERT_EQ(1, count);
}
-TEST(RecordingCanvas, drawTextInHighContrast) {
+OPENGL_PIPELINE_TEST(RecordingCanvas, drawTextInHighContrast) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.setHighContrastText(true);
Paint paint;
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 331a6ac..ab8e4e1 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -137,10 +137,11 @@
RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) {
VectorDrawable::Group* group = new VectorDrawable::Group();
- VectorDrawableRoot* vectorDrawable = new VectorDrawableRoot(group);
+ sp<VectorDrawableRoot> vectorDrawable(new VectorDrawableRoot(group));
+
auto rootNode = TestUtils::createNode(0, 0, 200, 400,
[&](RenderProperties& props, Canvas& canvas) {
- canvas.drawVectorDrawable(vectorDrawable);
+ canvas.drawVectorDrawable(vectorDrawable.get());
});
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
@@ -164,7 +165,5 @@
EXPECT_FALSE(info.layerUpdateQueue->entries().empty());
EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode);
EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
-
- delete vectorDrawable;
canvasContext->destroy(nullptr);
}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 95f9974..0ac09ac 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -28,7 +28,7 @@
* Verify that we get the same culling bounds for text for (1) drawing glyphs
* directly to a Canvas or (2) going through a SkPicture as an intermediate step.
*/
-TEST(SkiaCanvasProxy, drawGlyphsViaPicture) {
+OPENGL_PIPELINE_TEST(SkiaCanvasProxy, drawGlyphsViaPicture) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
// setup test variables
SkPaint paint;
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 899758a..8f6fc8b 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -118,7 +118,7 @@
}
};
-RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 494585a..0b8c2a9 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -36,7 +36,7 @@
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
-RENDERTHREAD_TEST(SkiaPipeline, renderFrame) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -55,7 +55,7 @@
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
-RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
auto halfGreenNode = TestUtils::createSkiaNode(0, 0, 2, 2,
[](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
SkPaint greenPaint;
@@ -80,7 +80,7 @@
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
}
-RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
auto redNode = TestUtils::createSkiaNode(0, 0, 2, 2,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -101,7 +101,7 @@
ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
}
-RENDERTHREAD_TEST(SkiaPipeline, renderLayer) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) {
auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -144,7 +144,7 @@
blueNode->setLayerSurface(sk_sp<SkSurface>());
}
-RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) {
ScopedProperty<bool> prop(Properties::debugOverdraw, true);
auto whiteNode = TestUtils::createSkiaNode(0, 0, 1, 1,
@@ -218,7 +218,7 @@
};
}
-RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) {
class DeferTestCanvas : public SkCanvas {
public:
DeferTestCanvas() : SkCanvas(800, 600) {}
@@ -284,7 +284,7 @@
EXPECT_EQ(4, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_TEST(SkiaPipeline, clipped) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 200;
class ClippedTestCanvas : public SkCanvas {
@@ -315,7 +315,7 @@
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_TEST(SkiaPipeline, clip_replace) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) {
static const int CANVAS_WIDTH = 50;
static const int CANVAS_HEIGHT = 50;
class ClipReplaceTestCanvas : public SkCanvas {
diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
index e7171c8..92d9d3d 100644
--- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
+++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
@@ -73,7 +73,7 @@
}
-RENDERTHREAD_TEST(RenderNodeDrawable, renderPropClipping) {
+TEST(RenderNodeDrawable, renderPropClipping) {
testProperty([](RenderProperties& properties) {
properties.setClipToBounds(true);
properties.setClipBounds(android::uirenderer::Rect(10, 20, 300, 400));
@@ -83,7 +83,7 @@
});
}
-RENDERTHREAD_TEST(RenderNodeDrawable, renderPropRevealClip) {
+TEST(RenderNodeDrawable, renderPropRevealClip) {
testProperty([](RenderProperties& properties) {
properties.mutableRevealClip().set(true, 50, 50, 25);
}, [](const SkCanvas& canvas) {
@@ -98,7 +98,7 @@
});
}
-RENDERTHREAD_TEST(RenderNodeDrawable, renderPropOutlineClip) {
+TEST(RenderNodeDrawable, renderPropOutlineClip) {
testProperty([](RenderProperties& properties) {
properties.mutableOutline().setShouldClip(true);
properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
@@ -114,7 +114,7 @@
});
}
-RENDERTHREAD_TEST(RenderNodeDrawable, renderPropTransform) {
+TEST(RenderNodeDrawable, renderPropTransform) {
testProperty([](RenderProperties& properties) {
properties.setLeftTopRightBottom(10, 10, 110, 110);
diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
index 0d26df2..8312bda 100644
--- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
+++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
@@ -26,7 +26,7 @@
using namespace android;
using namespace android::uirenderer;
-RENDERTHREAD_TEST(TextDropShadowCache, addRemove) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) {
SkPaint paint;
paint.setTextSize(20);
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 16847c1..eaec493 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -387,7 +387,6 @@
* @see BrowserRoot#EXTRA_RECENT
* @see BrowserRoot#EXTRA_OFFLINE
* @see BrowserRoot#EXTRA_SUGGESTED
- * @see BrowserRoot#EXTRA_SUGGESTION_KEYWORDS
*/
public abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
int clientUid, @Nullable Bundle rootHints);
@@ -550,7 +549,6 @@
* @see MediaBrowserService.BrowserRoot#EXTRA_RECENT
* @see MediaBrowserService.BrowserRoot#EXTRA_OFFLINE
* @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTED
- * @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTION_KEYWORDS
*/
public final Bundle getBrowserRootHints() {
if (mCurConnection == null) {
@@ -818,7 +816,6 @@
*
* @see #EXTRA_OFFLINE
* @see #EXTRA_SUGGESTED
- * @see #EXTRA_SUGGESTION_KEYWORDS
*/
public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
@@ -836,7 +833,6 @@
*
* @see #EXTRA_RECENT
* @see #EXTRA_SUGGESTED
- * @see #EXTRA_SUGGESTION_KEYWORDS
*/
public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
@@ -855,31 +851,9 @@
*
* @see #EXTRA_RECENT
* @see #EXTRA_OFFLINE
- * @see #EXTRA_SUGGESTION_KEYWORDS
*/
public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
- /**
- * The lookup key for a string that indicates specific keywords which will be considered
- * when the browser service suggests media items.
- *
- * <p>When creating a media browser for a given media browser service, this key can be
- * supplied as a root hint together with {@link #EXTRA_SUGGESTED} for retrieving suggested
- * media items related with the keywords. The list of media items passed in
- * {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
- * is considered ordered by relevance, first being the top suggestion.
- * If the media browser service can provide such media items, the implementation must return
- * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
- *
- * <p>The root hint may contain multiple keys.
- *
- * @see #EXTRA_RECENT
- * @see #EXTRA_OFFLINE
- * @see #EXTRA_SUGGESTED
- */
- public static final String EXTRA_SUGGESTION_KEYWORDS
- = "android.service.media.extra.SUGGESTION_KEYWORDS";
-
final private String mRootId;
final private Bundle mExtras;
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index e1657c7..f8f4f2a 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -29,6 +29,7 @@
import android.util.Slog;
import android.util.TypedValue;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.GridLayout;
import android.widget.TextClock;
import android.widget.TextView;
@@ -48,6 +49,7 @@
private TextClock mDateView;
private TextClock mClockView;
private TextView mOwnerInfo;
+ private ViewGroup mClockContainer;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@@ -105,6 +107,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mClockContainer = (ViewGroup) findViewById(R.id.keyguard_clock_container);
mAlarmStatusView = (TextView) findViewById(R.id.alarm_status);
mDateView = (TextClock) findViewById(R.id.date_view);
mClockView = (TextClock) findViewById(R.id.clock_view);
@@ -167,6 +170,11 @@
}
}
+ public int getClockBottom() {
+ return mClockView.getBottom() +
+ ((MarginLayoutParams) mClockView.getLayoutParams()).bottomMargin;
+ }
+
public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
if (info == null) {
return "";
@@ -260,4 +268,15 @@
cacheKey = key;
}
}
+
+ public void setDark(boolean dark) {
+ final int N = mClockContainer.getChildCount();
+ for (int i = 0; i < N; i++) {
+ View child = mClockContainer.getChildAt(i);
+ if (child == mClockView) {
+ continue;
+ }
+ child.setAlpha(dark ? 0 : 1);
+ }
+ }
}
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 5c00985..cfb990e 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -105,10 +105,10 @@
<!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40] -->
<string-array name="bluetooth_a2dp_codec_titles">
- <item>Default</item>
+ <item>Use System Selection (Default)</item>
<item>SBC</item>
<item>aptX</item>
- <item>aptX-HD</item>
+ <item>aptX HD</item>
<item>LDAC</item>
</string-array>
@@ -123,16 +123,16 @@
<!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40]-->
<string-array name="bluetooth_a2dp_codec_summaries" >
- <item>Default</item>
+ <item>Use System Selection (Default)</item>
<item>SBC</item>
<item>aptX</item>
- <item>aptX-HD</item>
+ <item>aptX HD</item>
<item>LDAC</item>
</string-array>
<!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40] -->
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item>Default</item>
+ <item>Use System Selection (Default)</item>
<item>44.1 kHz</item>
<item>48.0 kHz</item>
<item>88.2 kHz</item>
@@ -150,7 +150,7 @@
<!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40]-->
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries" >
- <item>Default</item>
+ <item>Use System Selection (Default)</item>
<item>44.1 kHz</item>
<item>48.0 kHz</item>
<item>88.2 kHz</item>
@@ -159,7 +159,7 @@
<!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40] -->
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item>Default</item>
+ <item>Use System Selection (Default)</item>
<item>16 bits/sample</item>
<item>24 bits/sample</item>
<item>32 bits/sample</item>
@@ -175,7 +175,7 @@
<!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40]-->
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries" >
- <item>Default</item>
+ <item>Use System Selection (Default)</item>
<item>16 bits/sample</item>
<item>24 bits/sample</item>
<item>32 bits/sample</item>
@@ -183,7 +183,7 @@
<!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40] -->
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item>Default</item>
+ <item>Use System Selection (Default)</item>
<item>Mono</item>
<item>Stereo</item>
</string-array>
@@ -197,16 +197,16 @@
<!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40]-->
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries" >
- <item>Default</item>
+ <item>Use System Selection (Default)</item>
<item>Mono</item>
<item>Stereo</item>
</string-array>
- <!-- Titles for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70] -->
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item>Sound quality preferred (990kbps/909kbps)</item>
- <item>Standard (660kbps/606kbps)</item>
- <item>Connection preferred (330kbps/303kbps)</item>
+ <item>Optimize for Audio Quality (990kbps/909kbps)</item>
+ <item>Balanced Audio And Connection Quality (660kbps/606kbps)</item>
+ <item>Optimize for Connection Quality (330kbps/303kbps)</item>
</string-array>
<!-- Values for Bluetooth Audio Codec LDAC Playback Quaility selection preference. -->
@@ -216,11 +216,11 @@
<item>1002</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70]-->
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries" >
- <item>Sound quality preferred (990kbps/909kbps)</item>
- <item>Standard (660kbps/606kbps)</item>
- <item>Connection preferred (330kbps/303kbps)</item>
+ <item>Optimize for Audio Quality</item>
+ <item>Balanced Audio And Connection Quality</item>
+ <item>Optimize for Connection Quality</item>
</string-array>
<!-- Titles for logd limit size selection preference. [CHAR LIMIT=14] -->
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 93bd5dc..a1b2bdf 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -430,28 +430,31 @@
<!-- UI debug setting: Select Bluetooth Audio Codec -->
<string name="bluetooth_select_a2dp_codec_type">Bluetooth Audio Codec</string>
- <!-- UI debug setting: Select Preferred Bluetooth A2DP Codec -->
- <string name="bluetooth_select_a2dp_codec_type_dialog_title">Select Preferred Bluetooth A2DP Codec</string>
+ <!-- UI debug setting: Select Bluetooth Audio Codec -->
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title">Select Bluetooth Audio Codec</string>
<!-- UI debug setting: Select Bluetooth Audio Sample Rate -->
<string name="bluetooth_select_a2dp_codec_sample_rate">Bluetooth Audio Sample Rate</string>
- <!-- UI debug setting: Select Preferred Bluetooth A2DP Codec Sample Rate -->
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title">Select Preferred Bluetooth A2DP Codec Sample Rate</string>
+ <!-- UI debug setting: Select Bluetooth Audio Codec: Sample Rate -->
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title">Select Bluetooth Audio Codec:\u000ASample Rate</string>
<!-- UI debug setting: Select Bluetooth Audio Bits Per Sample -->
<string name="bluetooth_select_a2dp_codec_bits_per_sample">Bluetooth Audio Bits Per Sample</string>
- <!-- UI debug setting: Select Preferred Bluetooth A2DP Codec Bits Per Sample -->
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title">Select Preferred Bluetooth A2DP Codec Bits Per Sample</string>
+ <!-- UI debug setting: Select Bluetooth Audio Codec: Bits Per Sample -->
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title">Select Bluetooth Audio Codec:\u000ABits Per Sample</string>
<!-- UI debug setting: Select Bluetooth Audio Channel Mode -->
<string name="bluetooth_select_a2dp_codec_channel_mode">Bluetooth Audio Channel Mode</string>
- <!-- UI debug setting: Select Preferred Bluetooth A2DP Codec Channel Mode -->
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title">Select Preferred Bluetooth A2DP Codec Channel Mode</string>
+ <!-- UI debug setting: Select Bluetooth Audio Codec: Channel Mode -->
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title">Select Bluetooth Audio Codec:\u000AChannel Mode</string>
<!-- UI debug setting: Select Bluetooth Audio LDAC Playback Quality -->
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality">Bluetooth Audio LDAC Playback Quality</string>
- <!-- UI debug setting: Select Preferred Bluetooth A2DP Codec LDAC Playback Quality -->
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title">Select Preferred Bluetooth A2DP Codec LDAC Playback Quality</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality">Bluetooth Audio LDAC Codec: Playback Quality</string>
+ <!-- UI debug setting: Select Bluetooth Audio LDAC Codec: LDAC Playback Quality -->
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title">Select Bluetooth Audio LDAC Codec:\u000APlayback Quality</string>
+
+ <!-- [CHAR LIMIT=NONE] Label for displaying Bluetooth Audio Codec Parameters while streaming -->
+ <string name="bluetooth_select_a2dp_codec_streaming_label">Streaming: <xliff:g id="streaming_parameter">%1$s</xliff:g></string>
<!-- setting Checkbox summary whether to show options for wireless display certification -->
<string name="wifi_display_certification_summary">Show options for wireless display certification</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index fcff305..9bb3c36 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -28,6 +28,8 @@
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.R;
+
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -147,6 +149,26 @@
enabledServicesBuilder.toString(), userId);
}
+ /**
+ * Get the name of the service that should be toggled by the accessibility shortcut. Use
+ * an OEM-configurable default if the setting has never been set.
+ *
+ * @param context A valid context
+ * @param userId The user whose settings should be checked
+ *
+ * @return The component name, flattened to a string, of the target service.
+ */
+ public static String getShortcutTargetServiceComponentNameString(
+ Context context, int userId) {
+ final String currentShortcutServiceId = Settings.Secure.getStringForUser(
+ context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ userId);
+ if (currentShortcutServiceId != null) {
+ return currentShortcutServiceId;
+ }
+ return context.getString(R.string.config_defaultAccessibilityService);
+ }
+
private static Set<ComponentName> getInstalledServices(Context context) {
final Set<ComponentName> installedServices = new HashSet<>();
installedServices.clear();
diff --git a/packages/SystemUI/res/drawable/ic_qs_nfc_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_nfc_disabled.xml
new file mode 100644
index 0000000..558f3d0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_nfc_disabled.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M4 20h16V4H4v16z" />
+ <path
+ android:fillColor="#4DFFFFFF"
+ android:pathData="M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1 .9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0
+18H4V4h16v16zM18 6h-5c-1.1 0-2 .9-2 2v2.28c-.6 .35 -1 .98-1 1.72 0 1.1 .9 2 2
+2s2-.9 2-2c0-.74-.4-1.38-1-1.72V8h3v8H8V8h2V6H6v12h12V6z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_nfc_enabled.xml b/packages/SystemUI/res/drawable/ic_qs_nfc_enabled.xml
new file mode 100644
index 0000000..becb18a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_nfc_enabled.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M4 20h16V4H4v16z" />
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1 .9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0
+18H4V4h16v16zM18 6h-5c-1.1 0-2 .9-2 2v2.28c-.6 .35 -1 .98-1 1.72 0 1.1 .9 2 2
+2s2-.9 2-2c0-.74-.4-1.38-1-1.72V8h3v8H8V8h2V6H6v12h12V6z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml b/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
new file mode 100644
index 0000000..a85beb8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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">
+ <solid android:color="#61FFFFFF" />
+ <corners android:radius="8dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 78d211f..6ffdbcb 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -75,6 +75,9 @@
<!-- Height of a large notification in the status bar -->
<dimen name="notification_max_height">284dp</dimen>
+ <!-- Height of an ambient notification on ambient display -->
+ <dimen name="notification_ambient_height">400dp</dimen>
+
<!-- Height of a heads up notification in the status bar for legacy custom views -->
<dimen name="notification_max_heads_up_height_legacy">128dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml
index 94ffdb1..0b9836ff 100644
--- a/packages/SystemUI/res/values/dimens_grid.xml
+++ b/packages/SystemUI/res/values/dimens_grid.xml
@@ -21,5 +21,6 @@
<dimen name="recents_grid_padding_task_view">20dp</dimen>
<dimen name="recents_grid_task_view_header_height">44dp</dimen>
<dimen name="recents_grid_task_view_header_button_padding">8dp</dimen>
+ <dimen name="recents_grid_task_view_focused_frame_thickness">8dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 69515fa..a17430a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -760,6 +760,12 @@
<string name="quick_settings_work_mode_label">Work mode</string>
<!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
<string name="quick_settings_night_display_label">Night Light</string>
+ <!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_nfc_label">NFC</string>
+ <!-- QuickSettings: NFC (off) [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_nfc_off">NFC is disabled</string>
+ <!-- QuickSettings: NFC (on) [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_nfc_on">NFC is enabled</string>
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">No recent items</string>
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index d98bb23..8141b28 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -326,7 +326,7 @@
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (DEBUG) Log.d(TAG, "up/cancel");
- finishExpanding(ev.getActionMasked() == MotionEvent.ACTION_CANCEL,
+ finishExpanding(ev.getActionMasked() == MotionEvent.ACTION_CANCEL /* forceAbort */,
getCurrentVelocity());
clearView();
break;
@@ -587,6 +587,9 @@
mFlingAnimationUtils.apply(mScaleAnimation, currentHeight, targetHeight, velocity);
mScaleAnimation.start();
} else {
+ if (targetHeight != currentHeight) {
+ mScaler.setHeight(targetHeight);
+ }
mCallback.setUserExpandedChild(mResizedView, nowExpanded);
mCallback.setUserLockedChild(mResizedView, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index e081b53..00d2298 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -37,7 +37,7 @@
interface Callback {
default void onNewNotifications() {}
- default void onBuzzBeepBlinked() {}
+ default void onNotificationHeadsUp() {}
default void onNotificationLight(boolean on) {}
default void onPowerSaveChanged(boolean active) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 84b22ea..db5a392 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -309,7 +309,7 @@
private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
@Override
- public void onBuzzBeepBlinked() {
+ public void onNotificationHeadsUp() {
onNotification();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
new file mode 100644
index 0000000..967c922
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2016, The Android Open Source Project
+ * Contributed by the Paranoid Android 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.qs.tiles;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.nfc.NfcAdapter;
+import android.provider.Settings;
+import android.widget.Switch;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+
+/** Quick settings tile: Enable/Disable NFC **/
+public class NfcTile extends QSTile<QSTile.BooleanState> {
+
+ private NfcAdapter mAdapter;
+
+ private boolean mListening;
+
+ public NfcTile(Host host) {
+ super(host);
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ mListening = listening;
+ if (mListening) {
+ mContext.registerReceiver(mNfcReceiver,
+ new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED));
+ if (mAdapter == null) {
+ try {
+ mAdapter = NfcAdapter.getNfcAdapter(mContext);
+ } catch (UnsupportedOperationException e) {
+ mAdapter = null;
+ }
+ }
+ } else {
+ mContext.unregisterReceiver(mNfcReceiver);
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC);
+ }
+
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return new Intent(Settings.ACTION_NFC_SETTINGS);
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mAdapter == null) return;
+ MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
+ if (!mAdapter.isEnabled()) {
+ mAdapter.enable();
+ } else {
+ mAdapter.disable();
+ }
+ }
+
+ @Override
+ protected void handleSecondaryClick() {
+ handleClick();
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.quick_settings_nfc_label);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ final Drawable mEnable = mContext.getDrawable(R.drawable.ic_qs_nfc_enabled);
+ final Drawable mDisable = mContext.getDrawable(R.drawable.ic_qs_nfc_disabled);
+ state.value = mAdapter == null ? false : mAdapter.isEnabled();
+ state.label = mContext.getString(R.string.quick_settings_nfc_label);
+ state.icon = new DrawableIcon(state.value ? mEnable : mDisable);
+ state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
+ = Switch.class.getName();
+ state.contentDescription = state.label;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.QS_NFC;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext.getString(R.string.quick_settings_nfc_on);
+ } else {
+ return mContext.getString(R.string.quick_settings_nfc_off);
+ }
+ }
+
+ private BroadcastReceiver mNfcReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ refreshState();
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 914035b..a7f6b70 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -50,7 +50,7 @@
/**
* Returns the task to focus given the current launch state.
*/
- public int getInitialFocusTaskIndex(int numTasks) {
+ public int getInitialFocusTaskIndex(int numTasks, boolean useGridLayout) {
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (launchedFromApp) {
@@ -66,6 +66,11 @@
return numTasks - 1;
}
+ if (useGridLayout) {
+ // If coming from another app to the grid layout, focus the front most task
+ return numTasks - 1;
+ }
+
// If coming from another app, focus the next task
return Math.max(0, numTasks - 2);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 0a56eac..cf6357b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -23,6 +23,7 @@
import static android.view.View.MeasureSpec;
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -60,6 +61,7 @@
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
+import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
import com.android.systemui.recents.misc.DozeTrigger;
import com.android.systemui.recents.misc.ForegroundThread;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -131,6 +133,11 @@
loader.loadTasks(mContext, plan, launchOpts);
}
}
+
+ @Override
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+ EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot));
+ }
}
protected static RecentsTaskLoadPlan sInstanceLoadPlan;
@@ -738,8 +745,12 @@
Task toTask = new Task();
TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask,
windowOverrideRect);
- Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
- mThumbTransitionBitmapCache);
+ // When using a grid layout, the header is already visible on screen at the target
+ // location, making it unnecessary to draw it in the transition thumbnail.
+ Bitmap thumbnail = stackView.useGridLayout()
+ ? mThumbTransitionBitmapCache.createAshmemBitmap()
+ : drawThumbnailTransitionBitmap(toTask, toTransform,
+ mThumbTransitionBitmapCache);
if (thumbnail != null) {
RectF toTaskRect = toTransform.rect;
return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
@@ -770,7 +781,6 @@
// Get the transform for the running task
stackView.updateLayoutAlgorithm(true /* boundScroll */);
stackView.updateToInitialState();
- boolean isInSplitScreen = Recents.getSystemServices().hasDockedTask();
stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
return mTmpTransform;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
new file mode 100644
index 0000000..07c3b3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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 android.app.ActivityManager.TaskSnapshot;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Sent when a task snapshot has changed.
+ */
+public class TaskSnapshotChangedEvent extends EventBus.Event {
+
+ public final int taskId;
+ public final TaskSnapshot taskSnapshot;
+
+ public TaskSnapshotChangedEvent(int taskId, TaskSnapshot taskSnapshot) {
+ this.taskId = taskId;
+ this.taskSnapshot = taskSnapshot;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 3587b89..a2b86d1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -24,7 +24,9 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityManager;
@@ -148,6 +150,7 @@
*/
public abstract static class TaskStackListener {
public void onTaskStackChanged() { }
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
public void onActivityPinned() { }
public void onPinnedActivityRestartAttempt() { }
public void onPinnedStackAnimationEnded() { }
@@ -202,6 +205,12 @@
public void onTaskProfileLocked(int taskId, int userId) {
mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
}
+
+ @Override
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
+ throws RemoteException {
+ mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
+ }
};
/**
@@ -591,17 +600,17 @@
/** Returns the top task thumbnail for the given task id */
public ThumbnailData getTaskThumbnail(int taskId) {
if (mAm == null) return null;
- ThumbnailData thumbnailData = new ThumbnailData();
// If we are mocking, then just return a dummy thumbnail
if (RecentsDebugFlags.Static.EnableMockTasks) {
+ ThumbnailData thumbnailData = new ThumbnailData();
thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth,
mDummyThumbnailHeight, Bitmap.Config.ARGB_8888);
thumbnailData.thumbnail.eraseColor(0xff333333);
return thumbnailData;
}
- getThumbnail(taskId, thumbnailData);
+ ThumbnailData thumbnailData = getThumbnail(taskId);
if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
thumbnailData.thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -621,11 +630,12 @@
/**
* Returns a task thumbnail from the activity manager
*/
- public void getThumbnail(int taskId, ThumbnailData thumbnailDataOut) {
+ public @NonNull ThumbnailData getThumbnail(int taskId) {
if (mAm == null) {
- return;
+ return new ThumbnailData();
}
+ final ThumbnailData thumbnailData;
if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
ActivityManager.TaskSnapshot snapshot = null;
try {
@@ -634,16 +644,14 @@
Log.w(TAG, "Failed to retrieve snapshot", e);
}
if (snapshot != null) {
- thumbnailDataOut.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
- thumbnailDataOut.orientation = snapshot.getOrientation();
- thumbnailDataOut.insets.set(snapshot.getContentInsets());
+ thumbnailData = ThumbnailData.createFromTaskSnapshot(snapshot);
} else {
- thumbnailDataOut.thumbnail = null;
+ return new ThumbnailData();
}
} else {
ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId);
if (taskThumbnail == null) {
- return;
+ return new ThumbnailData();
}
Bitmap thumbnail = taskThumbnail.mainThumbnail;
@@ -658,10 +666,12 @@
} catch (IOException e) {
}
}
- thumbnailDataOut.thumbnail = thumbnail;
- thumbnailDataOut.orientation = taskThumbnail.thumbnailInfo.screenOrientation;
- thumbnailDataOut.insets.setEmpty();
+ thumbnailData = new ThumbnailData();
+ thumbnailData.thumbnail = thumbnail;
+ thumbnailData.orientation = taskThumbnail.thumbnailInfo.screenOrientation;
+ thumbnailData.insets.setEmpty();
}
+ return thumbnailData;
}
/**
@@ -1172,12 +1182,13 @@
private final class H extends Handler {
private static final int ON_TASK_STACK_CHANGED = 1;
- private static final int ON_ACTIVITY_PINNED = 2;
- private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 3;
- private static final int ON_PINNED_STACK_ANIMATION_ENDED = 4;
- private static final int ON_ACTIVITY_FORCED_RESIZABLE = 5;
- private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 6;
- private static final int ON_TASK_PROFILE_LOCKED = 7;
+ private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
+ private static final int ON_ACTIVITY_PINNED = 3;
+ private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
+ private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
+ private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
+ private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
+ private static final int ON_TASK_PROFILE_LOCKED = 8;
@Override
public void handleMessage(Message msg) {
@@ -1188,6 +1199,13 @@
}
break;
}
+ case ON_TASK_SNAPSHOT_CHANGED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
+ (TaskSnapshot) msg.obj);
+ }
+ break;
+ }
case ON_ACTIVITY_PINNED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onActivityPinned();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 178cb9f..9b25ef8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -247,6 +247,9 @@
*/
public static class DockState implements DropTarget {
+ public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
+ public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
+
// The rotation to apply to the hint text
@Retention(RetentionPolicy.SOURCE)
@IntDef({HORIZONTAL, VERTICAL})
@@ -319,7 +322,8 @@
private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
int hintTextResId) {
dockAreaAlpha = areaAlpha;
- dockAreaOverlay = new ColorDrawable(0xFFffffff);
+ dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
+ ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
dockAreaOverlay.setAlpha(0);
hintTextAlpha = hintAlpha;
hintTextOrientation = hintOrientation;
@@ -435,7 +439,7 @@
* @param createMode used to pass to ActivityManager to dock the task
* @param touchArea the area in which touch will initiate this dock state
* @param dockArea the visible dock area
- * @param expandedTouchDockArea the areain which touch will continue to dock after entering
+ * @param expandedTouchDockArea the area in which touch will continue to dock after entering
* the initial touch area. This is also the new dock area to
* draw.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
index 18735ac..09a3712 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents.model;
+import android.app.ActivityManager.TaskSnapshot;
import android.graphics.Bitmap;
import android.graphics.Rect;
@@ -23,7 +24,17 @@
* Data for a single thumbnail.
*/
public class ThumbnailData {
+
+ // TODO: Make these final once the non-snapshot path is removed.
public Bitmap thumbnail;
public int orientation;
public final Rect insets = new Rect();
+
+ public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
+ ThumbnailData out = new ThumbnailData();
+ out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
+ out.insets.set(snapshot.getContentInsets());
+ out.orientation = snapshot.getOrientation();
+ return out;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index ed86d4c..a2ee4c5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -105,6 +105,7 @@
private static final Interpolator ENTER_WHILE_DOCKING_INTERPOLATOR =
Interpolators.LINEAR_OUT_SLOW_IN;
+ private final int mEnterAndExitFromHomeTranslationOffset;
private TaskStackView mStackView;
private TaskViewTransform mTmpTransform = new TaskViewTransform();
@@ -113,6 +114,8 @@
public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
mStackView = stackView;
+ mEnterAndExitFromHomeTranslationOffset = Recents.getConfiguration().isGridEnabled
+ ? 0 : DOUBLE_FRAME_OFFSET_MS;
}
/**
@@ -260,7 +263,7 @@
AnimationProps taskAnimation = new AnimationProps()
.setInitialPlayTime(AnimationProps.BOUNDS,
Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) *
- DOUBLE_FRAME_OFFSET_MS)
+ mEnterAndExitFromHomeTranslationOffset)
.setStartDelay(AnimationProps.ALPHA,
Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) *
FRAME_OFFSET_MS)
@@ -321,7 +324,7 @@
AnimationProps taskAnimation;
if (animated) {
int delay = Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS , taskIndexFromFront) *
- DOUBLE_FRAME_OFFSET_MS;
+ mEnterAndExitFromHomeTranslationOffset;
taskAnimation = new AnimationProps()
.setStartDelay(AnimationProps.BOUNDS, delay)
.setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION)
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index a58896a..4fa7ecb5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -291,6 +291,9 @@
@ViewDebug.ExportedProperty(category="recents")
private int mStackBottomOffset;
+ /** The height, in pixels, of each task view's title bar. */
+ private int mTitleBarHeight;
+
// The paths defining the motion of the tasks when the stack is focused and unfocused
private Path mUnfocusedCurve;
private Path mFocusedCurve;
@@ -403,6 +406,14 @@
mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
mFreeformStackGap =
res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
+ mTitleBarHeight = getDimensionForDevice(mContext,
+ R.dimen.recents_task_view_header_height,
+ R.dimen.recents_task_view_header_height,
+ R.dimen.recents_task_view_header_height,
+ R.dimen.recents_task_view_header_height_tablet_land,
+ R.dimen.recents_task_view_header_height,
+ R.dimen.recents_task_view_header_height_tablet_land,
+ R.dimen.recents_grid_task_view_header_height);
}
/**
@@ -903,12 +914,17 @@
* Transforms the given {@param transformOut} to the screen coordinates, overriding the current
* window rectangle with {@param windowOverrideRect} if non-null.
*/
- public TaskViewTransform transformToScreenCoordinates(TaskViewTransform transformOut,
+ TaskViewTransform transformToScreenCoordinates(TaskViewTransform transformOut,
Rect windowOverrideRect) {
Rect windowRect = windowOverrideRect != null
? windowOverrideRect
: Recents.getSystemServices().getWindowRect();
transformOut.rect.offset(windowRect.left, windowRect.top);
+ if (useGridLayout()) {
+ // Draw the thumbnail a little lower to perfectly coincide with the view we are
+ // transitioning to, where the header bar has already been drawn.
+ transformOut.rect.offset(0, mTitleBarHeight);
+ }
return transformOut;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index b7686ce..fc2550a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -70,6 +70,7 @@
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
@@ -93,6 +94,7 @@
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.grid.GridTaskView;
+import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -206,6 +208,10 @@
private int mLastWidth;
private int mLastHeight;
+ // We keep track of the task view focused by user interaction and draw a frame around it in the
+ // grid layout.
+ private TaskViewFocusFrame mTaskViewFocusFrame;
+
// A convenience update listener to request updating clipping of tasks
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -265,6 +271,14 @@
mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
mDisplayRect = ssp.getDisplayRect();
+ // Create a frame to draw around the focused task view
+ if (Recents.getConfiguration().isGridEnabled) {
+ mTaskViewFocusFrame = new TaskViewFocusFrame(mContext, this,
+ mLayoutAlgorithm.mTaskGridLayoutAlgorithm);
+ addView(mTaskViewFocusFrame);
+ getViewTreeObserver().addOnGlobalFocusChangeListener(mTaskViewFocusFrame);
+ }
+
int taskBarDismissDozeDelaySeconds = getResources().getInteger(
R.integer.recents_task_bar_dismiss_delay_seconds);
mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() {
@@ -878,7 +892,7 @@
*
* @return whether or not the stack will scroll as a part of this focus change
*/
- private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
+ public boolean setFocusedTask(int taskIndex, boolean scrollToTask,
final boolean requestViewFocus) {
return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0);
}
@@ -888,7 +902,7 @@
*
* @return whether or not the stack will scroll as a part of this focus change
*/
- private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
+ public boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
boolean requestViewFocus, int timerIndicatorDuration) {
// Find the next task to focus
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
@@ -940,6 +954,10 @@
newFocusedTaskView.setFocusedState(true, requestViewFocus);
}
}
+ // Any time a task view gets the focus, we move the focus frame around it.
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(getChildViewForTask(newFocusedTask));
+ }
}
return willScroll;
}
@@ -1005,20 +1023,28 @@
float stackScroll = mStackScroller.getStackScroll();
ArrayList<Task> tasks = mStack.getStackTasks();
int taskCount = tasks.size();
- if (forward) {
- // Walk backwards and focus the next task smaller than the current stack scroll
- for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) {
- float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
- if (Float.compare(taskP, stackScroll) <= 0) {
- break;
- }
- }
+ if (useGridLayout()) {
+ // For the grid layout, we directly set focus to the most recently used task
+ // no matter we're moving forwards or backwards.
+ newIndex = taskCount - 1;
} else {
- // Walk forwards and focus the next task larger than the current stack scroll
- for (newIndex = 0; newIndex < taskCount; newIndex++) {
- float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
- if (Float.compare(taskP, stackScroll) >= 0) {
- break;
+ // For the grid layout we pick a proper task to focus, according to the current
+ // stack scroll.
+ if (forward) {
+ // Walk backwards and focus the next task smaller than the current stack scroll
+ for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) {
+ float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
+ if (Float.compare(taskP, stackScroll) <= 0) {
+ break;
+ }
+ }
+ } else {
+ // Walk forwards and focus the next task larger than the current stack scroll
+ for (newIndex = 0; newIndex < taskCount; newIndex++) {
+ float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
+ if (Float.compare(taskP, stackScroll) >= 0) {
+ break;
+ }
}
}
}
@@ -1037,20 +1063,23 @@
/**
* Resets the focused task.
*/
- void resetFocusedTask(Task task) {
+ public void resetFocusedTask(Task task) {
if (task != null) {
TaskView tv = getChildViewForTask(task);
if (tv != null) {
tv.setFocusedState(false, false /* requestViewFocus */);
}
}
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(null);
+ }
mFocusedTask = null;
}
/**
* Returns the focused task.
*/
- Task getFocusedTask() {
+ public Task getFocusedTask() {
return mFocusedTask;
}
@@ -1253,6 +1282,9 @@
for (int i = 0; i < taskViewCount; i++) {
measureTaskView(mTmpTaskViews.get(i));
}
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.measure();
+ }
setMeasuredDimension(width, height);
mLastWidth = width;
@@ -1287,6 +1319,9 @@
for (int i = 0; i < taskViewCount; i++) {
layoutTaskView(changed, mTmpTaskViews.get(i));
}
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.layout();
+ }
if (changed) {
if (mStackScroller.isScrollOutOfBounds()) {
@@ -1339,10 +1374,19 @@
// until after the enter-animation
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
- if (focusedTaskIndex != -1) {
- setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
- false /* requestViewFocus */);
+
+ // We set the initial focused task view iff the following conditions are satisfied:
+ // 1. Recents is showing task views in stack layout.
+ // 2. Recents is launched with ALT + TAB.
+ boolean setFocusOnFirstLayout = !useGridLayout() ||
+ Recents.getConfiguration().getLaunchState().launchedWithAltTab;
+ if (setFocusOnFirstLayout) {
+ int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount(),
+ useGridLayout());
+ if (focusedTaskIndex != -1) {
+ setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
+ false /* requestViewFocus */);
+ }
}
updateStackActionButtonVisibility();
}
@@ -1443,6 +1487,11 @@
// Remove the task from the ignored set
removeIgnoreTask(removedTask);
+ // Resize the grid layout task view focus frame
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.resize();
+ }
+
// If requested, relayout with the given animation
if (animation != null) {
updateLayoutAlgorithm(true /* boundScroll */);
@@ -1740,10 +1789,18 @@
int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
Interpolators.FAST_OUT_SLOW_IN));
+
+ // Dismiss the grid task view focus frame
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(null);
+ }
}
public final void onBusEvent(DismissFocusedTaskViewEvent event) {
if (mFocusedTask != null) {
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(null);
+ }
TaskView tv = getChildViewForTask(mFocusedTask);
if (tv != null) {
tv.dismissTask();
@@ -2073,6 +2130,12 @@
mResetToInitialStateWhenResized = true;
}
+ public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+ if (!event.visible && mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(null);
+ }
+ }
+
public void reloadOnConfigurationChange() {
mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext());
mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 33fa3b0..5817e92 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -342,8 +342,9 @@
mSv.invalidate();
}
- // Reset the focused task after the user has scrolled
- if (!mSv.mTouchExplorationEnabled) {
+ // Reset the focused task after the user has scrolled, but we have no scrolling
+ // in grid layout and therefore we don't want to reset the focus there.
+ if (!mSv.mTouchExplorationEnabled && !mSv.useGridLayout()) {
mSv.resetFocusedTask(mSv.getFocusedTask());
}
} else if (mActiveTaskView == 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 5f37349..e41a718 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -22,7 +22,6 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Outline;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index e3bf1df..bae5daa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -35,6 +35,9 @@
import android.view.ViewDebug;
import com.android.systemui.R;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.EventBus.Event;
+import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.ThumbnailData;
@@ -347,6 +350,7 @@
mBgFillPaint.setColor(t.colorBackground);
}
mLockedPaint.setColor(t.colorPrimary);
+ EventBus.getDefault().register(this);
}
/**
@@ -361,6 +365,14 @@
void unbindFromTask() {
mTask = null;
setThumbnail(null);
+ EventBus.getDefault().unregister(this);
+ }
+
+ public final void onBusEvent(TaskSnapshotChangedEvent event) {
+ if (mTask == null || event.taskId != mTask.key.id || event.taskSnapshot == null) {
+ return;
+ }
+ setThumbnail(ThumbnailData.createFromTaskSnapshot(event.taskSnapshot));
}
public void dump(String prefix, PrintWriter writer) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index 6fc4ad7..70536b1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -51,6 +51,9 @@
private float mAppAspectRatio;
private Rect mSystemInsets = new Rect();
+ /** The thickness of the focused task view frame. */
+ private int mFocusedFrameThickness;
+
/**
* When the amount of tasks is determined, the size and position of every task view can be
* decided. Each instance of TaskGridRectInfo store the task view information for a certain
@@ -137,6 +140,9 @@
public void reloadOnConfigurationChange(Context context) {
Resources res = context.getResources();
mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
+ mFocusedFrameThickness = res.getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_focused_frame_thickness);
+
mTaskGridRect = new Rect();
mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
@@ -159,6 +165,10 @@
*/
public TaskViewTransform getTransform(int taskIndex, int taskCount,
TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
+ if (taskCount == 0) {
+ transformOut.reset();
+ return transformOut;
+ }
TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
mTaskGridRect.set(gridInfo.size);
@@ -174,7 +184,7 @@
// We also need to invert the index in order to display the most recent tasks first.
int taskLayoutIndex = taskCount - taskIndex - 1;
- boolean isTaskViewVisible = (taskLayoutIndex < MAX_LAYOUT_TASK_COUNT);
+ boolean isTaskViewVisible = taskLayoutIndex < MAX_LAYOUT_TASK_COUNT;
// Fill out the transform
transformOut.scale = 1f;
@@ -223,7 +233,18 @@
return buttonRect;
}
+ public void updateTaskGridRect(int taskCount) {
+ if (taskCount > 0) {
+ TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
+ mTaskGridRect.set(gridInfo.size);
+ }
+ }
+
public Rect getTaskGridRect() {
return mTaskGridRect;
}
+
+ public int getFocusFrameThickness() {
+ return mFocusedFrameThickness;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
new file mode 100644
index 0000000..86ed583
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 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.views.grid;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
+import com.android.systemui.R;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskStackView;
+
+public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener {
+
+ private TaskStackView mSv;
+ private TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
+ public TaskViewFocusFrame(Context context) {
+ this(context, null);
+ }
+
+ public TaskViewFocusFrame(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskViewFocusFrame(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TaskViewFocusFrame(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setBackground(mContext.getDrawable(
+ R.drawable.recents_grid_task_view_focus_frame_background));
+ setFocusable(false);
+ hide();
+ }
+
+ public TaskViewFocusFrame(Context context, TaskStackView stackView,
+ TaskGridLayoutAlgorithm taskGridLayoutAlgorithm) {
+ this(context);
+ mSv = stackView;
+ mTaskGridLayoutAlgorithm = taskGridLayoutAlgorithm;
+ }
+
+ /**
+ * Measure the width and height of the focus frame according to the current grid task view size.
+ */
+ public void measure() {
+ int thickness = mTaskGridLayoutAlgorithm.getFocusFrameThickness();
+ Rect rect = mTaskGridLayoutAlgorithm.getTaskGridRect();
+ measure(
+ MeasureSpec.makeMeasureSpec(rect.width() + thickness * 2, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(rect.height() + thickness * 2, MeasureSpec.EXACTLY));
+ }
+
+ /**
+ * Layout the focus frame with its size.
+ */
+ public void layout() {
+ layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ /**
+ * Update the current size of grid task view and the focus frame.
+ */
+ public void resize() {
+ if (mSv.useGridLayout()) {
+ mTaskGridLayoutAlgorithm.updateTaskGridRect(mSv.getStack().getTaskCount());
+ measure();
+ requestLayout();
+ }
+ }
+
+ /**
+ * Move the task view focus frame to surround the newly focused view. If it's {@code null} or
+ * it's not an instance of GridTaskView, we hide the focus frame.
+ * @param newFocus The newly focused view.
+ */
+ public void moveGridTaskViewFocus(View newFocus) {
+ if (mSv.useGridLayout()) {
+ // The frame only shows up in the grid layout. It shouldn't show up in the stack
+ // layout including when we're in the split screen.
+ if (newFocus instanceof GridTaskView) {
+ // If the focus goes to a GridTaskView, we show the frame and layout it.
+ int[] location = new int[2];
+ newFocus.getLocationInWindow(location);
+ int thickness = mTaskGridLayoutAlgorithm.getFocusFrameThickness();
+ setTranslationX(location[0] - thickness);
+ setTranslationY(location[1] - thickness);
+ show();
+ } else {
+ // If focus goes to other views, we hide the frame.
+ hide();
+ }
+ }
+ }
+
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ if (!mSv.useGridLayout()) {
+ return;
+ }
+ if (newFocus == null) {
+ // We're going to touch mode, unset the focus.
+ moveGridTaskViewFocus(null);
+ return;
+ }
+ if (oldFocus == null) {
+ // We're returning from touch mode, set the focus to the previously focused task.
+ final TaskStack stack = mSv.getStack();
+ final int taskCount = stack.getTaskCount();
+ final int k = stack.indexOfStackTask(mSv.getFocusedTask());
+ final int taskIndexToFocus = k == -1 ? (taskCount - 1) : (k % taskCount);
+ mSv.setFocusedTask(taskIndexToFocus, false, true);
+ }
+ }
+
+ private void show() {
+ setAlpha(1f);
+ }
+
+ private void hide() {
+ setAlpha(0f);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index d1ab96d..b5358bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -182,6 +182,7 @@
private int mStartTint;
private int mOverrideTint;
private float mOverrideAmount;
+ private boolean mShadowHidden;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -210,6 +211,7 @@
super.onFinishInflate();
mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal);
mFakeShadow = (FakeShadowView) findViewById(R.id.fake_shadow);
+ mShadowHidden = mFakeShadow.getVisibility() != VISIBLE;
mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
@@ -249,7 +251,7 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result;
- if (mDimmed && !isTouchExplorationEnabled()) {
+ if (mDimmed && !isTouchExplorationEnabled() && isInteractive()) {
boolean wasActivated = mActivated;
result = handleTouchEventDimmed(event);
if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
@@ -261,6 +263,13 @@
return result;
}
+ /**
+ * @return whether this view is interactive and can be double tapped
+ */
+ protected boolean isInteractive() {
+ return true;
+ }
+
@Override
public void drawableHotspotChanged(float x, float y) {
if (!mDimmed){
@@ -1020,9 +1029,13 @@
@Override
public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
int outlineTranslation) {
- mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ()
- + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd,
- outlineTranslation);
+ boolean hiddenBefore = mShadowHidden;
+ mShadowHidden = shadowIntensity == 0.0f;
+ if (!mShadowHidden || !hiddenBefore) {
+ mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ()
+ + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd,
+ outlineTranslation);
+ }
}
public int getBackgroundColorWithoutTint() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 561b469..faf143e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -122,7 +122,7 @@
public abstract class BaseStatusBar extends SystemUI implements
CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
- ExpandableNotificationRow.OnExpandClickListener, OnGutsClosedListener {
+ ExpandableNotificationRow.OnExpandClickListener {
public static final String TAG = "StatusBar";
public static final boolean DEBUG = false;
public static final boolean MULTIUSER_DEBUG = false;
@@ -1041,7 +1041,12 @@
PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
row.setTag(sbn.getPackageName());
final NotificationGuts guts = row.getGuts();
- guts.setClosedListener(this);
+ guts.setClosedListener((NotificationGuts g) -> {
+ if (!row.isRemoved()) {
+ mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
+ }
+ mNotificationGutsExposed = null;
+ });
final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
@@ -1127,6 +1132,11 @@
// Post to ensure the the guts are properly laid out.
guts.post(new Runnable() {
public void run() {
+ if (row.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to "
+ + "window");
+ return;
+ }
dismissPopups(-1 /* x */, -1 /* y */, false /* resetGear */,
false /* animate */);
guts.setVisibility(View.VISIBLE);
@@ -1149,7 +1159,7 @@
guts.setExposed(true /* exposed */,
mState == StatusBarState.KEYGUARD /* needsFalsingProtection */);
row.closeRemoteInput();
- mStackScroller.onHeightChanged(null, true /* needsAnimation */);
+ mStackScroller.onHeightChanged(row, true /* needsAnimation */);
mNotificationGutsExposed = guts;
}
});
@@ -1183,12 +1193,6 @@
}
@Override
- public void onGutsClosed(NotificationGuts guts) {
- mStackScroller.onHeightChanged(null, true /* needsAnimation */);
- mNotificationGutsExposed = null;
- }
-
- @Override
public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
int msg = MSG_SHOW_RECENT_APPS;
mHandler.removeMessages(msg);
@@ -1520,6 +1524,7 @@
final RemoteViews bigContentView = entry.cachedBigContentView;
final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
final RemoteViews publicContentView = entry.cachedPublicContentView;
+ final RemoteViews ambientContentView = entry.cachedAmbientContentView;
if (contentView == null) {
Log.v(TAG, "no contentView for: " + sbn.getNotification());
@@ -1600,6 +1605,7 @@
View bigContentViewLocal = null;
View headsUpContentViewLocal = null;
View publicViewLocal = null;
+ View ambientViewLocal = null;
try {
contentViewLocal = contentView.apply(
sbn.getPackageContext(mContext),
@@ -1622,6 +1628,11 @@
sbn.getPackageContext(mContext),
contentContainerPublic, mOnClickHandler);
}
+ if (ambientContentView != null) {
+ ambientViewLocal = ambientContentView.apply(
+ sbn.getPackageContext(mContext),
+ contentContainer, mOnClickHandler);
+ }
if (contentViewLocal != null) {
contentViewLocal.setIsRootNamespace(true);
@@ -1639,6 +1650,11 @@
publicViewLocal.setIsRootNamespace(true);
contentContainerPublic.setContractedChild(publicViewLocal);
}
+
+ if (ambientViewLocal != null) {
+ ambientViewLocal.setIsRootNamespace(true);
+ contentContainer.setAmbientChild(ambientViewLocal);
+ }
}
catch (RuntimeException e) {
final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
@@ -2135,6 +2151,7 @@
row.setOnKeyguard(false);
row.setSystemExpanded(visibleNotifications == 0 && !childNotification);
}
+ entry.row.setShowAmbient(isDozing());
int userId = entry.notification.getUserId();
boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
entry.notification) && !entry.row.isRemoved();
@@ -2172,6 +2189,10 @@
mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3);
}
+ public boolean isDozing() {
+ return false;
+ }
+
public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
}
@@ -2384,7 +2405,7 @@
Log.d(TAG, "failed to query dream manager", e);
}
- if (!inUse) {
+ if (!inUse && !isDozing()) {
if (DEBUG) {
Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 173a110..93c48f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -74,6 +74,7 @@
private int mMaxHeadsUpHeight;
private int mNotificationMinHeight;
private int mNotificationMaxHeight;
+ private int mNotificationAmbientHeight;
private int mIncreasedPaddingBetweenElements;
/** Does this row contain layouts that can adapt to row expansion */
@@ -197,6 +198,7 @@
private float mContentTransformationAmount;
private boolean mIconsVisible = true;
private boolean mAboveShelf;
+ private boolean mShowAmbient;
private boolean mIsLastChild;
private Runnable mOnDismissRunnable;
@@ -326,7 +328,8 @@
!= com.android.internal.R.id.status_bar_latest_event_content;
int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy
: mMaxHeadsUpHeight;
- layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
+ layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
+ mNotificationAmbientHeight);
}
public StatusBarNotification getStatusBarNotification() {
@@ -954,6 +957,7 @@
mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
+ mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
mMaxHeadsUpHeightLegacy = getFontScaledHeight(
R.dimen.notification_max_heads_up_height_legacy);
mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
@@ -1353,6 +1357,8 @@
return mGuts.getHeight();
} else if ((isChildInGroup() && !isGroupExpanded())) {
return mPrivateLayout.getMinHeight();
+ } else if (mShowAmbient) {
+ return getAmbientHeight();
} else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
return getMinHeight();
} else if (mIsSummaryWithChildren && !mOnKeyguard) {
@@ -1683,6 +1689,13 @@
return showingLayout.getMinHeight();
}
+ private int getAmbientHeight() {
+ NotificationContentView showingLayout = getShowingLayout();
+ return showingLayout.getAmbientChild() != null
+ ? showingLayout.getAmbientChild().getHeight()
+ : getCollapsedHeight();
+ }
+
@Override
public int getCollapsedHeight() {
if (mIsSummaryWithChildren && !mShowingPublic) {
@@ -1879,6 +1892,13 @@
return mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf);
}
+ public void setShowAmbient(boolean showAmbient) {
+ if (showAmbient != mShowAmbient) {
+ mShowAmbient = showAmbient;
+ notifyHeightChanged(false /* needsAnimation */);
+ }
+ }
+
public void setAboveShelf(boolean aboveShelf) {
mAboveShelf = aboveShelf;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index ad6a5db..b45cde8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -31,6 +31,7 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.HybridNotificationView;
@@ -52,6 +53,7 @@
private static final int VISIBLE_TYPE_EXPANDED = 1;
private static final int VISIBLE_TYPE_HEADSUP = 2;
private static final int VISIBLE_TYPE_SINGLELINE = 3;
+ private static final int VISIBLE_TYPE_AMBIENT = 4;
public static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
@@ -62,6 +64,7 @@
private View mExpandedChild;
private View mHeadsUpChild;
private HybridNotificationView mSingleLineView;
+ private View mAmbientChild;
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
@@ -69,6 +72,7 @@
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
private NotificationViewWrapper mHeadsUpWrapper;
+ private NotificationViewWrapper mAmbientWrapper;
private HybridGroupManager mHybridGroupManager;
private int mClipTopAmount;
private int mContentHeight;
@@ -81,6 +85,7 @@
private int mSmallHeight;
private int mHeadsUpHeight;
private int mNotificationMaxHeight;
+ private int mNotificationAmbientHeight;
private StatusBarNotification mStatusBarNotification;
private NotificationGroupManager mGroupManager;
private RemoteInputController mRemoteInputController;
@@ -136,10 +141,12 @@
reset();
}
- public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight) {
+ public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
+ int ambientHeight) {
mSmallHeight = smallHeight;
mHeadsUpHeight = headsUpMaxHeight;
mNotificationMaxHeight = maxHeight;
+ mNotificationAmbientHeight = ambientHeight;
}
@Override
@@ -215,6 +222,17 @@
MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST));
maxChildHeight = Math.max(maxChildHeight, mSingleLineView.getMeasuredHeight());
}
+ if (mAmbientChild != null) {
+ int size = Math.min(maxSize, mNotificationAmbientHeight);
+ ViewGroup.LayoutParams layoutParams = mAmbientChild.getLayoutParams();
+ if (layoutParams.height >= 0) {
+ // An actual height is set
+ size = Math.min(size, layoutParams.height);
+ }
+ mAmbientChild.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+ maxChildHeight = Math.max(maxChildHeight, mAmbientChild.getMeasuredHeight());
+ }
int ownHeight = Math.min(maxChildHeight, maxSize);
setMeasuredDimension(width, ownHeight);
}
@@ -293,10 +311,6 @@
}
public void reset() {
- if (mContractedChild != null) {
- mContractedChild.animate().cancel();
- removeView(mContractedChild);
- }
mPreviousExpandedRemoteInputIntent = null;
if (mExpandedRemoteInput != null) {
mExpandedRemoteInput.onNotificationUpdateOrReset();
@@ -327,7 +341,6 @@
removeView(mHeadsUpChild);
mHeadsUpRemoteInput = null;
}
- mContractedChild = null;
mExpandedChild = null;
mHeadsUpChild = null;
}
@@ -344,6 +357,10 @@
return mHeadsUpChild;
}
+ public View getAmbientChild() {
+ return mAmbientChild;
+ }
+
public void setContractedChild(View child) {
if (mContractedChild != null) {
mContractedChild.animate().cancel();
@@ -378,6 +395,17 @@
mContainingNotification);
}
+ public void setAmbientChild(View child) {
+ if (mAmbientChild != null) {
+ mAmbientChild.animate().cancel();
+ removeView(mAmbientChild);
+ }
+ addView(child);
+ mAmbientChild = child;
+ mAmbientWrapper = NotificationViewWrapper.wrap(getContext(), child,
+ mContainingNotification);
+ }
+
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -452,6 +480,11 @@
com.android.internal.R.dimen.notification_action_list_height);
}
+ if (isVisibleOrTransitioning(VISIBLE_TYPE_AMBIENT)) {
+ return mContractedChild.getHeight() + mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_action_list_height);
+ }
+
// Transition between heads-up & expanded, or pinned.
if (mHeadsUpChild != null && mExpandedChild != null) {
boolean transitioningBetweenHunAndExpanded =
@@ -656,39 +689,26 @@
}
private void forceUpdateVisibilities() {
- boolean contractedVisible = mVisibleType == VISIBLE_TYPE_CONTRACTED
- || mTransformationStartVisibleType == VISIBLE_TYPE_CONTRACTED;
- boolean expandedVisible = mVisibleType == VISIBLE_TYPE_EXPANDED
- || mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED;
- boolean headsUpVisible = mVisibleType == VISIBLE_TYPE_HEADSUP
- || mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP;
- boolean singleLineVisible = mVisibleType == VISIBLE_TYPE_SINGLELINE
- || mTransformationStartVisibleType == VISIBLE_TYPE_SINGLELINE;
- if (!contractedVisible) {
- mContractedChild.setVisibility(View.INVISIBLE);
+ forceUpdateVisibility(VISIBLE_TYPE_CONTRACTED, mContractedChild, mContractedWrapper);
+ forceUpdateVisibility(VISIBLE_TYPE_EXPANDED, mExpandedChild, mExpandedWrapper);
+ forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
+ forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
+ forceUpdateVisibility(VISIBLE_TYPE_AMBIENT, mAmbientChild, mAmbientWrapper);
+ // forceUpdateVisibilities cancels outstanding animations without updating the
+ // mAnimationStartVisibleType. Do so here instead.
+ mAnimationStartVisibleType = UNDEFINED;
+ }
+
+ private void forceUpdateVisibility(int type, View view, TransformableView wrapper) {
+ if (view == null) {
+ return;
+ }
+ boolean visible = mVisibleType == type
+ || mTransformationStartVisibleType == type;
+ if (!visible) {
+ view.setVisibility(INVISIBLE);
} else {
- mContractedWrapper.setVisible(true);
- }
- if (mExpandedChild != null) {
- if (!expandedVisible) {
- mExpandedChild.setVisibility(View.INVISIBLE);
- } else {
- mExpandedWrapper.setVisible(true);
- }
- }
- if (mHeadsUpChild != null) {
- if (!headsUpVisible) {
- mHeadsUpChild.setVisibility(View.INVISIBLE);
- } else {
- mHeadsUpWrapper.setVisible(true);
- }
- }
- if (mSingleLineView != null) {
- if (!singleLineVisible) {
- mSingleLineView.setVisibility(View.INVISIBLE);
- } else {
- mSingleLineView.setVisible(true);
- }
+ wrapper.setVisible(true);
}
}
@@ -722,19 +742,25 @@
}
private void updateViewVisibilities(int visibleType) {
- boolean contractedVisible = visibleType == VISIBLE_TYPE_CONTRACTED;
- mContractedWrapper.setVisible(contractedVisible);
- if (mExpandedChild != null) {
- boolean expandedVisible = visibleType == VISIBLE_TYPE_EXPANDED;
- mExpandedWrapper.setVisible(expandedVisible);
- }
- if (mHeadsUpChild != null) {
- boolean headsUpVisible = visibleType == VISIBLE_TYPE_HEADSUP;
- mHeadsUpWrapper.setVisible(headsUpVisible);
- }
- if (mSingleLineView != null) {
- boolean singleLineVisible = visibleType == VISIBLE_TYPE_SINGLELINE;
- mSingleLineView.setVisible(singleLineVisible);
+ updateViewVisibility(visibleType, VISIBLE_TYPE_CONTRACTED,
+ mContractedChild, mContractedWrapper);
+ updateViewVisibility(visibleType, VISIBLE_TYPE_EXPANDED,
+ mExpandedChild, mExpandedWrapper);
+ updateViewVisibility(visibleType, VISIBLE_TYPE_HEADSUP,
+ mHeadsUpChild, mHeadsUpWrapper);
+ updateViewVisibility(visibleType, VISIBLE_TYPE_SINGLELINE,
+ mSingleLineView, mSingleLineView);
+ updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT,
+ mAmbientChild, mAmbientWrapper);
+ // updateViewVisibilities cancels outstanding animations without updating the
+ // mAnimationStartVisibleType. Do so here instead.
+ mAnimationStartVisibleType = UNDEFINED;
+ }
+
+ private void updateViewVisibility(int visibleType, int type, View view,
+ TransformableView wrapper) {
+ if (view != null) {
+ wrapper.setVisible(visibleType == type);
}
}
@@ -784,6 +810,8 @@
return mHeadsUpWrapper;
case VISIBLE_TYPE_SINGLELINE:
return mSingleLineView;
+ case VISIBLE_TYPE_AMBIENT:
+ return mAmbientWrapper;
default:
return mContractedWrapper;
}
@@ -801,6 +829,8 @@
return mHeadsUpChild;
case VISIBLE_TYPE_SINGLELINE:
return mSingleLineView;
+ case VISIBLE_TYPE_AMBIENT:
+ return mAmbientChild;
default:
return mContractedChild;
}
@@ -814,6 +844,8 @@
return mHeadsUpWrapper;
case VISIBLE_TYPE_CONTRACTED:
return mContractedWrapper;
+ case VISIBLE_TYPE_AMBIENT:
+ return mAmbientWrapper;
default:
return null;
}
@@ -823,6 +855,10 @@
* @return one of the static enum types in this view, calculated form the current state
*/
public int calculateVisibleType() {
+ if (mDark && !mIsChildInGroup) {
+ // TODO: Handle notification groups
+ return VISIBLE_TYPE_AMBIENT;
+ }
if (mUserExpanding) {
int height = !mIsChildInGroup || isGroupExpanded()
|| mContainingNotification.isExpanded(true /* allowOnKeyguard */)
@@ -895,6 +931,7 @@
if (mSingleLineView != null && (mVisibleType == VISIBLE_TYPE_SINGLELINE || !dark)) {
mSingleLineView.setDark(dark, fade, delay);
}
+ selectLayout(!dark && fade /* animate */, false /* force */);
}
public void setHeadsUp(boolean headsUp) {
@@ -947,6 +984,9 @@
if (mHeadsUpChild != null) {
mHeadsUpWrapper.notifyContentUpdated(entry.notification);
}
+ if (mAmbientChild != null) {
+ mAmbientWrapper.notifyContentUpdated(entry.notification);
+ }
updateShowingLegacyBackground();
mForceSelectNextLayout = true;
setDark(mDark, false /* animate */, 0 /* delay */);
@@ -1133,6 +1173,9 @@
if (header == null && mHeadsUpChild != null) {
header = mHeadsUpWrapper.getNotificationHeader();
}
+ if (header == null && mAmbientChild != null) {
+ header = mAmbientWrapper.getNotificationHeader();
+ }
return header;
}
@@ -1200,6 +1243,11 @@
}
}
+ @VisibleForTesting
+ boolean isAnimatingVisibleType() {
+ return mAnimationStartVisibleType != UNDEFINED;
+ }
+
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
mHeadsUpAnimatingAway = headsUpAnimatingAway;
selectLayout(false /* animate */, true /* force */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index a6e730d..458daf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -67,6 +67,7 @@
public RemoteViews cachedBigContentView;
public RemoteViews cachedHeadsUpContentView;
public RemoteViews cachedPublicContentView;
+ public RemoteViews cachedAmbientContentView;
public CharSequence remoteInputText;
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
@@ -126,6 +127,8 @@
updatedNotificationBuilder.createHeadsUpContentView();
final RemoteViews newPublicNotification
= updatedNotificationBuilder.makePublicContentView();
+ final RemoteViews newAmbientNotification
+ = updatedNotificationBuilder.makeAmbientNotification();
boolean sameCustomView = Objects.equals(
notification.getNotification().extras.getBoolean(
@@ -136,11 +139,13 @@
&& compareRemoteViews(cachedBigContentView, newBigContentView)
&& compareRemoteViews(cachedHeadsUpContentView, newHeadsUpContentView)
&& compareRemoteViews(cachedPublicContentView, newPublicNotification)
+ && compareRemoteViews(cachedAmbientContentView, newAmbientNotification)
&& sameCustomView;
cachedPublicContentView = newPublicNotification;
cachedHeadsUpContentView = newHeadsUpContentView;
cachedBigContentView = newBigContentView;
cachedContentView = newContentView;
+ cachedAmbientContentView = newAmbientNotification;
} else {
final Notification.Builder builder
= Notification.Builder.recoverBuilder(ctx, notification.getNotification());
@@ -149,6 +154,7 @@
cachedBigContentView = builder.createBigContentView();
cachedHeadsUpContentView = builder.createHeadsUpContentView();
cachedPublicContentView = builder.makePublicContentView();
+ cachedAmbientContentView = builder.makeAmbientNotification();
applyInPlace = false;
}
@@ -488,20 +494,6 @@
return false;
}
- /**
- * Return whether there are any clearable notifications (that aren't errors).
- */
- public boolean hasActiveClearableNotifications() {
- for (Entry e : mSortedAndFiltered) {
- if (e.getContentView() != null) { // the view successfully inflated
- if (e.notification.isClearable()) {
- return true;
- }
- }
- }
- return false;
- }
-
// Q: What kinds of notifications should show during setup?
// A: Almost none! Only things coming from the system (package is "android") that also
// have special "kind" tags marking them as relevant for setup (see below).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index bc1b9fb..e8e9d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -67,6 +67,7 @@
private int mStatusBarState;
private float mMaxShelfEnd;
private int mRelativeOffset;
+ private boolean mInteractive;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -128,6 +129,7 @@
} else {
mViewInvertHelper.update(dark);
}
+ mShelfIcons.setAmbient(dark);
}
@Override
@@ -555,13 +557,18 @@
}
private void updateInteractiveness() {
- boolean interactive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
- setClickable(interactive);
- setFocusable(interactive);
- setImportantForAccessibility(interactive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
+ setClickable(mInteractive);
+ setFocusable(mInteractive);
+ setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
: View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
+ @Override
+ protected boolean isInteractive() {
+ return mInteractive;
+ }
+
public void setMaxShelfEnd(float maxShelfEnd) {
mMaxShelfEnd = maxShelfEnd;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index a2c2fd7..399b0d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -221,6 +221,8 @@
setContentDescription(icon.contentDescription);
if (!iconEquals) {
if (!updateDrawable(false /* no clear */)) return false;
+ // we have to clear the grayscale tag since it may have changed
+ setTag(R.id.icon_is_grayscale, null);
}
if (!levelEquals) {
setImageLevel(icon.iconLevel);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 7ca2df9..b984c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -116,8 +116,10 @@
private void resolveTemplateViews(StatusBarNotification notification) {
mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
- mPicture.setTag(ImageTransformState.ICON_TAG,
- notification.getNotification().getLargeIcon());
+ if (mPicture != null) {
+ mPicture.setTag(ImageTransformState.ICON_TAG,
+ notification.getNotification().getLargeIcon());
+ }
mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title);
mText = (TextView) mView.findViewById(com.android.internal.R.id.text);
final View progress = mView.findViewById(com.android.internal.R.id.progress);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 5047041..a4e5916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -39,6 +39,7 @@
private VisibilityLocationProvider mVisibilityLocationProvider;
private ArraySet<View> mAllowedReorderViews = new ArraySet<>();
private ArraySet<View> mAddedChildren = new ArraySet<>();
+ private boolean mPulsing;
/**
* Add a callback to invoke when reordering is allowed again.
@@ -67,8 +68,16 @@
updateReorderingAllowed();
}
+ /**
+ * @param pulsing whether we are currently pulsing for ambient display.
+ */
+ public void setPulsing(boolean pulsing) {
+ mPulsing = pulsing;
+ updateReorderingAllowed();
+ }
+
private void updateReorderingAllowed() {
- boolean reorderingAllowed = !mScreenOn || !mPanelExpanded;
+ boolean reorderingAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
boolean changed = reorderingAllowed && !mReorderingAllowed;
mReorderingAllowed = reorderingAllowed;
if (changed) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 01ffe01..b78f748 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -26,6 +26,7 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.Interpolators;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -41,7 +42,9 @@
private final Handler mHandler = new Handler();
private final ScrimController mScrimController;
+ private final Context mContext;
private final View mStackScroller;
+ private final NotificationPanelView mNotificationPanelView;
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
@@ -52,10 +55,12 @@
private float mBehindTarget;
public DozeScrimController(ScrimController scrimController, Context context,
- View stackScroller) {
+ View stackScroller, NotificationPanelView notificationPanelView) {
+ mContext = context;
mStackScroller = stackScroller;
mScrimController = scrimController;
mDozeParameters = new DozeParameters(context);
+ mNotificationPanelView = notificationPanelView;
}
public void setDozing(boolean dozing, boolean animate) {
@@ -65,10 +70,7 @@
abortAnimations();
mScrimController.setDozeBehindAlpha(1f);
mScrimController.setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? 0f : 1f);
- if (mDozeParameters.getAlwaysOn()) {
- mStackScroller.setAlpha(0f);
- mHandler.postDelayed(() -> mStackScroller.setAlpha(0f), 30);
- }
+ mNotificationPanelView.setDark(true);
} else {
cancelPulsing();
if (animate) {
@@ -83,9 +85,8 @@
mScrimController.setDozeBehindAlpha(0f);
mScrimController.setDozeInFrontAlpha(0f);
}
- if (mDozeParameters.getAlwaysOn()) {
- mStackScroller.setAlpha(1f);
- }
+ // TODO: animate
+ mNotificationPanelView.setDark(false);
}
}
@@ -123,9 +124,6 @@
if (isPulsing()) {
final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
|| mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
- if (mDozeParameters.getAlwaysOn()) {
- mStackScroller.setAlpha(1f);
- }
startScrimAnimation(true /* inFront */, 0f,
mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
@@ -291,9 +289,6 @@
@Override
public void run() {
if (DEBUG) Log.d(TAG, "Pulse out finished");
- if (mDozeParameters.getAlwaysOn()) {
- mStackScroller.setAlpha(0f);
- }
DozeLog.tracePulseFinish();
// Signal that the pulse is all finished so we can turn the screen off now.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 70beac8ea..c78ec83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -68,6 +68,8 @@
}
private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
+ private int mClockBottom;
+ private boolean mDark;
/**
* Refreshes the dimension values.
@@ -86,7 +88,8 @@
}
public void setup(int maxKeyguardNotifications, int maxPanelHeight, float expandedHeight,
- int notificationCount, int height, int keyguardStatusHeight, float emptyDragAmount) {
+ int notificationCount, int height, int keyguardStatusHeight, float emptyDragAmount,
+ int clockBottom, boolean dark) {
mMaxKeyguardNotifications = maxKeyguardNotifications;
mMaxPanelHeight = maxPanelHeight;
mExpandedHeight = expandedHeight;
@@ -94,6 +97,8 @@
mHeight = height;
mKeyguardStatusHeight = keyguardStatusHeight;
mEmptyDragAmount = emptyDragAmount;
+ mClockBottom = clockBottom;
+ mDark = dark;
}
public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) {
@@ -115,6 +120,9 @@
result.clockY,
y + getClockNotificationsPadding() + mKeyguardStatusHeight);
result.clockAlpha = getClockAlpha(result.clockScale);
+ if (mDark) {
+ result.stackScrollerPadding = mClockBottom + y;
+ }
}
private float getClockScale(int notificationPadding, int clockY, int startPadding) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 6dddf18..26b0d53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -43,7 +43,18 @@
private boolean mDockedLight;
private int mLastStatusBarMode;
private int mLastNavigationBarMode;
+
+ /**
+ * Whether the navigation bar should be light factoring in already how much alpha the scrim has
+ */
private boolean mNavigationLight;
+
+ /**
+ * Whether the flags indicate that a light status bar is requested. This doesn't factor in the
+ * scrim alpha yet.
+ */
+ private boolean mHasLightNavigationBar;
+ private boolean mScrimAlphaBelowThreshold;
private float mScrimAlpha;
private final Rect mLastFullscreenBounds = new Rect();
@@ -101,7 +112,9 @@
if ((diffVis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0
|| nbModeChanged) {
boolean last = mNavigationLight;
- mNavigationLight = isNavigationLight(newVis, navigationBarMode);
+ mHasLightNavigationBar = isLight(vis, navigationBarMode,
+ View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+ mNavigationLight = mHasLightNavigationBar && mScrimAlphaBelowThreshold;
if (mNavigationLight != last) {
updateNavigation();
}
@@ -120,12 +133,11 @@
public void setScrimAlpha(float alpha) {
mScrimAlpha = alpha;
- reevaluate();
- }
-
- private boolean isNavigationLight(int vis, int barMode) {
- return isLight(vis, barMode, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
- && mScrimAlpha < NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
+ boolean belowThresholdBefore = mScrimAlphaBelowThreshold;
+ mScrimAlphaBelowThreshold = mScrimAlpha < NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
+ if (mHasLightNavigationBar && belowThresholdBefore != mScrimAlphaBelowThreshold) {
+ reevaluate();
+ }
}
private boolean isLight(int vis, int barMode, int flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 9fb5980..c25a45c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -22,9 +22,7 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
-import android.util.Property;
import android.view.View;
-import android.view.animation.Interpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -32,7 +30,6 @@
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.stack.AnimationFilter;
import com.android.systemui.statusbar.stack.AnimationProperties;
-import com.android.systemui.statusbar.stack.HeadsUpAppearInterpolator;
import com.android.systemui.statusbar.stack.ViewState;
import java.util.HashMap;
@@ -98,6 +95,7 @@
private int mActualLayoutWidth = NO_VALUE;
private float mActualPaddingEnd = NO_VALUE;
private float mActualPaddingStart = NO_VALUE;
+ private boolean mCentered;
private boolean mChangingViewPositions;
private int mAddAnimationStartIndex = -1;
private int mCannedAnimationStartIndex = -1;
@@ -105,6 +103,7 @@
private int mIconSize;
private float mOpenedAmount = 0.0f;
private float mVisualOverflowAdaption;
+ private boolean mDisallowNextAnimation;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -165,6 +164,7 @@
}
mAddAnimationStartIndex = -1;
mCannedAnimationStartIndex = -1;
+ mDisallowNextAnimation = false;
}
@Override
@@ -310,6 +310,15 @@
numDots++;
}
}
+ if (mCentered && translationX < getLayoutEnd()) {
+ float delta = (getLayoutEnd() - translationX) / 2;
+ for (int i = 0; i < childCount; i++) {
+ View view = getChildAt(i);
+ IconState iconState = mIconStates.get(view);
+ iconState.xTranslation += delta;
+ }
+ }
+
if (isLayoutRtl()) {
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
@@ -379,6 +388,11 @@
mChangingViewPositions = changingViewPositions;
}
+ public void setAmbient(boolean ambient) {
+ mCentered = ambient;
+ mDisallowNextAnimation = true;
+ }
+
public IconState getIconState(StatusBarIconView icon) {
return mIconStates.get(icon);
}
@@ -469,7 +483,7 @@
animate = true;
}
icon.setVisibleState(visibleState);
- if (animate) {
+ if (animate && !mDisallowNextAnimation) {
animateTo(icon, animationProperties);
} else {
super.applyToView(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index d48819a..3bdd5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -211,6 +211,7 @@
private boolean mOpening;
private int mIndicationBottomPadding;
private boolean mIsFullWidth;
+ private boolean mDark;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -391,7 +392,9 @@
mNotificationStackScroller.getNotGoneChildCount(),
getHeight(),
mKeyguardStatusView.getHeight(),
- mEmptyDragAmount);
+ mEmptyDragAmount,
+ mKeyguardStatusView.getClockBottom(),
+ mDark);
mClockPositionAlgorithm.run(mClockPositionResult);
if (animate || mClockAnimator != null) {
startClockAnimation(mClockPositionResult.clockY);
@@ -2453,4 +2456,10 @@
}
}
};
+
+ public void setDark(boolean dark) {
+ mDark = dark;
+ mKeyguardStatusView.setDark(dark);
+ positionClockAndNotifications();
+ }
}
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 b338420..191718e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -74,7 +74,6 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
@@ -879,7 +878,8 @@
mHeadsUpManager.addListener(mScrimController);
mStackScroller.setScrimController(mScrimController);
mStatusBarView.setScrimController(mScrimController);
- mDozeScrimController = new DozeScrimController(mScrimController, context, mStackScroller);
+ mDozeScrimController = new DozeScrimController(mScrimController, context, mStackScroller,
+ mNotificationPanel);
// Other icons
mLocationController = new LocationControllerImpl(mContext,
@@ -1195,8 +1195,10 @@
List<ExpandableNotificationRow> children = row.getNotificationChildren();
if (row.areChildrenExpanded() && children != null) {
for (ExpandableNotificationRow childRow : children) {
- if (childRow.getVisibility() == View.VISIBLE) {
- viewsToHide.add(childRow);
+ if (mStackScroller.canChildBeDismissed(childRow)) {
+ if (childRow.getVisibility() == View.VISIBLE) {
+ viewsToHide.add(childRow);
+ }
}
}
}
@@ -1544,8 +1546,15 @@
}
List<ExpandableNotificationRow> notificationChildren =
entry.row.getNotificationChildren();
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(notificationChildren);
- for (int i = 0; i < toRemove.size(); i++) {
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow row = notificationChildren.get(i);
+ if ((row.getStatusBarNotification().getNotification().flags
+ & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ // the child is a forground service notification which we can't remove!
+ continue;
+ }
+ toRemove.add(row);
toRemove.get(i).setKeepInParent(true);
// we need to set this state earlier as otherwise we might generate some weird
// animations
@@ -1817,10 +1826,27 @@
private void updateClearAll() {
boolean showDismissView =
mState != StatusBarState.KEYGUARD &&
- mNotificationData.hasActiveClearableNotifications();
+ hasActiveClearableNotifications();
mStackScroller.updateDismissView(showDismissView);
}
+ /**
+ * Return whether there are any clearable notifications
+ */
+ private boolean hasActiveClearableNotifications() {
+ int childCount = mStackScroller.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mStackScroller.getChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ if (((ExpandableNotificationRow) child).canViewBeDismissed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void updateEmptyShadeView() {
boolean showEmptyShade =
mState != StatusBarState.KEYGUARD &&
@@ -1867,7 +1893,7 @@
if (SPEW) {
final boolean clearable = hasActiveNotifications() &&
- mNotificationData.hasActiveClearableNotifications();
+ hasActiveClearableNotifications();
Log.d(TAG, "setAreThereNotifications: N=" +
mNotificationData.getActiveNotifications().size() + " any=" +
hasActiveNotifications() + " clearable=" + clearable);
@@ -2375,6 +2401,7 @@
return getBarState() == StatusBarState.KEYGUARD;
}
+ @Override
public boolean isDozing() {
return mDozing;
}
@@ -2461,6 +2488,9 @@
}
} else {
updateNotificationRanking(null);
+ if (isHeadsUp) {
+ mDozeServiceHost.fireNotificationHeadsUp();
+ }
}
}
@@ -2860,9 +2890,6 @@
@Override // CommandQueue
public void buzzBeepBlinked() {
- if (mDozeServiceHost != null) {
- mDozeServiceHost.fireBuzzBeepBlinked();
- }
}
@Override
@@ -3492,6 +3519,9 @@
if (mSecurityController != null) {
mSecurityController.onUserSwitched(mCurrentUserId);
}
+ if (mNetworkController != null) {
+ mNetworkController.onUserSwitched(mCurrentUserId);
+ }
}
private void resetUserSetupObserver() {
@@ -4208,6 +4238,7 @@
mDozeScrimController.setDozing(mDozing &&
mFingerprintUnlockController.getMode()
!= FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, animate);
+ updateRowStates();
Trace.endSection();
}
@@ -4878,9 +4909,9 @@
}
}
- public void fireBuzzBeepBlinked() {
+ public void fireNotificationHeadsUp() {
for (Callback callback : mCallbacks) {
- callback.onBuzzBeepBlinked();
+ callback.onNotificationHeadsUp();
}
}
@@ -4924,12 +4955,14 @@
public void onPulseStarted() {
callback.onPulseStarted();
mStackScroller.setPulsing(true);
+ mVisualStabilityManager.setPulsing(true);
}
@Override
public void onPulseFinished() {
callback.onPulseFinished();
mStackScroller.setPulsing(false);
+ mVisualStabilityManager.setPulsing(false);
}
}, reason);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 227ebdf..d4cf533 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -52,6 +52,7 @@
import com.android.systemui.qs.tiles.HotspotTile;
import com.android.systemui.qs.tiles.IntentTile;
import com.android.systemui.qs.tiles.LocationTile;
+import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.UserTile;
@@ -440,6 +441,7 @@
else if (tileSpec.equals("battery")) return new BatteryTile(this);
else if (tileSpec.equals("saver")) return new DataSaverTile(this);
else if (tileSpec.equals("night")) return new NightDisplayTile(this);
+ else if (tileSpec.equals("nfc")) return new NfcTile(this);
// Intent tiles.
else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 517551d..8fcbf38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -338,13 +338,13 @@
private void setCurrentScrimAlpha(View scrim, float alpha) {
if (scrim == mScrimBehind) {
mCurrentBehindAlpha = alpha;
+ mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
} else if (scrim == mScrimInFront) {
mCurrentInFrontAlpha = alpha;
} else {
alpha = Math.max(0.0f, Math.min(1.0f, alpha));
mCurrentHeadsUpAlpha = alpha;
}
- mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
}
protected void updateScrimColor(View scrim) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 06cd769..395e8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -359,6 +359,7 @@
private boolean mInHeadsUpPinnedMode;
private boolean mHeadsUpAnimatingAway;
private int mStatusBarState;
+ private int mCachedBackgroundColor;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -445,8 +446,11 @@
+ alphaInv * Color.green(scrimColor)),
(int) (mBackgroundFadeAmount * Color.blue(mBgColor)
+ alphaInv * Color.blue(scrimColor)));
- mBackgroundPaint.setColor(color);
- invalidate();
+ if (mCachedBackgroundColor != color) {
+ mCachedBackgroundColor = color;
+ mBackgroundPaint.setColor(color);
+ invalidate();
+ }
}
private void initView(Context context) {
@@ -1879,12 +1883,16 @@
float previousIncreasedAmount = 0.0f;
int numShownItems = 0;
boolean finish = false;
+ int maxDisplayedNotifications = mAmbientState.isDark()
+ ? (mPulsing ? 1 : 0)
+ : mMaxDisplayedNotifications;
+
for (int i = 0; i < getChildCount(); i++) {
ExpandableView expandableView = (ExpandableView) getChildAt(i);
if (expandableView.getVisibility() != View.GONE
&& !expandableView.hasNoContentHeight()) {
- if (mMaxDisplayedNotifications != -1
- && numShownItems >= mMaxDisplayedNotifications) {
+ if (maxDisplayedNotifications != -1
+ && numShownItems >= maxDisplayedNotifications) {
expandableView = mShelf;
finish = true;
}
@@ -2092,9 +2100,14 @@
* Update the background bounds to the new desired bounds
*/
private void updateBackgroundBounds() {
- getLocationInWindow(mTempInt2);
- mBackgroundBounds.left = mTempInt2[0];
- mBackgroundBounds.right = mTempInt2[0] + getWidth();
+ if (mAmbientState.isPanelFullWidth()) {
+ mBackgroundBounds.left = 0;
+ mBackgroundBounds.right = getWidth();
+ } else {
+ getLocationInWindow(mTempInt2);
+ mBackgroundBounds.left = mTempInt2[0];
+ mBackgroundBounds.right = mTempInt2[0] + getWidth();
+ }
if (!mIsExpanded) {
mBackgroundBounds.top = 0;
mBackgroundBounds.bottom = 0;
@@ -3477,6 +3490,8 @@
updateBackground();
setWillNotDraw(false);
}
+ updateContentHeight();
+ notifyHeightChangeListener(mShelf);
}
private void setBackgroundFadeAmount(float fadeAmount) {
@@ -3912,6 +3927,8 @@
public void setPulsing(boolean pulsing) {
mPulsing = pulsing;
updateNotificationAnimationStates();
+ updateContentHeight();
+ notifyHeightChangeListener(mShelf);
}
public void setFadingOut(boolean fadingOut) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/notification/VisualStabilityManagerTest.java
index be6290b..76bb6c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/notification/VisualStabilityManagerTest.java
@@ -150,4 +150,28 @@
mVisualStabilityManager.onReorderingFinished();
assertEquals(mVisualStabilityManager.canReorderNotification(mRow), false);
}
+
+ @Test
+ public void testPulsing() {
+ mVisualStabilityManager.setPulsing(true);
+ assertEquals(mVisualStabilityManager.canReorderNotification(mRow), false);
+ mVisualStabilityManager.setPulsing(false);
+ assertEquals(mVisualStabilityManager.canReorderNotification(mRow), true);
+ }
+
+ @Test
+ public void testReorderingAllowedChanges_Pulsing() {
+ mVisualStabilityManager.setPulsing(true);
+ assertEquals(mVisualStabilityManager.isReorderingAllowed(), false);
+ mVisualStabilityManager.setPulsing(false);
+ assertEquals(mVisualStabilityManager.isReorderingAllowed(), true);
+ }
+
+ @Test
+ public void testCallBackCalled_Pulsing() {
+ mVisualStabilityManager.setPulsing(true);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+ mVisualStabilityManager.setPulsing(false);
+ verify(mCallback).onReorderingAllowed();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
new file mode 100644
index 0000000..3bb9f5f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationContentViewTest {
+
+ NotificationContentView mView;
+ Context mContext;
+
+ @Before
+ public void setup() {
+ ExpandableNotificationRow rowMock = mock(ExpandableNotificationRow.class);
+ when(rowMock.getIntrinsicHeight()).thenReturn(10);
+
+ mContext = InstrumentationRegistry.getTargetContext();
+ mView = new NotificationContentView(mContext, null);
+ mView.setContainingNotification(rowMock);
+ mView.setHeights(10, 20, 30, 40);
+
+ mView.setContractedChild(createViewWithHeight(10));
+ mView.setExpandedChild(createViewWithHeight(20));
+ mView.setHeadsUpChild(createViewWithHeight(30));
+ mView.setAmbientChild(createViewWithHeight(40));
+
+ mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+ }
+
+ private View createViewWithHeight(int height) {
+ View view = new View(mContext, null);
+ view.setMinimumHeight(height);
+ return view;
+ }
+
+ @Test
+ @UiThreadTest
+ public void animationStartType_getsClearedAfterUpdatingVisibilitiesWithoutAnimation() {
+ mView.setHeadsUp(true);
+ mView.setDark(true, false, 0);
+ mView.setDark(false, true, 0);
+ mView.setHeadsUpAnimatingAway(true);
+ Assert.assertFalse(mView.isAnimatingVisibleType());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
new file mode 100644
index 0000000..7d9e073
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.statusbar;
+
+import android.graphics.drawable.Icon;
+import android.os.Debug;
+import android.os.UserHandle;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StatusBarIconViewTest extends SysuiTestCase {
+
+ private StatusBarIconView mIconView;
+ private StatusBarIcon mStatusBarIcon = mock(StatusBarIcon.class);
+
+ @Before
+ public void setUp() {
+ mIconView = new StatusBarIconView(getContext(), "slot", null);
+ mStatusBarIcon = new StatusBarIcon(UserHandle.ALL, getContext().getPackageName(),
+ Icon.createWithResource(getContext(), R.drawable.ic_android), 0, 0, "");
+ }
+
+ @Test
+ public void testSetClearsGrayscale() {
+ mIconView.setTag(R.id.icon_is_grayscale, true);
+ mIconView.set(mStatusBarIcon);
+ assertNull(mIconView.getTag(R.id.icon_is_grayscale));
+ }
+
+}
\ No newline at end of file
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index dc92f56..5004940 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3191,8 +3191,97 @@
// ACTION: Clicking on any search result in Settings.
ACTION_CLICK_SETTINGS_SEARCH_RESULT = 763;
+ // ACTION: Allow Battery optimization for an app
+ APP_SPECIAL_PERMISSION_BATTERY_ALLOW = 764;
+
+ // ACTION: Deny Battery optimization for an app
+ APP_SPECIAL_PERMISSION_BATTERY_DENY = 765;
+
+ // ACTION: Enable Device Admin app
+ APP_SPECIAL_PERMISSION_ADMIN_ALLOW = 766;
+
+ // ACTION: Disable Device Admin app
+ APP_SPECIAL_PERMISSION_ADMIN_DENY = 767;
+
+ // ACTION: Allow "Do Not Disturb access" for an app
+ APP_SPECIAL_PERMISSION_DND_ALLOW = 768;
+
+ // ACTION: Deny "Do Not Disturb access" for an app
+ APP_SPECIAL_PERMISSION_DND_DENY = 769;
+
+ // ACTION: Allow "Draw over other apps" for an app
+ APP_SPECIAL_PERMISSION_APPDRAW_ALLOW = 770;
+
+ // ACTION: Deny "Draw over other apps" for an app
+ APP_SPECIAL_PERMISSION_APPDRAW_DENY = 771;
+
+ // ACTION: Allow "VR helper services" for an app
+ APP_SPECIAL_PERMISSION_VRHELPER_ALLOW = 772;
+
+ // ACTION: Deny "VR helper services" for an app
+ APP_SPECIAL_PERMISSION_VRHELPER_DENY = 773;
+
+ // ACTION: Allow "Modify system settings" for an app
+ APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_ALLOW = 774;
+
+ // ACTION: Deny "Modify system settings" for an app
+ APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_DENY = 775;
+
+ // ACTION: Allow "Notification access" for an app
+ APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW = 776;
+
+ // ACTION: Deny "Notification access" for an app
+ APP_SPECIAL_PERMISSION_NOTIVIEW_DENY = 777;
+
+ // ACTION: "Premium SMS access" for an app - "ask user" option
+ APP_SPECIAL_PERMISSION_PREMIUM_SMS_ASK = 778;
+
+ // ACTION: "Premium SMS access" for an app - "never allow" option
+ APP_SPECIAL_PERMISSION_PREMIUM_SMS_DENY = 779;
+
+ // ACTION: "Premium SMS access" for an app - "always allow" option
+ APP_SPECIAL_PERMISSION_PREMIUM_SMS_ALWAYS_ALLOW = 780;
+
+ // ACTION: Allow "Unrestricted data access" for an app
+ APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW = 781;
+
+ // ACTION: Deny "Unrestricted data access" for an app
+ APP_SPECIAL_PERMISSION_UNL_DATA_DENY = 782;
+
+ // ACTION: Allow "Usage access" for an app
+ APP_SPECIAL_PERMISSION_USAGE_VIEW_ALLOW = 783;
+
+ // ACTION: Deny "Usage access" for an app
+ APP_SPECIAL_PERMISSION_USAGE_VIEW_DENY = 784;
+
+ // OPEN: Settings > Apps > Default Apps > Default browser
+ DEFAULT_BROWSER_PICKER = 785;
+
+ // OPEN: Settings > Apps > Default Apps > Default emergency app
+ DEFAULT_EMERGENCY_APP_PICKER = 786;
+
+ // OPEN: Settings > Apps > Default Apps > Default home
+ DEFAULT_HOME_PICKER = 787;
+
+ // OPEN: Settings > Apps > Default Apps > Default phone
+ DEFAULT_PHONE_PICKER = 788;
+
+ // OPEN: Settings > Apps > Default Apps > Default sms
+ DEFAULT_SMS_PICKER = 789;
+
+ // OPEN: Settings > Apps > Default Apps > Default notification assistant
+ DEFAULT_NOTIFICATION_ASSISTANT = 790;
+
+ // OPEN: Settings > Apps > Default Apps > Warning dialog to confirm selection
+ DEFAULT_APP_PICKER_CONFIRMATION_DIALOG = 791;
+
// ---- End O Constants, all O constants go above this line ----
+ // OPEN: QS NFC tile shown
+ // ACTION: QS NFC tile tapped
+ // CATEGORY: QUICK_SETTINGS
+ QS_NFC = 800;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/services/Android.mk b/services/Android.mk
index abd1459..e760fe2 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -37,8 +37,8 @@
# The convention is to name each service module 'services.$(module_name)'
LOCAL_STATIC_JAVA_LIBRARIES := $(addprefix services.,$(services)) \
- android.hidl.base@1.0-java \
- android.hardware.biometrics.fingerprint@2.1-java
+ android.hidl.base@1.0-java-static \
+ android.hardware.biometrics.fingerprint@2.1-java-static
ifeq ($(EMMA_INSTRUMENT_FRAMEWORK),true)
LOCAL_EMMA_INSTRUMENT := true
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b34e4e4..ece5149 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -97,6 +97,7 @@
import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
+import com.android.server.policy.AccessibilityShortcutController;
import com.android.server.statusbar.StatusBarManagerInternal;
import org.xmlpull.v1.XmlPullParserException;
@@ -1489,6 +1490,7 @@
mInitialized = true;
updateLegacyCapabilitiesLocked(userState);
updateServicesLocked(userState);
+ updateAccessibilityShortcutLocked(userState);
updateWindowsForAccessibilityCallbackLocked(userState);
updateAccessibilityFocusBehaviorLocked(userState);
updateFilterKeyEventsLocked(userState);
@@ -1613,7 +1615,7 @@
somethingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState);
somethingChanged |= readDisplayMagnificationEnabledSettingLocked(userState);
somethingChanged |= readAutoclickEnabledSettingLocked(userState);
-
+ somethingChanged |= readAccessibilityShortcutSettingLocked(userState);
return somethingChanged;
}
@@ -1722,6 +1724,50 @@
}
}
+ private boolean readAccessibilityShortcutSettingLocked(UserState userState) {
+ String componentNameToEnableString = AccessibilityShortcutController
+ .getTargetServiceComponentNameString(mContext, userState.mUserId);
+ if ((componentNameToEnableString == null) || componentNameToEnableString.isEmpty()) {
+ if (userState.mServiceToEnableWithShortcut == null) {
+ return false;
+ }
+ userState.mServiceToEnableWithShortcut = null;
+ return true;
+ }
+ ComponentName componentNameToEnable =
+ ComponentName.unflattenFromString(componentNameToEnableString);
+ if (componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
+ return false;
+ }
+ userState.mServiceToEnableWithShortcut = componentNameToEnable;
+ return true;
+ }
+
+ /**
+ * Check if the service that will be enabled by the shortcut is installed. If it isn't,
+ * clear the value and the associated setting so a sideloaded service can't spoof the
+ * package name of the default service.
+ *
+ * @param userState
+ */
+ private void updateAccessibilityShortcutLocked(UserState userState) {
+ if (userState.mServiceToEnableWithShortcut == null) {
+ return;
+ }
+ boolean shortcutServiceIsInstalled = false;
+ for (int i = 0; i < userState.mInstalledServices.size(); i++) {
+ if (userState.mInstalledServices.get(i).getComponentName()
+ .equals(userState.mServiceToEnableWithShortcut)) {
+ shortcutServiceIsInstalled = true;
+ }
+ }
+ if (!shortcutServiceIsInstalled) {
+ userState.mServiceToEnableWithShortcut = null;
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", userState.mUserId);
+ }
+ }
+
private boolean canRequestAndRequestsTouchExplorationLocked(Service service) {
// Service not ready or cannot request the feature - well nothing to do.
if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) {
@@ -1895,44 +1941,63 @@
}
/**
+ * AIDL-exposed method to be called when the accessibility shortcut is enabled. Requires
+ * permission to write secure settings, since someone with that permission can enable
+ * accessibility services themselves.
+ */
+ public void performAccessibilityShortcut() {
+ if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
+ && (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException(
+ "performAccessibilityShortcut requires the WRITE_SECURE_SETTINGS permission");
+ }
+ synchronized(mLock) {
+ UserState userState = getUserStateLocked(mCurrentUserId);
+ ComponentName serviceName = userState.mServiceToEnableWithShortcut;
+ if (serviceName == null) {
+ return;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (userState.mComponentNameToServiceMap.get(serviceName) == null) {
+ enableAccessibilityServiceLocked(serviceName, mCurrentUserId);
+ } else {
+ disableAccessibilityServiceLocked(serviceName, mCurrentUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ };
+
+ /**
* Enables accessibility service specified by {@param componentName} for the {@param userId}.
*/
- public void enableAccessibilityService(ComponentName componentName, int userId) {
- synchronized(mLock) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("only SYSTEM can call enableAccessibilityService.");
- }
+ private void enableAccessibilityServiceLocked(ComponentName componentName, int userId) {
+ SettingsStringHelper settingsHelper = new SettingsStringHelper(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId);
+ settingsHelper.addService(componentName);
+ settingsHelper.writeToSettings();
- SettingsStringHelper settingsHelper = new SettingsStringHelper(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId);
- settingsHelper.addService(componentName);
- settingsHelper.writeToSettings();
-
- UserState userState = getUserStateLocked(userId);
- if (userState.mEnabledServices.add(componentName)) {
- onUserStateChangedLocked(userState);
- }
+ UserState userState = getUserStateLocked(userId);
+ if (userState.mEnabledServices.add(componentName)) {
+ onUserStateChangedLocked(userState);
}
}
/**
* Disables accessibility service specified by {@param componentName} for the {@param userId}.
*/
- public void disableAccessibilityService(ComponentName componentName, int userId) {
- synchronized(mLock) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("only SYSTEM can call disableAccessibility");
- }
+ private void disableAccessibilityServiceLocked(ComponentName componentName, int userId) {
+ SettingsStringHelper settingsHelper = new SettingsStringHelper(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId);
+ settingsHelper.deleteService(componentName);
+ settingsHelper.writeToSettings();
- SettingsStringHelper settingsHelper = new SettingsStringHelper(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId);
- settingsHelper.deleteService(componentName);
- settingsHelper.writeToSettings();
-
- UserState userState = getUserStateLocked(userId);
- if (userState.mEnabledServices.remove(componentName)) {
- onUserStateChangedLocked(userState);
- }
+ UserState userState = getUserStateLocked(userId);
+ if (userState.mEnabledServices.remove(componentName)) {
+ onUserStateChangedLocked(userState);
}
}
@@ -4307,6 +4372,8 @@
public ComponentName mServiceChangingSoftKeyboardMode;
+ public ComponentName mServiceToEnableWithShortcut;
+
public int mLastSentClientState = -1;
public int mSoftKeyboardShowMode = 0;
@@ -4439,6 +4506,9 @@
private final Uri mAccessibilitySoftKeyboardModeUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+ private final Uri mAccessibilityShortcutServiceIdUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -4467,6 +4537,8 @@
mHighTextContrastUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -4519,6 +4591,10 @@
notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode);
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilityShortcutServiceIdUri.equals(uri)) {
+ if (readAccessibilityShortcutSettingLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 87eb380..3523706 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -126,6 +126,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
OnCrossProfileWidgetProvidersChangeListener {
@@ -152,6 +153,8 @@
// Bump if the stored widgets need to be upgraded.
private static final int CURRENT_VERSION = 1;
+ private static final AtomicLong REQUEST_COUNTER = new AtomicLong();
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -771,7 +774,8 @@
LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>();
for (int i = 0; i < N; i++) {
if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) {
- // We key the updates based on time, so that the values are sorted by time.
+ // We key the updates based on request id, so that the values are sorted in the
+ // order they were received.
int M = updatesMap.size();
for (int j = 0; j < M; j++) {
outUpdates.add(updatesMap.valueAt(j));
@@ -1854,9 +1858,9 @@
// method with a wrong id. In that case, ignore the call.
return;
}
- long requestTime = SystemClock.uptimeMillis();
+ long requestId = REQUEST_COUNTER.incrementAndGet();
if (widget != null) {
- widget.updateTimes.put(viewId, requestTime);
+ widget.updateRequestIds.put(viewId, requestId);
}
if (widget == null || widget.host == null || widget.host.zombie
|| widget.host.callbacks == null || widget.provider == null
@@ -1867,7 +1871,7 @@
SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
- args.arg3 = requestTime;
+ args.arg3 = requestId;
args.argi1 = widget.appWidgetId;
args.argi2 = viewId;
@@ -1878,10 +1882,10 @@
private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
- int appWidgetId, int viewId, long requestTime) {
+ int appWidgetId, int viewId, long requestId) {
try {
callbacks.viewDataChanged(appWidgetId, viewId);
- host.lastWidgetUpdateTime = requestTime;
+ host.lastWidgetUpdateRequestId = requestId;
} catch (RemoteException re) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
@@ -1928,9 +1932,9 @@
}
private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
- long requestTime = SystemClock.uptimeMillis();
+ long requestId = REQUEST_COUNTER.incrementAndGet();
if (widget != null) {
- widget.updateTimes.put(ID_VIEWS_UPDATE, requestTime);
+ widget.updateRequestIds.put(ID_VIEWS_UPDATE, requestId);
}
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
@@ -1941,7 +1945,7 @@
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = (updateViews != null) ? updateViews.clone() : null;
- args.arg4 = requestTime;
+ args.arg4 = requestId;
args.argi1 = widget.appWidgetId;
mCallbackHandler.obtainMessage(
@@ -1950,10 +1954,10 @@
}
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
- int appWidgetId, RemoteViews views, long requestTime) {
+ int appWidgetId, RemoteViews views, long requestId) {
try {
callbacks.updateAppWidget(appWidgetId, views);
- host.lastWidgetUpdateTime = requestTime;
+ host.lastWidgetUpdateRequestId = requestId;
} catch (RemoteException re) {
synchronized (mLock) {
Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -1963,11 +1967,11 @@
}
private void scheduleNotifyProviderChangedLocked(Widget widget) {
- long requestTime = SystemClock.uptimeMillis();
+ long requestId = REQUEST_COUNTER.incrementAndGet();
if (widget != null) {
// When the provider changes, reset everything else.
- widget.updateTimes.clear();
- widget.updateTimes.append(ID_PROVIDER_CHANGED, requestTime);
+ widget.updateRequestIds.clear();
+ widget.updateRequestIds.append(ID_PROVIDER_CHANGED, requestId);
}
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
@@ -1978,7 +1982,7 @@
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = widget.provider.info;
- args.arg4 = requestTime;
+ args.arg4 = requestId;
args.argi1 = widget.appWidgetId;
mCallbackHandler.obtainMessage(
@@ -1987,10 +1991,10 @@
}
private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
- int appWidgetId, AppWidgetProviderInfo info, long requestTime) {
+ int appWidgetId, AppWidgetProviderInfo info, long requestId) {
try {
callbacks.providerChanged(appWidgetId, info);
- host.lastWidgetUpdateTime = requestTime;
+ host.lastWidgetUpdateRequestId = requestId;
} catch (RemoteException re) {
synchronized (mLock){
Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -3463,11 +3467,11 @@
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
RemoteViews views = (RemoteViews) args.arg3;
- long requestTime = (Long) args.arg4;
+ long requestId = (Long) args.arg4;
final int appWidgetId = args.argi1;
args.recycle();
- handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestTime);
+ handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestId);
} break;
case MSG_NOTIFY_PROVIDER_CHANGED: {
@@ -3475,11 +3479,11 @@
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
AppWidgetProviderInfo info = (AppWidgetProviderInfo)args.arg3;
- long requestTime = (Long) args.arg4;
+ long requestId = (Long) args.arg4;
final int appWidgetId = args.argi1;
args.recycle();
- handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestTime);
+ handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestId);
} break;
case MSG_NOTIFY_PROVIDERS_CHANGED: {
@@ -3495,13 +3499,13 @@
SomeArgs args = (SomeArgs) message.obj;
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
- long requestTime = (Long) args.arg3;
+ long requestId = (Long) args.arg3;
final int appWidgetId = args.argi1;
final int viewId = args.argi2;
args.recycle();
handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId,
- requestTime);
+ requestId);
} break;
}
}
@@ -3817,7 +3821,7 @@
boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
int tag = TAG_UNDEFINED; // for use while saving state (the index)
- long lastWidgetUpdateTime; // last time we were successfully able to send an update.
+ long lastWidgetUpdateRequestId; // request id for the last update successfully sent
public int getUserId() {
return UserHandle.getUserId(id.uid);
@@ -3844,18 +3848,18 @@
*/
public boolean getPendingUpdatesForId(int appWidgetId,
LongSparseArray<PendingHostUpdate> outUpdates) {
- long updateTime = lastWidgetUpdateTime;
+ long updateRequestId = lastWidgetUpdateRequestId;
int N = widgets.size();
for (int i = 0; i < N; i++) {
Widget widget = widgets.get(i);
if (widget.appWidgetId == appWidgetId) {
outUpdates.clear();
- for (int j = widget.updateTimes.size() - 1; j >= 0; j--) {
- long time = widget.updateTimes.valueAt(j);
- if (time <= updateTime) {
+ for (int j = widget.updateRequestIds.size() - 1; j >= 0; j--) {
+ long requestId = widget.updateRequestIds.valueAt(j);
+ if (requestId <= updateRequestId) {
continue;
}
- int id = widget.updateTimes.keyAt(j);
+ int id = widget.updateRequestIds.keyAt(j);
final PendingHostUpdate update;
switch (id) {
case ID_PROVIDER_CHANGED:
@@ -3869,7 +3873,7 @@
default:
update = PendingHostUpdate.viewDataChanged(appWidgetId, id);
}
- outUpdates.put(time, update);
+ outUpdates.put(requestId, update);
}
return true;
}
@@ -3951,8 +3955,8 @@
RemoteViews maskedViews;
Bundle options;
Host host;
- // timestamps for various operations
- SparseLongArray updateTimes = new SparseLongArray(2);
+ // Request ids for various operations
+ SparseLongArray updateRequestIds = new SparseLongArray(2);
@Override
public String toString() {
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 07f14d4..cd88b85 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -25,8 +25,8 @@
android.hardware.tv.cec@1.0-java
LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update2 \
- android.hidl.base@1.0-java \
- android.hardware.biometrics.fingerprint@2.1-java \
+ android.hidl.base@1.0-java-static \
+ android.hardware.biometrics.fingerprint@2.1-java-static \
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/DiskStatsService.java b/services/core/java/com/android/server/DiskStatsService.java
index dd95f67..962ac6f 100644
--- a/services/core/java/com/android/server/DiskStatsService.java
+++ b/services/core/java/com/android/server/DiskStatsService.java
@@ -95,9 +95,7 @@
pw.println("File-based Encryption: true");
}
- if (isCheckin(args)) {
- reportCachedValues(pw);
- }
+ reportCachedValues(pw);
// TODO: Read /proc/yaffs and report interesting values;
// add configurable (through args) performance test parameters.
@@ -130,15 +128,6 @@
}
}
- private boolean isCheckin(String[] args) {
- for (String opt : args) {
- if ("--checkin".equals(opt)) {
- return true;
- }
- }
- return false;
- }
-
private void reportCachedValues(PrintWriter pw) {
try {
String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index bd9f684..6f49702 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -75,6 +75,7 @@
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
@@ -87,7 +88,7 @@
private final Context mContext;
private final NetworkScorerAppManager mNetworkScorerAppManager;
- private final RequestRecommendationCaller mRequestRecommendationCaller;
+ private final AtomicReference<RequestRecommendationCaller> mReqRecommendationCallerRef;
@GuardedBy("mScoreCaches")
private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
/** Lock used to update mPackageMonitor when scorer package changes occur. */
@@ -249,8 +250,8 @@
mContext.registerReceiverAsUser(
mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
null /* scheduler */);
- mRequestRecommendationCaller =
- new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
+ mReqRecommendationCallerRef = new AtomicReference<>(
+ new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS));
mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
mHandler = new ServiceHandler(looper);
mContentObserver = new DispatchingContentObserver(context, mHandler);
@@ -569,7 +570,8 @@
final INetworkRecommendationProvider provider = getRecommendationProvider();
if (provider != null) {
try {
- return mRequestRecommendationCaller.getRecommendationResult(provider, request);
+ final RequestRecommendationCaller caller = mReqRecommendationCallerRef.get();
+ return caller.getRecommendationResult(provider, request);
} catch (RemoteException | TimeoutException e) {
Log.w(TAG, "Failed to request a recommendation.", e);
// TODO(jjoslin): 12/15/16 - Keep track of failures.
@@ -748,6 +750,7 @@
}
if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
mRecommendationRequestTimeoutMs = timeoutMs;
+ mReqRecommendationCallerRef.set(new RequestRecommendationCaller(timeoutMs));
}
private static class ScoringServiceConnection implements ServiceConnection {
diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java
index 2010e64..3c8c699 100644
--- a/services/core/java/com/android/server/RecoverySystemService.java
+++ b/services/core/java/com/android/server/RecoverySystemService.java
@@ -181,7 +181,7 @@
}
@Override // Binder call
- public void rebootRecoveryWithCommand(String command, boolean update) {
+ public void rebootRecoveryWithCommand(String command) {
if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
synchronized (sRequestLock) {
if (!setupOrClearBcb(true, command)) {
@@ -190,10 +190,7 @@
// Having set up the BCB, go ahead and reboot.
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- // PowerManagerService may additionally request uncrypting the package when it's
- // to install an update (REBOOT_RECOVERY_UPDATE).
- pm.reboot(update ? PowerManager.REBOOT_RECOVERY_UPDATE :
- PowerManager.REBOOT_RECOVERY);
+ pm.reboot(PowerManager.REBOOT_RECOVERY);
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 55d31c3..f9b9d6f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -88,11 +88,13 @@
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IMediaContainerService;
+import com.android.internal.os.AppFuseMount;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
@@ -104,7 +106,7 @@
import com.android.server.NativeDaemonConnector.Command;
import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.pm.PackageManagerService;
-
+import com.android.server.storage.AppFuseBridge;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -135,6 +137,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -337,6 +340,15 @@
private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
+ /** Holding lock for AppFuse business */
+ private final Object mAppFuseLock = new Object();
+
+ @GuardedBy("mAppFuseLock")
+ private int mNextAppFuseName = 0;
+
+ @GuardedBy("mAppFuseLock")
+ private final SparseArray<Integer> mAppFusePids = new SparseArray<>();
+
private VolumeInfo findVolumeByIdOrThrow(String id) {
synchronized (mLock) {
final VolumeInfo vol = mVolumes.get(id);
@@ -3010,6 +3022,128 @@
}
}
+
+ class CloseableHolder<T extends AutoCloseable> implements AutoCloseable {
+ @Nullable T mCloseable;
+
+ CloseableHolder(T closeable) {
+ mCloseable = closeable;
+ }
+
+ @Nullable T get() {
+ return mCloseable;
+ }
+
+ @Nullable T release() {
+ final T result = mCloseable;
+ mCloseable = null;
+ return result;
+ }
+
+ @Override
+ public void close() {
+ if (mCloseable != null) {
+ IoUtils.closeQuietly(mCloseable);
+ }
+ }
+ }
+
+ class AppFuseMountScope implements AppFuseBridge.IMountScope {
+ final int mUid;
+ final int mName;
+ final ParcelFileDescriptor mDeviceFd;
+
+ AppFuseMountScope(int uid, int pid, int name) throws NativeDaemonConnectorException {
+ final NativeDaemonEvent event = mConnector.execute(
+ "appfuse", "mount", uid, Process.myPid(), name);
+ mUid = uid;
+ mName = name;
+ synchronized (mLock) {
+ mAppFusePids.put(name, pid);
+ }
+ if (event.getFileDescriptors() != null &&
+ event.getFileDescriptors().length > 0) {
+ mDeviceFd = new ParcelFileDescriptor(event.getFileDescriptors()[0]);
+ } else {
+ mDeviceFd = null;
+ }
+ }
+
+ @Override
+ public void close() throws NativeDaemonConnectorException {
+ try {
+ IoUtils.closeQuietly(mDeviceFd);
+ mConnector.execute(
+ "appfuse", "unmount", mUid, Process.myPid(), mName);
+ } finally {
+ synchronized (mLock) {
+ mAppFusePids.delete(mName);
+ }
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor getDeviceFileDescriptor() {
+ return mDeviceFd;
+ }
+ }
+
+ @Override
+ public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+ final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final int name;
+ synchronized (mAppFuseLock) {
+ name = mNextAppFuseName++;
+ }
+ try (CloseableHolder<AppFuseMountScope> mountScope =
+ new CloseableHolder<>(new AppFuseMountScope(uid, pid, name))) {
+ if (mountScope.get().getDeviceFileDescriptor() == null) {
+ throw new RemoteException("Failed to obtain device FD");
+ }
+
+ // Create communication channel.
+ final ArrayBlockingQueue<Boolean> channel = new ArrayBlockingQueue<>(1);
+ final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair();
+ try (CloseableHolder<ParcelFileDescriptor> remote = new CloseableHolder<>(fds[0])) {
+ new Thread(
+ new AppFuseBridge(mountScope.release(), fds[1], channel),
+ AppFuseBridge.TAG).start();
+ if (!channel.take()) {
+ throw new RemoteException("Failed to init AppFuse mount point");
+ }
+
+ return new AppFuseMount(name, remote.release());
+ }
+ } catch (NativeDaemonConnectorException e){
+ throw e.rethrowAsParcelableException();
+ } catch (IOException | InterruptedException error) {
+ throw new RemoteException(error.getMessage());
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode) {
+ final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ try {
+ synchronized (mAppFuseLock) {
+ final int expectedPid = mAppFusePids.get(mountId, -1);
+ if (expectedPid == -1) {
+ Slog.i(TAG, "The mount point has already been unmounted");
+ return null;
+ }
+ if (expectedPid != pid) {
+ throw new SecurityException("Mount point was not created by this process.");
+ }
+ }
+ return AppFuseBridge.openFile(uid, mountId, fileId, mode);
+ } catch (FileNotFoundException error) {
+ Slog.e(TAG, "Failed to openProxyFileDescriptor", error);
+ return null;
+ }
+ }
+
@Override
public int mkdirs(String callingPkg, String appPath) {
final int userId = UserHandle.getUserId(Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 47c3e6f..a2fb9f9 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1802,6 +1802,9 @@
}
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
+ if (mWindowContainerController == null) {
+ return;
+ }
final CompatibilityInfo compatInfo =
service.compatibilityInfoForPackageLocked(info.applicationInfo);
final boolean shown = mWindowContainerController.addStartingWindow(packageName, theme,
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index e2870d8..9348023 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -20,7 +20,6 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructTimeval;
-import android.system.StructUcred;
import android.system.UnixSocketAddress;
import android.util.Slog;
@@ -105,9 +104,9 @@
if (DEBUG) Slog.i(TAG, "Starting up");
- // The file system entity for this socket is created with 0700 perms, owned
- // by system:system. debuggerd runs as root, so is capable of connecting to
- // it, but 3rd party apps cannot.
+ // The file system entity for this socket is created with 0777 perms, owned
+ // by system:system. selinux restricts things so that only crash_dump can
+ // access it.
{
File socketFile = new File(DEBUGGERD_SOCKET_PATH);
if (socketFile.exists()) {
@@ -121,6 +120,7 @@
DEBUGGERD_SOCKET_PATH);
Os.bind(serverFd, sockAddr);
Os.listen(serverFd, 1);
+ Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);
while (true) {
FileDescriptor peerFd = null;
@@ -129,19 +129,14 @@
peerFd = Os.accept(serverFd, null /* peerAddress */);
if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
if (peerFd != null) {
- // Only the superuser is allowed to talk to us over this socket
- StructUcred credentials =
- Os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
- if (credentials.uid == 0) {
- // the reporting thread may take responsibility for
- // acking the debugger; make sure we play along.
- consumeNativeCrashData(peerFd);
- }
+ // the reporting thread may take responsibility for
+ // acking the debugger; make sure we play along.
+ consumeNativeCrashData(peerFd);
}
} catch (Exception e) {
Slog.w(TAG, "Error handling connection", e);
} finally {
- // Always ack debuggerd's connection to us. The actual
+ // Always ack crash_dump's connection to us. The actual
// byte written is irrelevant.
if (peerFd != null) {
try {
@@ -194,7 +189,7 @@
return totalRead;
}
- // Read the crash report from the debuggerd connection
+ // Read a crash report from the connection
void consumeNativeCrashData(FileDescriptor fd) {
if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
final byte[] buf = new byte[4096];
@@ -205,6 +200,10 @@
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
+ // The socket is guarded by an selinux neverallow rule that only
+ // permits crash_dump to connect to it. This allows us to trust the
+ // received values.
+
// first, the pid and signal number
int headerBytes = readExactly(fd, buf, 0, 8);
if (headerBytes != 8) {
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index cb20eac..d035fa9 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
import android.app.ITaskStackListener;
import android.app.ActivityManager.TaskDescription;
import android.content.ComponentName;
@@ -43,6 +45,7 @@
static final int NOTIFY_ACTIVITY_REQUESTED_ORIENTATION_CHANGED_LISTENERS = 12;
static final int NOTIFY_TASK_REMOVAL_STARTED_LISTENERS = 13;
static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
+ static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -113,6 +116,10 @@
l.onTaskProfileLocked(m.arg1, m.arg2);
};
+ private final TaskStackConsumer mNotifyTaskSnapshotChanged = (l, m) -> {
+ l.onTaskSnapshotChanged(m.arg1, (TaskSnapshot) m.obj);
+ };
+
@FunctionalInterface
public interface TaskStackConsumer {
void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -170,7 +177,9 @@
break;
case NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyTaskProfileLocked, msg);
-
+ break;
+ case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG:
+ forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg);
break;
}
}
@@ -348,4 +357,14 @@
forAllLocalListeners(mNotifyTaskProfileLocked, msg);
msg.sendToTarget();
}
+
+ /**
+ * Notify listeners that the snapshot of a task has changed.
+ */
+ void notifyTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+ final Message msg = mHandler.obtainMessage(NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG,
+ taskId, 0, snapshot);
+ forAllLocalListeners(mNotifyTaskSnapshotChanged, msg);
+ msg.sendToTarget();
+ }
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 4c4c444..a72a958 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -52,6 +52,8 @@
import com.android.internal.util.XmlUtils;
import com.android.server.wm.TaskWindowContainerController;
+import com.android.server.wm.TaskWindowContainerListener;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -105,7 +107,7 @@
import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
-final class TaskRecord extends ConfigurationContainer {
+final class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
@@ -412,8 +414,8 @@
final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
final Configuration overrideConfig = getOverrideConfiguration();
- mWindowContainerController = new TaskWindowContainerController(taskId, getStackId(), userId,
- bounds, overrideConfig, mResizeMode, isHomeTask(), isOnTopLauncher(), onTop,
+ mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(),
+ userId, bounds, overrideConfig, mResizeMode, isHomeTask(), isOnTopLauncher(), onTop,
showForAllUsers);
}
@@ -429,6 +431,11 @@
mWindowContainerController = null;
}
+ @Override
+ public void onSnapshotChanged(TaskSnapshot snapshot) {
+ mService.mTaskChangeNotificationController.notifyTaskSnapshotChanged(taskId, snapshot);
+ }
+
void setResizeMode(int resizeMode) {
if (mResizeMode == resizeMode) {
return;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 71ebad9..f516e99 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -259,6 +259,11 @@
int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
MetricsLogger.histogram(mInjector.getContext(),
"framework_locked_boot_completed", uptimeSeconds);
+ final int MAX_UPTIME_SECONDS = 120;
+ if (uptimeSeconds > MAX_UPTIME_SECONDS) {
+ Slog.wtf("SystemServerTiming",
+ "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
+ }
}
mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 927dfd5..4c950de 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,6 +16,9 @@
package com.android.server.connectivity.tethering;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -142,7 +145,11 @@
// message to aid in any subsequent debugging
if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
- cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback);
+ // The following use of the legacy type system cannot be removed until
+ // after upstream selection no longer finds networks by legacy type.
+ // See also b/34364553.
+ final int apnType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
+ cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback, 0, apnType);
}
public void releaseMobileNetworkRequest() {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 87f4030..fbb39384 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -25,6 +25,7 @@
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
import com.android.internal.R;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
@@ -1700,6 +1701,7 @@
// Binder call
@Override
public void setCustomPointerIcon(PointerIcon icon) {
+ Preconditions.checkNotNull(icon);
nativeSetCustomPointerIcon(mPtr, icon);
}
diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/services/core/java/com/android/server/net/NetworkIdentitySet.java
index c48f430..ee00fdc 100644
--- a/services/core/java/com/android/server/net/NetworkIdentitySet.java
+++ b/services/core/java/com/android/server/net/NetworkIdentitySet.java
@@ -17,6 +17,8 @@
package com.android.server.net;
import android.net.NetworkIdentity;
+import android.service.NetworkIdentitySetProto;
+import android.util.proto.ProtoOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -143,4 +145,14 @@
final NetworkIdentity anotherIdent = another.iterator().next();
return ident.compareTo(anotherIdent);
}
+
+ public void writeToProto(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ for (NetworkIdentity ident : this) {
+ ident.writeToProto(proto, NetworkIdentitySetProto.IDENTITIES);
+ }
+
+ proto.end(start);
+ }
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index c45b416..0354300 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -34,9 +34,13 @@
import android.net.NetworkTemplate;
import android.net.TrafficStats;
import android.os.Binder;
+import android.service.NetworkStatsCollectionKeyProto;
+import android.service.NetworkStatsCollectionProto;
+import android.service.NetworkStatsCollectionStatsProto;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.IntArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FileRotator;
@@ -532,12 +536,15 @@
/ mBucketDuration);
}
- public void dump(IndentingPrintWriter pw) {
+ private ArrayList<Key> getSortedKeys() {
final ArrayList<Key> keys = Lists.newArrayList();
keys.addAll(mStats.keySet());
Collections.sort(keys);
+ return keys;
+ }
- for (Key key : keys) {
+ public void dump(IndentingPrintWriter pw) {
+ for (Key key : getSortedKeys()) {
pw.print("ident="); pw.print(key.ident.toString());
pw.print(" uid="); pw.print(key.uid);
pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
@@ -550,6 +557,29 @@
}
}
+ public void writeToProto(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ for (Key key : getSortedKeys()) {
+ final long startStats = proto.start(NetworkStatsCollectionProto.STATS);
+
+ // Key
+ final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY);
+ key.ident.writeToProto(proto, NetworkStatsCollectionKeyProto.IDENTITY);
+ proto.write(NetworkStatsCollectionKeyProto.UID, key.uid);
+ proto.write(NetworkStatsCollectionKeyProto.SET, key.set);
+ proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag);
+ proto.end(startKey);
+
+ // Value
+ final NetworkStatsHistory history = mStats.get(key);
+ history.writeToProto(proto, NetworkStatsCollectionStatsProto.HISTORY);
+ proto.end(startStats);
+ }
+
+ proto.end(start);
+ }
+
public void dumpCheckin(PrintWriter pw, long start, long end) {
dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell");
dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi");
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index 090a076..80309e1 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -29,9 +29,11 @@
import android.net.NetworkTemplate;
import android.net.TrafficStats;
import android.os.DropBoxManager;
+import android.service.NetworkStatsRecorderProto;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.net.VpnInfo;
import com.android.internal.util.FileRotator;
@@ -465,6 +467,15 @@
}
}
+ public void writeToProtoLocked(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+ if (mPending != null) {
+ proto.write(NetworkStatsRecorderProto.PENDING_TOTAL_BYTES, mPending.getTotalBytes());
+ }
+ getOrLoadCompleteLocked().writeToProto(proto, NetworkStatsRecorderProto.COMPLETE_HISTORY);
+ proto.end(start);
+ }
+
public void dumpCheckin(PrintWriter pw, long start, long end) {
// Only load and dump stats from the requested window
getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end);
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 386e78b..104c296 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -104,6 +104,8 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.service.NetworkInterfaceProto;
+import android.service.NetworkStatsServiceDumpProto;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
@@ -115,6 +117,7 @@
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.TrustedTime;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.VpnInfo;
@@ -1255,6 +1258,12 @@
final IndentingPrintWriter pw = new IndentingPrintWriter(rawWriter, " ");
synchronized (mStatsLock) {
+ if (args.length > 0 && "--proto".equals(args[0])) {
+ // In this case ignore all other arguments.
+ dumpProto(fd);
+ return;
+ }
+
if (poll) {
performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
pw.println("Forced poll");
@@ -1327,6 +1336,33 @@
}
}
+ private void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ // TODO Right now it writes all history. Should it limit to the "since-boot" log?
+
+ dumpInterfaces(proto, NetworkStatsServiceDumpProto.ACTIVE_INTERFACES, mActiveIfaces);
+ dumpInterfaces(proto, NetworkStatsServiceDumpProto.ACTIVE_UID_INTERFACES, mActiveUidIfaces);
+ mDevRecorder.writeToProtoLocked(proto, NetworkStatsServiceDumpProto.DEV_STATS);
+ mXtRecorder.writeToProtoLocked(proto, NetworkStatsServiceDumpProto.XT_STATS);
+ mUidRecorder.writeToProtoLocked(proto, NetworkStatsServiceDumpProto.UID_STATS);
+ mUidTagRecorder.writeToProtoLocked(proto, NetworkStatsServiceDumpProto.UID_TAG_STATS);
+
+ proto.flush();
+ }
+
+ private static void dumpInterfaces(ProtoOutputStream proto, long tag,
+ ArrayMap<String, NetworkIdentitySet> ifaces) {
+ for (int i = 0; i < ifaces.size(); i++) {
+ final long start = proto.start(tag);
+
+ proto.write(NetworkInterfaceProto.INTERFACE, ifaces.keyAt(i));
+ ifaces.valueAt(i).writeToProto(proto, NetworkInterfaceProto.IDENTITIES);
+
+ proto.end(start);
+ }
+ }
+
/**
* Return snapshot of current UID statistics, including any
* {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0cac406..9018302 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3289,7 +3289,9 @@
private boolean playSound(final NotificationRecord record, Uri soundUri) {
boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
// do not play notifications if there is a user of exclusive audio focus
- if (!mAudioManager.isAudioFocusExclusive()) {
+ // or the device is in vibrate mode
+ if (!mAudioManager.isAudioFocusExclusive() && mAudioManager.getRingerModeInternal()
+ != AudioManager.RINGER_MODE_VIBRATE) {
final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
@@ -3995,7 +3997,8 @@
NotificationRecord childR = mNotificationList.get(i);
StatusBarNotification childSbn = childR.sbn;
if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
- childR.getGroupKey().equals(r.getGroupKey())) {
+ childR.getGroupKey().equals(r.getGroupKey())
+ && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
childSbn.getTag(), userId, 0, 0, reason, listenerName);
mNotificationList.remove(i);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 98249dd1..de0d2a3 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -415,6 +415,15 @@
}
}
+ public void invalidateMounts() throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.invalidateMounts();
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 48e000d8..2ddf6db 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -21,9 +21,11 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ILauncherApps;
@@ -277,24 +279,11 @@
@Override
public ParceledListSlice<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
throws RemoteException {
- ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
- if (!isUserEnabled(user)) {
- return null;
- }
-
- final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- mainIntent.setPackage(packageName);
- long ident = Binder.clearCallingIdentity();
- try {
- List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- user.getIdentifier());
- return new ParceledListSlice<>(apps);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ return queryActivitiesForUser(
+ new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(packageName),
+ user);
}
@Override
@@ -318,6 +307,53 @@
}
@Override
+ public ParceledListSlice getShortcutConfigActivities(String packageName, UserHandle user)
+ throws RemoteException {
+ return queryActivitiesForUser(
+ new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName), user);
+ }
+
+ private ParceledListSlice<ResolveInfo> queryActivitiesForUser(Intent intent,
+ UserHandle user) {
+ ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
+ if (!isUserEnabled(user)) {
+ return null;
+ }
+
+ long ident = injectClearCallingIdentity();
+ try {
+ List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(intent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ user.getIdentifier());
+ return new ParceledListSlice<>(apps);
+ } finally {
+ injectRestoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public IntentSender getShortcutConfigActivityIntent(String callingPackage,
+ ComponentName component, UserHandle user) throws RemoteException {
+ ensureShortcutPermission(callingPackage, user);
+ Preconditions.checkNotNull(component);
+ Preconditions.checkArgument(isUserEnabled(user), "User not enabled");
+
+ // All right, create the sender.
+ Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(component);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return PendingIntent.getActivityAsUser(
+ mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
+ null, user)
+ .getIntentSender();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public boolean isPackageEnabled(String packageName, UserHandle user)
throws RemoteException {
ensureInUserProfiles(user, "Cannot check package for unrelated profile " + user);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index d25abbf..d516acf 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -145,6 +145,7 @@
private static final String ATTR_ABI_OVERRIDE = "abiOverride";
private static final String ATTR_VOLUME_UUID = "volumeUuid";
private static final String ATTR_NAME = "name";
+ private static final String ATTR_INSTALL_REASON = "installRason";
/** Automatically destroy sessions older than this */
private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
@@ -412,6 +413,7 @@
params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
+ params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
final File appIconFile = buildAppIconFile(sessionId);
if (appIconFile.exists()) {
@@ -484,6 +486,7 @@
writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
+ writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
// Persist app icon if changed since last written
final File appIconFile = buildAppIconFile(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7362a51..af1e007 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1713,9 +1713,11 @@
}
// Now that we successfully installed the package, grant runtime
- // permissions if requested before broadcasting the install.
- if (grantPermissions && res.pkg.applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.M) {
+ // permissions if requested before broadcasting the install. Also
+ // for legacy apps in permission review mode we clear the permission
+ // review flag which is used to emulate runtime permissions for
+ // legacy apps.
+ if (grantPermissions) {
grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions);
}
@@ -1958,11 +1960,6 @@
for (int userId : userIds) {
grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions);
}
-
- // We could have touched GID membership, so flush out packages.list
- synchronized (mPackages) {
- mSettings.writePackageListLPr();
- }
}
private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
@@ -1977,6 +1974,9 @@
final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+ final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
+ >= Build.VERSION_CODES.M;
+
for (String permission : pkg.requestedPermissions) {
final BasePermission bp;
synchronized (mPackages) {
@@ -1986,9 +1986,18 @@
&& (grantedPermissions == null
|| ArrayUtils.contains(grantedPermissions, permission))) {
final int flags = permissionsState.getPermissionFlags(permission, userId);
- // Installer cannot change immutable permissions.
- if ((flags & immutableFlags) == 0) {
- grantRuntimePermission(pkg.packageName, permission, userId);
+ if (supportsRuntimePermissions) {
+ // Installer cannot change immutable permissions.
+ if ((flags & immutableFlags) == 0) {
+ grantRuntimePermission(pkg.packageName, permission, userId);
+ }
+ } else if (mPermissionReviewRequired) {
+ // In permission review mode we clear the review flag when we
+ // are asked to install the app with all permissions granted.
+ if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ updatePermissionFlags(permission, pkg.packageName,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId);
+ }
}
}
}
@@ -12208,6 +12217,67 @@
mHandler.sendMessage(msg);
}
+
+ /**
+ * Ensure that the install reason matches what we know about the package installer (e.g. whether
+ * it is acting on behalf on an enterprise or the user).
+ *
+ * Note that the ordering of the conditionals in this method is important. The checks we perform
+ * are as follows, in this order:
+ *
+ * 1) If the install is being performed by a system app, we can trust the app to have set the
+ * install reason correctly. Thus, we pass through the install reason unchanged, no matter
+ * what it is.
+ * 2) If the install is being performed by a device or profile owner app, the install reason
+ * should be enterprise policy. However, we cannot be sure that the device or profile owner
+ * set the install reason correctly. If the app targets an older SDK version where install
+ * reasons did not exist yet, or if the app author simply forgot, the install reason may be
+ * unset or wrong. Thus, we force the install reason to be enterprise policy.
+ * 3) In all other cases, the install is being performed by a regular app that is neither part
+ * of the system nor a device or profile owner. We have no reason to believe that this app is
+ * acting on behalf of the enterprise admin. Thus, we check whether the install reason was
+ * set to enterprise policy and if so, change it to unknown instead.
+ */
+ private int fixUpInstallReason(String installerPackageName, int installerUid,
+ int installReason) {
+ if (checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
+ == PERMISSION_GRANTED) {
+ // If the install is being performed by a system app, we trust that app to have set the
+ // install reason correctly.
+ return installReason;
+ }
+
+ final IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
+ if (dpm != null) {
+ ComponentName owner = null;
+ try {
+ owner = dpm.getDeviceOwnerComponent(true /* callingUserOnly */);
+ if (owner == null) {
+ owner = dpm.getProfileOwner(UserHandle.getUserId(installerUid));
+ }
+ } catch (RemoteException e) {
+ }
+ if (owner != null && owner.getPackageName().equals(installerPackageName)) {
+ // If the install is being performed by a device or profile owner, the install
+ // reason should be enterprise policy.
+ return PackageManager.INSTALL_REASON_POLICY;
+ }
+ }
+
+ if (installReason == PackageManager.INSTALL_REASON_POLICY) {
+ // If the install is being performed by a regular app (i.e. neither system app nor
+ // device or profile owner), we have no reason to believe that the app is acting on
+ // behalf of an enterprise. If the app set the install reason to enterprise policy,
+ // change it to unknown instead.
+ return PackageManager.INSTALL_REASON_UNKNOWN;
+ }
+
+ // If the install is being performed by a regular app and the install reason was set to any
+ // value but enterprise policy, leave the install reason unchanged.
+ return installReason;
+ }
+
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
@@ -12229,10 +12299,12 @@
}
final Message msg = mHandler.obtainMessage(INIT_COPY);
+ final int installReason = fixUpInstallReason(installerPackageName, installerUid,
+ sessionParams.installReason);
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
- sessionParams.grantedRuntimePermissions, certificates, sessionParams.installReason);
+ sessionParams.grantedRuntimePermissions, certificates, installReason);
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index c8ddf0a..a156356 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -15,6 +15,7 @@
*/
package com.android.server.pm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -24,6 +25,7 @@
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.PinItemRequest;
import android.content.pm.ShortcutInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
@@ -50,18 +52,31 @@
private static class PinItemRequestInner extends IPinItemRequest.Stub {
protected final ShortcutRequestPinProcessor mProcessor;
private final IntentSender mResultIntent;
+ private final int mLauncherUid;
@GuardedBy("this")
private boolean mAccepted;
private PinItemRequestInner(ShortcutRequestPinProcessor processor,
- IntentSender resultIntent) {
+ IntentSender resultIntent, int launcherUid) {
mProcessor = processor;
mResultIntent = resultIntent;
+ mLauncherUid = launcherUid;
+ }
+
+ /**
+ * Returns true if the caller is same as the default launcher app when this request
+ * object was created.
+ */
+ private boolean isCallerValid() {
+ return mProcessor.isCallerUid(mLauncherUid);
}
@Override
public boolean isValid() {
+ if (!isCallerValid()) {
+ return false;
+ }
// TODO When an app calls requestPinShortcut(), all pending requests should be
// invalidated.
synchronized (this) {
@@ -76,6 +91,9 @@
public boolean accept(Bundle options) {
// Make sure the options are unparcellable by the FW. (e.g. not containing unknown
// classes.)
+ if (!isCallerValid()) {
+ throw new SecurityException("Calling uid mismatch");
+ }
Intent extras = null;
if (options != null) {
try {
@@ -126,8 +144,8 @@
private PinShortcutRequestInner(ShortcutRequestPinProcessor processor,
ShortcutInfo shortcutOriginal, ShortcutInfo shortcutForLauncher,
IntentSender resultIntent,
- String launcherPackage, int launcherUserId, boolean preExisting) {
- super(processor, resultIntent);
+ String launcherPackage, int launcherUserId, int launcherUid, boolean preExisting) {
+ super(processor, resultIntent, launcherUid);
this.shortcutOriginal = shortcutOriginal;
this.shortcutForLauncher = shortcutForLauncher;
this.launcherPackage = launcherPackage;
@@ -157,6 +175,7 @@
/**
* Handle {@link android.content.pm.ShortcutManager#requestPinShortcut)} and
* {@link android.appwidget.AppWidgetManager#requestPinAppWidget}.
+ * In this flow the PinItemRequest is delivered directly to the default launcher app.
* One of {@param inShortcut} and {@param inAppWidget} is always non-null and the other is
* always null.
*/
@@ -184,9 +203,13 @@
// Next, validate the incoming shortcut, etc.
final PinItemRequest request;
if (inShortcut != null) {
- request = requestPinShortcutLocked(inShortcut, resultIntent, confirmActivity);
+ request = requestPinShortcutLocked(inShortcut, resultIntent, confirmActivity,
+ true /* ignoreIfAlreadyPinned */);
} else {
- request = new PinItemRequest(inAppWidget, new PinItemRequestInner(this, resultIntent));
+ int launcherUid = mService.injectGetPackageUid(
+ confirmActivity.first.getPackageName(), launcherUserId);
+ request = new PinItemRequest(inAppWidget,
+ new PinItemRequestInner(this, resultIntent, launcherUid));
}
if (request == null) {
@@ -197,10 +220,41 @@
}
/**
+ * Handle {@link android.content.pm.ShortcutManager#createShortcutResultIntent(ShortcutInfo)}.
+ * In this flow the PinItemRequest is delivered to the caller app. Its the app's responsibility
+ * to send it to the Launcher app (via {@link android.app.Activity#setResult(int, Intent)}).
+ */
+ public Intent createShortcutResultIntent(@NonNull ShortcutInfo inShortcut, int userId) {
+ // Find the default launcher activity
+ final int launcherUserId = mService.getParentOrSelfUserId(userId);
+ final ComponentName defaultLauncher = mService.getDefaultLauncher(launcherUserId);
+ if (defaultLauncher == null) {
+ Log.e(TAG, "Default launcher not found.");
+ return null;
+ }
+
+ // Make sure the launcher user is unlocked. (it's always the parent profile, so should
+ // really be unlocked here though.)
+ mService.throwIfUserLockedL(launcherUserId);
+
+ // Next, validate the incoming shortcut, etc.
+ PinItemRequest request = requestPinShortcutLocked(inShortcut, null,
+ Pair.create(defaultLauncher, launcherUserId), false /* ignoreIfAlreadyPinned */);
+ if (request == null) {
+ return null;
+ }
+ return new Intent().putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request);
+ }
+
+ /**
* Handle {@link android.content.pm.ShortcutManager#requestPinShortcut)}.
+ *
+ * @param ignoreIfAlreadyPinned if true and the {@param inShortcut} is already pinned for
+ * {@param confirmActivity}, null is returned instead.
*/
private PinItemRequest requestPinShortcutLocked(ShortcutInfo inShortcut,
- IntentSender resultIntent, Pair<ComponentName, Integer> confirmActivity) {
+ IntentSender resultIntent, Pair<ComponentName, Integer> confirmActivity,
+ boolean ignoreIfAlreadyPinned) {
final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked(
inShortcut.getPackage(), inShortcut.getUserId());
@@ -221,9 +275,10 @@
if (existsAlready) {
validateExistingShortcut(existing);
+ final boolean isAlreadyPinned = mService.getLauncherShortcutsLocked(
+ launcherPackage, existing.getUserId(), launcherUserId).hasPinned(existing);
// See if it's already pinned.
- if (mService.getLauncherShortcutsLocked(
- launcherPackage, existing.getUserId(), launcherUserId).hasPinned(existing)) {
+ if (ignoreIfAlreadyPinned && isAlreadyPinned) {
Log.i(TAG, "Launcher's already pinning shortcut " + existing.getId()
+ " for package " + existing.getPackage());
return null;
@@ -233,8 +288,10 @@
// Note this will remove the intent and icons.
shortcutForLauncher = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
- // FLAG_PINNED is still set, if it's pinned by other launchers.
- shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED);
+ if (!isAlreadyPinned) {
+ // FLAG_PINNED is still set, if it's pinned by other launchers.
+ shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED);
+ }
} else {
// If the shortcut has no default activity, try to set the main activity.
// But in the request-pin case, it's optional, so it's okay even if the caller
@@ -264,7 +321,9 @@
// Create a request object.
final PinShortcutRequestInner inner =
new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher, resultIntent,
- launcherPackage, launcherUserId, existsAlready);
+ launcherPackage, launcherUserId,
+ mService.injectGetPackageUid(launcherPackage, launcherUserId),
+ existsAlready);
return new PinItemRequest(shortcutForLauncher, inner);
}
@@ -327,6 +386,10 @@
mService.injectSendIntentSender(intent, extras);
}
+ public boolean isCallerUid(int uid) {
+ return uid == mService.injectBinderCallingUid();
+ }
+
/**
* The last step of the "request pin shortcut" flow. Called when the launcher accepted a
* request.
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a890526..ae709fe 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1529,7 +1529,7 @@
if (UserHandle.getUserId(callingUid) != userId) {
throw new SecurityException("Invalid user-ID");
}
- if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) {
+ if (injectGetPackageUid(packageName, userId) == callingUid) {
return; // Caller is valid.
}
throw new SecurityException("Calling package name mismatch");
@@ -1854,6 +1854,25 @@
return requestPinItem(packageName, userId, shortcut, null, resultIntent);
}
+ @Override
+ public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId)
+ throws RemoteException {
+ Preconditions.checkNotNull(shortcut);
+ Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
+ verifyCaller(packageName, userId);
+
+ final Intent ret;
+ synchronized (mLock) {
+ throwIfUserLockedL(userId);
+
+ // Send request to the launcher, if supported.
+ ret = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId);
+ }
+
+ verifyStates();
+ return ret;
+ }
+
/**
* Handles {@link #requestPinShortcut} and {@link ShortcutServiceInternal#requestPinAppWidget}.
* After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9fc70d6..e646ffc 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -67,6 +67,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManager.EnforcingUser;
import android.os.UserManagerInternal;
import android.os.UserManagerInternal.UserRestrictionsListener;
import android.os.storage.StorageManager;
@@ -118,6 +119,7 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -162,7 +164,11 @@
private static final String TAG_USER = "user";
private static final String TAG_RESTRICTIONS = "restrictions";
private static final String TAG_DEVICE_POLICY_RESTRICTIONS = "device_policy_restrictions";
+ private static final String TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS =
+ "device_policy_global_restrictions";
+ /** Legacy name for device owner id tag. */
private static final String TAG_GLOBAL_RESTRICTION_OWNER_ID = "globalRestrictionOwnerUserId";
+ private static final String TAG_DEVICE_OWNER_USER_ID = "deviceOwnerUserId";
private static final String TAG_ENTRY = "entry";
private static final String TAG_VALUE = "value";
private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions";
@@ -202,7 +208,7 @@
@VisibleForTesting
static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
- private static final int USER_VERSION = 6;
+ private static final int USER_VERSION = 7;
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
@@ -267,7 +273,7 @@
/**
* User restrictions set via UserManager. This doesn't include restrictions set by
- * device owner / profile owners.
+ * device owner / profile owners. Only non-empty restriction bundles are stored.
*
* DO NOT Change existing {@link Bundle} in it. When changing a restriction for a user,
* a new {@link Bundle} should always be created and set. This is because a {@link Bundle}
@@ -305,20 +311,21 @@
/**
* User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
- * that should be applied to all users, including guests.
+ * that should be applied to all users, including guests. Only non-empty restriction bundles are
+ * stored.
*/
@GuardedBy("mRestrictionsLock")
- private Bundle mDevicePolicyGlobalUserRestrictions;
+ private final SparseArray<Bundle> mDevicePolicyGlobalUserRestrictions = new SparseArray<>();
/**
* Id of the user that set global restrictions.
*/
@GuardedBy("mRestrictionsLock")
- private int mGlobalRestrictionOwnerUserId = UserHandle.USER_NULL;
+ private int mDeviceOwnerUserId = UserHandle.USER_NULL;
/**
* User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
- * for each user.
+ * for each user. Only non-empty restriction bundles are stored.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
@@ -1176,39 +1183,36 @@
}
/**
- * See {@link UserManagerInternal#setDevicePolicyUserRestrictions(int, Bundle, Bundle)}
+ * See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
*/
- void setDevicePolicyUserRestrictionsInner(int userId, @NonNull Bundle local,
- @Nullable Bundle global) {
- Preconditions.checkNotNull(local);
- boolean globalChanged = false;
- boolean localChanged;
+ private void setDevicePolicyUserRestrictionsInner(int userId, @Nullable Bundle restrictions,
+ boolean isDeviceOwner, int cameraRestrictionScope) {
+ final Bundle global = new Bundle();
+ final Bundle local = new Bundle();
+
+ // Sort restrictions into local and global ensuring they don't overlap.
+ UserRestrictionsUtils.sortToGlobalAndLocal(restrictions, isDeviceOwner,
+ cameraRestrictionScope, global, local);
+
+ boolean globalChanged, localChanged;
synchronized (mRestrictionsLock) {
- if (global != null) {
- // Update global.
- globalChanged = !UserRestrictionsUtils.areEqual(
- mDevicePolicyGlobalUserRestrictions, global);
- if (globalChanged) {
- mDevicePolicyGlobalUserRestrictions = global;
- }
+ // Update global and local restrictions if they were changed.
+ globalChanged = updateRestrictionsIfNeededLR(
+ userId, global, mDevicePolicyGlobalUserRestrictions);
+ localChanged = updateRestrictionsIfNeededLR(
+ userId, local, mDevicePolicyLocalUserRestrictions);
+
+ if (isDeviceOwner) {
// Remember the global restriction owner userId to be able to make a distinction
// in getUserRestrictionSource on who set local policies.
- mGlobalRestrictionOwnerUserId = userId;
+ mDeviceOwnerUserId = userId;
} else {
- if (mGlobalRestrictionOwnerUserId == userId) {
+ if (mDeviceOwnerUserId == userId) {
// When profile owner sets restrictions it passes null global bundle and we
// reset global restriction owner userId.
// This means this user used to have DO, but now the DO is gone and the user
// instead has PO.
- mGlobalRestrictionOwnerUserId = UserHandle.USER_NULL;
- }
- }
- {
- // Update local.
- final Bundle prev = mDevicePolicyLocalUserRestrictions.get(userId);
- localChanged = !UserRestrictionsUtils.areEqual(prev, local);
- if (localChanged) {
- mDevicePolicyLocalUserRestrictions.put(userId, local);
+ mDeviceOwnerUserId = UserHandle.USER_NULL;
}
}
}
@@ -1220,12 +1224,9 @@
}
// Don't call them within the mRestrictionsLock.
synchronized (mPackagesLock) {
- if (localChanged) {
+ if (localChanged || globalChanged) {
writeUserLP(getUserDataNoChecks(userId));
}
- if (globalChanged) {
- writeUserListLP();
- }
}
synchronized (mRestrictionsLock) {
@@ -1237,11 +1238,30 @@
}
}
+ /**
+ * Updates restriction bundle for a given user in a given restriction array. If new bundle is
+ * empty, record is removed from the array.
+ * @return whether restrictions bundle is different from the old one.
+ */
+ private boolean updateRestrictionsIfNeededLR(int userId, @Nullable Bundle restrictions,
+ SparseArray<Bundle> restrictionsArray) {
+ final boolean changed =
+ !UserRestrictionsUtils.areEqual(restrictionsArray.get(userId), restrictions);
+ if (changed) {
+ if (!UserRestrictionsUtils.isEmpty(restrictions)) {
+ restrictionsArray.put(userId, restrictions);
+ } else {
+ restrictionsArray.delete(userId);
+ }
+ }
+ return changed;
+ }
+
@GuardedBy("mRestrictionsLock")
private Bundle computeEffectiveUserRestrictionsLR(int userId) {
final Bundle baseRestrictions =
UserRestrictionsUtils.nonNull(mBaseUserRestrictions.get(userId));
- final Bundle global = mDevicePolicyGlobalUserRestrictions;
+ final Bundle global = UserRestrictionsUtils.mergeAll(mDevicePolicyGlobalUserRestrictions);
final Bundle local = mDevicePolicyLocalUserRestrictions.get(userId);
if (UserRestrictionsUtils.isEmpty(global) && UserRestrictionsUtils.isEmpty(local)) {
@@ -1299,39 +1319,58 @@
*/
@Override
public int getUserRestrictionSource(String restrictionKey, int userId) {
- checkManageUsersPermission("getUserRestrictionSource");
+ List<EnforcingUser> enforcingUsers = getUserRestrictionSources(restrictionKey, userId);
+ // Get "bitwise or" of restriction sources for all enforcing users.
int result = UserManager.RESTRICTION_NOT_SET;
+ for (int i = enforcingUsers.size() - 1; i >= 0; i--) {
+ result |= enforcingUsers.get(i).getUserRestrictionSource();
+ }
+ return result;
+ }
+
+ @Override
+ public List<EnforcingUser> getUserRestrictionSources(
+ String restrictionKey, @UserIdInt int userId) {
+ checkManageUsersPermission("getUserRestrictionSource");
// Shortcut for the most common case
if (!hasUserRestriction(restrictionKey, userId)) {
- return result;
+ return Collections.emptyList();
}
+ final List<EnforcingUser> result = new ArrayList<>();
+
+ // Check if it is base restriction.
if (hasBaseUserRestriction(restrictionKey, userId)) {
- result |= UserManager.RESTRICTION_SOURCE_SYSTEM;
+ result.add(new EnforcingUser(
+ UserHandle.USER_NULL, UserManager.RESTRICTION_SOURCE_SYSTEM));
}
- synchronized(mRestrictionsLock) {
- Bundle localRestrictions = mDevicePolicyLocalUserRestrictions.get(userId);
- if (!UserRestrictionsUtils.isEmpty(localRestrictions)
- && localRestrictions.getBoolean(restrictionKey)) {
- // Local restrictions may have been set by device owner the userId of which is
- // stored in mGlobalRestrictionOwnerUserId.
- if (mGlobalRestrictionOwnerUserId == userId) {
- result |= UserManager.RESTRICTION_SOURCE_DEVICE_OWNER;
- } else {
- result |= UserManager.RESTRICTION_SOURCE_PROFILE_OWNER;
+ synchronized (mRestrictionsLock) {
+ // Check if it is set by profile owner.
+ Bundle profileOwnerRestrictions = mDevicePolicyLocalUserRestrictions.get(userId);
+ if (UserRestrictionsUtils.contains(profileOwnerRestrictions, restrictionKey)) {
+ result.add(getEnforcingUserLocked(userId));
+ }
+
+ // Iterate over all users who enforce global restrictions.
+ for (int i = mDevicePolicyGlobalUserRestrictions.size() - 1; i >= 0; i--) {
+ Bundle globalRestrictions = mDevicePolicyGlobalUserRestrictions.valueAt(i);
+ int profileUserId = mDevicePolicyGlobalUserRestrictions.keyAt(i);
+ if (UserRestrictionsUtils.contains(globalRestrictions, restrictionKey)) {
+ result.add(getEnforcingUserLocked(profileUserId));
}
}
- if (!UserRestrictionsUtils.isEmpty(mDevicePolicyGlobalUserRestrictions)
- && mDevicePolicyGlobalUserRestrictions.getBoolean(restrictionKey)) {
- result |= UserManager.RESTRICTION_SOURCE_DEVICE_OWNER;
- }
}
-
return result;
}
+ private EnforcingUser getEnforcingUserLocked(@UserIdInt int userId) {
+ int source = mDeviceOwnerUserId == userId ? UserManager.RESTRICTION_SOURCE_DEVICE_OWNER
+ : UserManager.RESTRICTION_SOURCE_PROFILE_OWNER;
+ return new EnforcingUser(userId, source);
+ }
+
/**
* @return UserRestrictions that are in effect currently. This always returns a new
* {@link Bundle}.
@@ -1374,28 +1413,26 @@
* Optionally updating user restrictions, calculate the effective user restrictions and also
* propagate to other services and system settings.
*
- * @param newRestrictions User restrictions to set.
+ * @param newBaseRestrictions User restrictions to set.
* If null, will not update user restrictions and only does the propagation.
* @param userId target user ID.
*/
@GuardedBy("mRestrictionsLock")
private void updateUserRestrictionsInternalLR(
- @Nullable Bundle newRestrictions, int userId) {
-
+ @Nullable Bundle newBaseRestrictions, int userId) {
final Bundle prevAppliedRestrictions = UserRestrictionsUtils.nonNull(
mAppliedUserRestrictions.get(userId));
// Update base restrictions.
- if (newRestrictions != null) {
- // If newRestrictions == the current one, it's probably a bug.
+ if (newBaseRestrictions != null) {
+ // If newBaseRestrictions == the current one, it's probably a bug.
final Bundle prevBaseRestrictions = mBaseUserRestrictions.get(userId);
- Preconditions.checkState(prevBaseRestrictions != newRestrictions);
+ Preconditions.checkState(prevBaseRestrictions != newBaseRestrictions);
Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
- != newRestrictions);
+ != newBaseRestrictions);
- if (!UserRestrictionsUtils.areEqual(prevBaseRestrictions, newRestrictions)) {
- mBaseUserRestrictions.put(userId, newRestrictions);
+ if (updateRestrictionsIfNeededLR(userId, newBaseRestrictions, mBaseUserRestrictions)) {
scheduleWriteUser(getUserDataNoChecks(userId));
}
}
@@ -1746,7 +1783,9 @@
}
}
- final Bundle newDevicePolicyGlobalUserRestrictions = new Bundle();
+ // Pre-O global user restriction were stored as a single bundle (as opposed to per-user
+ // currently), take care of it in case of upgrade.
+ Bundle oldDevicePolicyGlobalUserRestrictions = null;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG) {
@@ -1771,29 +1810,30 @@
if (type == XmlPullParser.START_TAG) {
if (parser.getName().equals(TAG_RESTRICTIONS)) {
synchronized (mGuestRestrictions) {
- UserRestrictionsUtils
- .readRestrictions(parser, mGuestRestrictions);
+ mGuestRestrictions.putAll(
+ UserRestrictionsUtils.readRestrictions(parser));
}
}
break;
}
}
- } else if (name.equals(TAG_DEVICE_POLICY_RESTRICTIONS)) {
- UserRestrictionsUtils.readRestrictions(parser,
- newDevicePolicyGlobalUserRestrictions);
- } else if (name.equals(TAG_GLOBAL_RESTRICTION_OWNER_ID)) {
+ } else if (name.equals(TAG_DEVICE_OWNER_USER_ID)
+ // Legacy name, should only be encountered when upgrading from pre-O.
+ || name.equals(TAG_GLOBAL_RESTRICTION_OWNER_ID)) {
String ownerUserId = parser.getAttributeValue(null, ATTR_ID);
if (ownerUserId != null) {
- mGlobalRestrictionOwnerUserId = Integer.parseInt(ownerUserId);
+ mDeviceOwnerUserId = Integer.parseInt(ownerUserId);
}
+ } else if (name.equals(TAG_DEVICE_POLICY_RESTRICTIONS)) {
+ // Should only happen when upgrading from pre-O (version < 7).
+ oldDevicePolicyGlobalUserRestrictions =
+ UserRestrictionsUtils.readRestrictions(parser);
}
}
}
- synchronized (mRestrictionsLock) {
- mDevicePolicyGlobalUserRestrictions = newDevicePolicyGlobalUserRestrictions;
- }
+
updateUserIds();
- upgradeIfNecessaryLP();
+ upgradeIfNecessaryLP(oldDevicePolicyGlobalUserRestrictions);
} catch (IOException | XmlPullParserException e) {
fallbackToSingleUserLP();
} finally {
@@ -1803,8 +1843,9 @@
/**
* Upgrade steps between versions, either for fixing bugs or changing the data format.
+ * @param oldGlobalUserRestrictions Pre-O global device policy restrictions.
*/
- private void upgradeIfNecessaryLP() {
+ private void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions) {
final int originalVersion = mUserVersion;
int userVersion = mUserVersion;
if (userVersion < 1) {
@@ -1855,6 +1896,23 @@
userVersion = 6;
}
+ if (userVersion < 7) {
+ // Previously only one user could enforce global restrictions, now it is per-user.
+ synchronized (mRestrictionsLock) {
+ if (!UserRestrictionsUtils.isEmpty(oldGlobalUserRestrictions)
+ && mDeviceOwnerUserId != UserHandle.USER_NULL) {
+ mDevicePolicyGlobalUserRestrictions.put(
+ mDeviceOwnerUserId, oldGlobalUserRestrictions);
+ }
+ // ENSURE_VERIFY_APPS is now enforced globally even if put by profile owner, so move
+ // it from local to global bundle for all users who set it.
+ UserRestrictionsUtils.moveRestriction(UserManager.ENSURE_VERIFY_APPS,
+ mDevicePolicyLocalUserRestrictions, mDevicePolicyGlobalUserRestrictions
+ );
+ }
+ userVersion = 7;
+ }
+
if (userVersion < USER_VERSION) {
Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to "
+ USER_VERSION);
@@ -1893,8 +1951,10 @@
Log.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
}
- synchronized (mRestrictionsLock) {
- mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
+ if (!restrictions.isEmpty()) {
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
+ }
}
updateUserIds();
@@ -2004,6 +2064,9 @@
UserRestrictionsUtils.writeRestrictions(serializer,
mDevicePolicyLocalUserRestrictions.get(userInfo.id),
TAG_DEVICE_POLICY_RESTRICTIONS);
+ UserRestrictionsUtils.writeRestrictions(serializer,
+ mDevicePolicyGlobalUserRestrictions.get(userInfo.id),
+ TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
}
if (userData.account != null) {
@@ -2057,13 +2120,9 @@
.writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
}
serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
- synchronized (mRestrictionsLock) {
- UserRestrictionsUtils.writeRestrictions(serializer,
- mDevicePolicyGlobalUserRestrictions, TAG_DEVICE_POLICY_RESTRICTIONS);
- }
- serializer.startTag(null, TAG_GLOBAL_RESTRICTION_OWNER_ID);
- serializer.attribute(null, ATTR_ID, Integer.toString(mGlobalRestrictionOwnerUserId));
- serializer.endTag(null, TAG_GLOBAL_RESTRICTION_OWNER_ID);
+ serializer.startTag(null, TAG_DEVICE_OWNER_USER_ID);
+ serializer.attribute(null, ATTR_ID, Integer.toString(mDeviceOwnerUserId));
+ serializer.endTag(null, TAG_DEVICE_OWNER_USER_ID);
int[] userIdsToWrite;
synchronized (mUsersLock) {
userIdsToWrite = new int[mUsers.size()];
@@ -2125,8 +2184,9 @@
String seedAccountName = null;
String seedAccountType = null;
PersistableBundle seedAccountOptions = null;
- Bundle baseRestrictions = new Bundle();
- Bundle localRestrictions = new Bundle();
+ Bundle baseRestrictions = null;
+ Bundle localRestrictions = null;
+ Bundle globalRestrictions = null;
XmlPullParser parser = Xml.newPullParser();
parser.setInput(is, StandardCharsets.UTF_8.name());
@@ -2187,9 +2247,11 @@
name = parser.getText();
}
} else if (TAG_RESTRICTIONS.equals(tag)) {
- UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
+ baseRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
- UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
+ localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+ } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) {
+ globalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_ACCOUNT.equals(tag)) {
type = parser.next();
if (type == XmlPullParser.TEXT) {
@@ -2224,8 +2286,15 @@
userData.seedAccountOptions = seedAccountOptions;
synchronized (mRestrictionsLock) {
- mBaseUserRestrictions.put(id, baseRestrictions);
- mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
+ if (baseRestrictions != null) {
+ mBaseUserRestrictions.put(id, baseRestrictions);
+ }
+ if (localRestrictions != null) {
+ mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
+ }
+ if (globalRestrictions != null) {
+ mDevicePolicyGlobalUserRestrictions.put(id, globalRestrictions);
+ }
}
return userData;
}
@@ -2731,6 +2800,10 @@
mAppliedUserRestrictions.remove(userHandle);
mCachedEffectiveUserRestrictions.remove(userHandle);
mDevicePolicyLocalUserRestrictions.remove(userHandle);
+ if (mDevicePolicyGlobalUserRestrictions.get(userHandle) != null) {
+ mDevicePolicyGlobalUserRestrictions.remove(userHandle);
+ applyUserRestrictionsForAllUsersLR();
+ }
}
// Update the user list
synchronized (mPackagesLock) {
@@ -3420,6 +3493,9 @@
synchronized (mRestrictionsLock) {
UserRestrictionsUtils.dumpRestrictions(
pw, " ", mBaseUserRestrictions.get(userInfo.id));
+ pw.println(" Device policy global restrictions:");
+ UserRestrictionsUtils.dumpRestrictions(
+ pw, " ", mDevicePolicyGlobalUserRestrictions.get(userInfo.id));
pw.println(" Device policy local restrictions:");
UserRestrictionsUtils.dumpRestrictions(
pw, " ", mDevicePolicyLocalUserRestrictions.get(userInfo.id));
@@ -3448,13 +3524,7 @@
}
}
pw.println();
- pw.println(" Device policy global restrictions:");
- synchronized (mRestrictionsLock) {
- UserRestrictionsUtils
- .dumpRestrictions(pw, " ", mDevicePolicyGlobalUserRestrictions);
- }
- pw.println();
- pw.println(" Global restrictions owner id:" + mGlobalRestrictionOwnerUserId);
+ pw.println(" Device owner id:" + mDeviceOwnerUserId);
pw.println();
pw.println(" Guest restrictions:");
synchronized (mGuestRestrictions) {
@@ -3508,10 +3578,10 @@
private class LocalService extends UserManagerInternal {
@Override
- public void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle localRestrictions,
- @Nullable Bundle globalRestrictions) {
- UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId, localRestrictions,
- globalRestrictions);
+ public void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
+ boolean isDeviceOwner, int cameraRestrictionScope) {
+ UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId, restrictions,
+ isDeviceOwner, cameraRestrictionScope);
}
@Override
@@ -3525,8 +3595,10 @@
public void setBaseUserRestrictionsByDpmsForMigration(
int userId, Bundle baseRestrictions) {
synchronized (mRestrictionsLock) {
- mBaseUserRestrictions.put(userId, new Bundle(baseRestrictions));
- invalidateEffectiveUserRestrictionsLR(userId);
+ if (updateRestrictionsIfNeededLR(
+ userId, new Bundle(baseRestrictions), mBaseUserRestrictions)) {
+ invalidateEffectiveUserRestrictionsLR(userId);
+ }
}
final UserData userData = getUserDataNoChecks(userId);
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index f5b8669..d301463 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -30,11 +30,13 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.service.persistentdata.PersistentDataBlockManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -117,9 +119,10 @@
);
/**
- * User restrictions that can not be set by profile owners.
+ * User restrictions that cannot be set by profile owners of secondary users. When set by DO
+ * they will be applied to all users.
*/
- private static final Set<String> DEVICE_OWNER_ONLY_RESTRICTIONS = Sets.newArraySet(
+ private static final Set<String> PRIMARY_USER_ONLY_RESTRICTIONS = Sets.newArraySet(
UserManager.DISALLOW_BLUETOOTH,
UserManager.DISALLOW_USB_FILE_TRANSFER,
UserManager.DISALLOW_CONFIG_TETHERING,
@@ -163,6 +166,13 @@
UserManager.DISALLOW_ADD_MANAGED_PROFILE
);
+ /*
+ * Special user restrictions that are always applied to all users no matter who sets them.
+ */
+ private static final Set<String> PROFILE_GLOBAL_RESTRICTIONS = Sets.newArraySet(
+ UserManager.ENSURE_VERIFY_APPS
+ );
+
/**
* Throws {@link IllegalArgumentException} if the given restriction name is invalid.
*/
@@ -205,6 +215,12 @@
}
}
+ public static Bundle readRestrictions(XmlPullParser parser) {
+ final Bundle result = new Bundle();
+ readRestrictions(parser, result);
+ return result;
+ }
+
/**
* @return {@code in} itself when it's not null, or an empty bundle (which can writable).
*/
@@ -217,6 +233,14 @@
}
/**
+ * Returns {@code true} if given bundle is not null and contains {@code true} for a given
+ * restriction.
+ */
+ public static boolean contains(@Nullable Bundle in, String restriction) {
+ return in != null && in.getBoolean(restriction);
+ }
+
+ /**
* Creates a copy of the {@code in} Bundle. If {@code in} is null, it'll return an empty
* bundle.
*
@@ -241,6 +265,22 @@
}
/**
+ * Merges a sparse array of restrictions bundles into one.
+ */
+ @Nullable
+ public static Bundle mergeAll(SparseArray<Bundle> restrictions) {
+ if (restrictions.size() == 0) {
+ return null;
+ } else {
+ final Bundle result = new Bundle();
+ for (int i = 0; i < restrictions.size(); i++) {
+ merge(result, restrictions.valueAt(i));
+ }
+ return result;
+ }
+ }
+
+ /**
* @return true if a restriction is settable by device owner.
*/
public static boolean canDeviceOwnerChange(String restriction) {
@@ -254,7 +294,7 @@
public static boolean canProfileOwnerChange(String restriction, int userId) {
return !IMMUTABLE_BY_OWNERS.contains(restriction)
&& !(userId != UserHandle.USER_SYSTEM
- && DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction));
+ && PRIMARY_USER_ONLY_RESTRICTIONS.contains(restriction));
}
/**
@@ -269,8 +309,15 @@
* Takes restrictions that can be set by device owner, and sort them into what should be applied
* globally and what should be applied only on the current user.
*/
- public static void sortToGlobalAndLocal(@Nullable Bundle in, @NonNull Bundle global,
- @NonNull Bundle local) {
+ public static void sortToGlobalAndLocal(@Nullable Bundle in, boolean isDeviceOwner,
+ int cameraRestrictionScope,
+ @NonNull Bundle global, @NonNull Bundle local) {
+ // Camera restriction (as well as all others) goes to at most one bundle.
+ if (cameraRestrictionScope == UserManagerInternal.CAMERA_DISABLED_GLOBALLY) {
+ global.putBoolean(UserManager.DISALLOW_CAMERA, true);
+ } else if (cameraRestrictionScope == UserManagerInternal.CAMERA_DISABLED_LOCALLY) {
+ local.putBoolean(UserManager.DISALLOW_CAMERA, true);
+ }
if (in == null || in.size() == 0) {
return;
}
@@ -278,7 +325,7 @@
if (!in.getBoolean(key)) {
continue;
}
- if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)) {
+ if (isGlobal(isDeviceOwner, key)) {
global.putBoolean(key, true);
} else {
local.putBoolean(key, true);
@@ -287,6 +334,15 @@
}
/**
+ * Whether given user restriction should be enforced globally.
+ */
+ private static boolean isGlobal(boolean isDeviceOwner, String key) {
+ return (isDeviceOwner &&
+ (PRIMARY_USER_ONLY_RESTRICTIONS.contains(key)|| GLOBAL_RESTRICTIONS.contains(key)))
+ || PROFILE_GLOBAL_RESTRICTIONS.contains(key);
+ }
+
+ /**
* @return true if two Bundles contain the same user restriction.
* A null bundle and an empty bundle are considered to be equal.
*/
@@ -485,4 +541,29 @@
pw.println(prefix + "null");
}
}
+
+ /**
+ * Moves a particular restriction from one array of bundles to another, e.g. for all users.
+ */
+ public static void moveRestriction(String restrictionKey, SparseArray<Bundle> srcRestrictions,
+ SparseArray<Bundle> destRestrictions) {
+ for (int i = 0; i < srcRestrictions.size(); i++) {
+ int key = srcRestrictions.keyAt(i);
+ Bundle from = srcRestrictions.valueAt(i);
+ if (contains(from, restrictionKey)) {
+ from.remove(restrictionKey);
+ Bundle to = destRestrictions.get(key);
+ if (to == null) {
+ to = new Bundle();
+ destRestrictions.append(key, to);
+ }
+ to.putBoolean(restrictionKey, true);
+ // Don't keep empty bundles.
+ if (from.isEmpty()) {
+ srcRestrictions.removeAt(i);
+ i--;
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
new file mode 100644
index 0000000..133881a
--- /dev/null
+++ b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * 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.policy;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.ContentObserver;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import android.widget.Toast;
+import com.android.internal.R;
+
+import java.util.List;
+
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+
+/**
+ * Class to help manage the accessibility shortcut
+ */
+public class AccessibilityShortcutController {
+ private static final String TAG = "AccessibilityShortcutController";
+
+ private final Context mContext;
+ private AlertDialog mAlertDialog;
+ private boolean mIsShortcutEnabled;
+ // Visible for testing
+ public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
+
+ public static String getTargetServiceComponentNameString(
+ Context context, int userId) {
+ final String currentShortcutServiceId = Settings.Secure.getStringForUser(
+ context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ userId);
+ if (currentShortcutServiceId != null) {
+ return currentShortcutServiceId;
+ }
+ return context.getString(R.string.config_defaultAccessibilityService);
+ }
+
+ public AccessibilityShortcutController(Context context, Handler handler) {
+ mContext = context;
+
+ // Keep track of state of shortcut
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
+ false,
+ new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onSettingsChanged();
+ }
+ },
+ UserHandle.USER_ALL);
+ updateShortcutEnabled();
+ }
+
+ public boolean isAccessibilityShortcutAvailable() {
+ return mIsShortcutEnabled;
+ }
+
+ public void onSettingsChanged() {
+ updateShortcutEnabled();
+ }
+
+ /**
+ * Called when the accessibility shortcut is activated
+ */
+ public void performAccessibilityShortcut() {
+ Slog.d(TAG, "Accessibility shortcut activated");
+ final ContentResolver cr = mContext.getContentResolver();
+ final int userId = ActivityManager.getCurrentUser();
+ final int dialogAlreadyShown = Settings.Secure.getIntForUser(
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+ final Ringtone tone =
+ RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_NOTIFICATION_URI);
+ if (tone != null) {
+ tone.setAudioAttributes(new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
+ .build());
+ tone.play();
+ }
+ if (dialogAlreadyShown == 0) {
+ // The first time, we show a warning rather than toggle the service to give the user a
+ // chance to turn off this feature before stuff gets enabled.
+ mAlertDialog = createShortcutWarningDialog(userId);
+ if (mAlertDialog == null) {
+ return;
+ }
+ Window w = mAlertDialog.getWindow();
+ WindowManager.LayoutParams attr = w.getAttributes();
+ attr.type = TYPE_KEYGUARD_DIALOG;
+ w.setAttributes(attr);
+ mAlertDialog.show();
+ Settings.Secure.putIntForUser(
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1, userId);
+ } else {
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ mAlertDialog = null;
+ }
+
+ // Show a toast alerting the user to what's happening
+ final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
+ if (serviceInfo == null) {
+ Slog.e(TAG, "Accessibility shortcut set to invalid service");
+ return;
+ }
+ String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo)
+ ? R.string.accessibility_shortcut_disabling_service
+ : R.string.accessibility_shortcut_enabling_service);
+ String toastMessage = String.format(toastMessageFormatString,
+ serviceInfo.getResolveInfo()
+ .loadLabel(mContext.getPackageManager()).toString());
+ mFrameworkObjectProvider.makeToastFromText(mContext, toastMessage, Toast.LENGTH_LONG)
+ .show();
+
+ mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext)
+ .performAccessibilityShortcut();
+ }
+ }
+
+ private void updateShortcutEnabled() {
+ mIsShortcutEnabled = !TextUtils.isEmpty(getTargetServiceComponentNameString(
+ mContext, UserHandle.myUserId()));
+ }
+
+ private AlertDialog createShortcutWarningDialog(int userId) {
+ final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
+
+ if (serviceInfo == null) {
+ return null;
+ }
+
+ final String warningMessage = String.format(
+ mContext.getString(R.string.accessibility_shortcut_toogle_warning),
+ serviceInfo.getResolveInfo().loadLabel(mContext.getPackageManager()).toString());
+ final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(mContext)
+ .setTitle(R.string.accessibility_shortcut_warning_dialog_title)
+ .setMessage(warningMessage)
+ .setCancelable(false)
+ .setPositiveButton(R.string.leave_accessibility_shortcut_on, null)
+ .setNegativeButton(R.string.disable_accessibility_shortcut,
+ (DialogInterface d, int which) -> {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
+ userId);
+ })
+ .setOnCancelListener((DialogInterface d) -> {
+ // If canceled, treat as if the dialog has never been shown
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+ })
+ .create();
+ return alertDialog;
+ }
+
+ private AccessibilityServiceInfo getInfoForTargetService() {
+ final String currentShortcutServiceString = getTargetServiceComponentNameString(
+ mContext, UserHandle.myUserId());
+ if (currentShortcutServiceString == null) {
+ return null;
+ }
+ AccessibilityManager accessibilityManager =
+ mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext);
+ return accessibilityManager.getInstalledServiceInfoWithComponentName(
+ ComponentName.unflattenFromString(currentShortcutServiceString));
+ }
+
+ private boolean isServiceEnabled(AccessibilityServiceInfo serviceInfo) {
+ AccessibilityManager accessibilityManager =
+ mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext);
+ return accessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK).contains(serviceInfo);
+ }
+
+ // Class to allow mocking of static framework calls
+ public static class FrameworkObjectProvider {
+ public AccessibilityManager getAccessibilityManagerInstance(Context context) {
+ return AccessibilityManager.getInstance(context);
+ }
+
+ public AlertDialog.Builder getAlertDialogBuilder(Context context) {
+ return new AlertDialog.Builder(context);
+ }
+
+ public Toast makeToastFromText(Context context, CharSequence charSequence, int duration) {
+ return Toast.makeText(context, charSequence, duration);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/EnableAccessibilityController.java b/services/core/java/com/android/server/policy/EnableAccessibilityController.java
deleted file mode 100644
index 6b203a9..0000000
--- a/services/core/java/com/android/server/policy/EnableAccessibilityController.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2012 Google Inc.
- *
- * 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.policy;
-
-import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.speech.tts.TextToSpeech;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.IWindowManager;
-import android.view.MotionEvent;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.view.WindowManagerInternal;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-
-import com.android.internal.R;
-import com.android.server.LocalServices;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-public class EnableAccessibilityController {
- private static final String TAG = "EnableAccessibilityController";
-
- private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
- private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;
-
- public static final int MESSAGE_SPEAK_WARNING = 1;
- public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2;
- public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_SPEAK_WARNING: {
- String text = mContext.getString(R.string.continue_to_enable_accessibility);
- mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
- } break;
- case MESSAGE_SPEAK_ENABLE_CANCELED: {
- String text = mContext.getString(R.string.enable_accessibility_canceled);
- mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
- } break;
- case MESSAGE_ENABLE_ACCESSIBILITY: {
- enableAccessibility();
- mTone.play();
- mTts.speak(mContext.getString(R.string.accessibility_enabled),
- TextToSpeech.QUEUE_FLUSH, null);
- } break;
- }
- }
- };
-
- private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
- .Stub.asInterface(ServiceManager.getService("accessibility"));
-
-
- private final Context mContext;
- private final Runnable mOnAccessibilityEnabledCallback;
- private final UserManager mUserManager;
- private final TextToSpeech mTts;
- private final Ringtone mTone;
-
- private final float mTouchSlop;
-
- private boolean mDestroyed;
- private boolean mCanceled;
-
- private float mFirstPointerDownX;
- private float mFirstPointerDownY;
- private float mSecondPointerDownX;
- private float mSecondPointerDownY;
-
- public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) {
- mContext = context;
- mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback;
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
- @Override
- public void onInit(int status) {
- if (mDestroyed) {
- mTts.shutdown();
- }
- }
- });
- mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
- mTone.setStreamType(AudioManager.STREAM_MUSIC);
- mTouchSlop = context.getResources().getDimensionPixelSize(
- R.dimen.accessibility_touch_slop);
- }
-
- public static boolean canEnableAccessibilityViaGesture(Context context) {
- AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
- // Accessibility is enabled and there is an enabled speaking
- // accessibility service, then we have nothing to do.
- if (accessibilityManager.isEnabled()
- && !accessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
- return false;
- }
- // If the global gesture is enabled and there is a speaking service
- // installed we are good to go, otherwise there is nothing to do.
- return Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
- && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
- }
-
- public static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
- Context context) {
- List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
- services.addAll(AccessibilityManager.getInstance(context)
- .getInstalledAccessibilityServiceList());
- Iterator<AccessibilityServiceInfo> iterator = services.iterator();
- while (iterator.hasNext()) {
- AccessibilityServiceInfo service = iterator.next();
- if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
- iterator.remove();
- }
- }
- return services;
- }
-
- public void onDestroy() {
- mDestroyed = true;
- }
-
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
- && event.getPointerCount() == 2) {
- mFirstPointerDownX = event.getX(0);
- mFirstPointerDownY = event.getY(0);
- mSecondPointerDownX = event.getX(1);
- mSecondPointerDownY = event.getY(1);
- mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
- SPEAK_WARNING_DELAY_MILLIS);
- mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
- ENABLE_ACCESSIBILITY_DELAY_MILLIS);
- return true;
- }
- return false;
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- final int pointerCount = event.getPointerCount();
- final int action = event.getActionMasked();
- if (mCanceled) {
- if (action == MotionEvent.ACTION_UP) {
- mCanceled = false;
- }
- return true;
- }
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN: {
- if (pointerCount > 2) {
- cancel();
- }
- } break;
- case MotionEvent.ACTION_MOVE: {
- final float firstPointerMove = MathUtils.dist(event.getX(0),
- event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
- if (Math.abs(firstPointerMove) > mTouchSlop) {
- cancel();
- }
- final float secondPointerMove = MathUtils.dist(event.getX(1),
- event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
- if (Math.abs(secondPointerMove) > mTouchSlop) {
- cancel();
- }
- } break;
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_CANCEL: {
- cancel();
- } break;
- }
- return true;
- }
-
- private void cancel() {
- mCanceled = true;
- if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
- mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
- } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
- mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
- }
- mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
- }
-
- private void enableAccessibility() {
- if (enableAccessibility(mContext)) {
- mOnAccessibilityEnabledCallback.run();
- }
- }
-
- public static boolean enableAccessibility(Context context) {
- final IAccessibilityManager accessibilityManager = IAccessibilityManager
- .Stub.asInterface(ServiceManager.getService("accessibility"));
- final WindowManagerInternal windowManager = LocalServices.getService(
- WindowManagerInternal.class);
- final UserManager userManager = (UserManager) context.getSystemService(
- Context.USER_SERVICE);
- ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
- if (componentName == null) {
- return false;
- }
-
- boolean keyguardLocked = windowManager.isKeyguardLocked();
- final boolean hasMoreThanOneUser = userManager.getUsers().size() > 1;
- try {
- if (!keyguardLocked || !hasMoreThanOneUser) {
- final int userId = ActivityManager.getCurrentUser();
- accessibilityManager.enableAccessibilityService(componentName, userId);
- } else if (keyguardLocked) {
- accessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
- componentName, true /* enableTouchExploration */);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "cannot enable accessibilty: " + e);
- }
-
- return true;
- }
-
- public static void disableAccessibility(Context context) {
- final IAccessibilityManager accessibilityManager = IAccessibilityManager
- .Stub.asInterface(ServiceManager.getService("accessibility"));
- ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
- if (componentName == null) {
- return;
- }
-
- final int userId = ActivityManager.getCurrentUser();
- try {
- accessibilityManager.disableAccessibilityService(componentName, userId);
- } catch (RemoteException e) {
- Log.e(TAG, "cannot disable accessibility " + e);
- }
- }
-
- public static boolean isAccessibilityEnabled(Context context) {
- final AccessibilityManager accessibilityManager =
- context.getSystemService(AccessibilityManager.class);
- List enabledServices = accessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_SPOKEN);
- return enabledServices != null && !enabledServices.isEmpty();
- }
-
- @Nullable
- public static ComponentName getInstalledSpeakingAccessibilityServiceComponent(
- Context context) {
- List<AccessibilityServiceInfo> services =
- getInstalledSpeakingAccessibilityServices(context);
- if (services.isEmpty()) {
- return null;
- }
-
- ServiceInfo serviceInfo = services.get(0).getResolveInfo().serviceInfo;
- return new ComponentName(serviceInfo.packageName, serviceInfo.name);
- }
-}
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index d4adcc4..335a230 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -44,7 +44,6 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -59,12 +58,9 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.TypedValue;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -1194,21 +1190,14 @@
private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
private final Context mContext;
- private final int mWindowTouchSlop;
private final AlertController mAlert;
private final MyAdapter mAdapter;
- private EnableAccessibilityController mEnableAccessibilityController;
-
- private boolean mIntercepted;
- private boolean mCancelOnUp;
-
public GlobalActionsDialog(Context context, AlertParams params) {
super(context, getDialogTheme(context));
mContext = getContext();
mAlert = AlertController.create(mContext, this, getWindow());
mAdapter = (MyAdapter) params.mAdapter;
- mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
params.apply(mAlert);
}
@@ -1221,76 +1210,10 @@
@Override
protected void onStart() {
- // If global accessibility gesture can be performed, we will take care
- // of dismissing the dialog on touch outside. This is because the dialog
- // is dismissed on the first down while the global gesture is a long press
- // with two fingers anywhere on the screen.
- if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
- mEnableAccessibilityController = new EnableAccessibilityController(mContext,
- new Runnable() {
- @Override
- public void run() {
- dismiss();
- }
- });
- super.setCanceledOnTouchOutside(false);
- } else {
- mEnableAccessibilityController = null;
- super.setCanceledOnTouchOutside(true);
- }
-
+ super.setCanceledOnTouchOutside(true);
super.onStart();
}
- @Override
- protected void onStop() {
- if (mEnableAccessibilityController != null) {
- mEnableAccessibilityController.onDestroy();
- }
- super.onStop();
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (mEnableAccessibilityController != null) {
- final int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN) {
- View decor = getWindow().getDecorView();
- final int eventX = (int) event.getX();
- final int eventY = (int) event.getY();
- if (eventX < -mWindowTouchSlop
- || eventY < -mWindowTouchSlop
- || eventX >= decor.getWidth() + mWindowTouchSlop
- || eventY >= decor.getHeight() + mWindowTouchSlop) {
- mCancelOnUp = true;
- }
- }
- try {
- if (!mIntercepted) {
- mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
- if (mIntercepted) {
- final long now = SystemClock.uptimeMillis();
- event = MotionEvent.obtain(now, now,
- MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
- event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- mCancelOnUp = true;
- }
- } else {
- return mEnableAccessibilityController.onTouchEvent(event);
- }
- } finally {
- if (action == MotionEvent.ACTION_UP) {
- if (mCancelOnUp) {
- cancel();
- }
- mCancelOnUp = false;
- mIntercepted = false;
- }
- }
- }
- return super.dispatchTouchEvent(event);
- }
-
public ListView getListView() {
return mAlert.getListView();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4b2b184..32b8c9b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -149,8 +149,6 @@
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioService;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
import android.media.session.MediaSessionLegacyHelper;
import android.os.Binder;
import android.os.Build;
@@ -441,6 +439,9 @@
/** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */
boolean mEnableShiftMenuBugReports = false;
+ /** Controller that supports enabling an AccessibilityService by holding down the volume keys */
+ private AccessibilityShortcutController mAccessibilityShortcutController;
+
boolean mSafeMode;
WindowState mStatusBar = null;
int mStatusBarHeight;
@@ -748,7 +749,10 @@
private boolean mScreenshotChordVolumeDownKeyTriggered;
private long mScreenshotChordVolumeDownKeyTime;
private boolean mScreenshotChordVolumeDownKeyConsumed;
- private boolean mScreenshotChordVolumeUpKeyTriggered;
+ private boolean mA11yShortcutChordVolumeUpKeyTriggered;
+ private long mA11yShortcutChordVolumeUpKeyTime;
+ private boolean mA11yShortcutChordVolumeUpKeyConsumed;
+
private boolean mScreenshotChordPowerKeyTriggered;
private long mScreenshotChordPowerKeyTime;
@@ -794,6 +798,7 @@
private static final int MSG_BACK_LONG_PRESS = 18;
private static final int MSG_DISPOSE_INPUT_CONSUMER = 19;
private static final int MSG_BACK_DELAYED_PRESS = 20;
+ private static final int MSG_ACCESSIBILITY_SHORTCUT = 21;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -869,6 +874,9 @@
backMultiPressAction((Long) msg.obj, msg.arg1);
finishBackKeyPress();
break;
+ case MSG_ACCESSIBILITY_SHORTCUT:
+ accessibilityShortcutActivated();
+ break;
}
}
}
@@ -1213,7 +1221,7 @@
// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
- || mScreenshotChordVolumeUpKeyTriggered || gesturedServiceIntercepted;
+ || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted;
if (!mPowerKeyHandled) {
if (interactive) {
// When interactive, we're already awake.
@@ -1406,9 +1414,7 @@
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
- if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
- performAuditoryFeedbackForAccessibilityIfNeed();
- }
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
showGlobalActionsInternal();
break;
case LONG_PRESS_POWER_SHUT_OFF:
@@ -1439,6 +1445,10 @@
}
}
+ private void accessibilityShortcutActivated() {
+ mAccessibilityShortcutController.performAccessibilityShortcut();
+ }
+
private void disposeInputConsumer(InputConsumer inputConsumer) {
if (inputConsumer != null) {
inputConsumer.dismiss();
@@ -1484,7 +1494,7 @@
private void interceptScreenshotChord() {
if (mScreenshotChordEnabled
&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered
- && !mScreenshotChordVolumeUpKeyTriggered) {
+ && !mA11yShortcutChordVolumeUpKeyTriggered) {
final long now = SystemClock.uptimeMillis();
if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&& now <= mScreenshotChordPowerKeyTime
@@ -1497,6 +1507,22 @@
}
}
+ private void interceptAccessibilityShortcutChord() {
+ if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable()
+ && mScreenshotChordVolumeDownKeyTriggered && mA11yShortcutChordVolumeUpKeyTriggered
+ && !mScreenshotChordPowerKeyTriggered) {
+ final long now = SystemClock.uptimeMillis();
+ if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
+ && now <= mA11yShortcutChordVolumeUpKeyTime
+ + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
+ mScreenshotChordVolumeDownKeyConsumed = true;
+ mA11yShortcutChordVolumeUpKeyConsumed = true;
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT),
+ ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+ }
+ }
+ }
+
private long getScreenshotChordLongPressDelay() {
if (mKeyguardDelegate.isShowing()) {
// Double the time it takes to take a screenshot from the keyguard
@@ -1510,13 +1536,15 @@
mHandler.removeCallbacks(mScreenshotRunnable);
}
+ private void cancelPendingAccessibilityShortcutAction() {
+ mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT);
+ }
+
private final Runnable mEndCallLongPress = new Runnable() {
@Override
public void run() {
mEndCallKeyHandled = true;
- if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
- performAuditoryFeedbackForAccessibilityIfNeed();
- }
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
showGlobalActionsInternal();
}
};
@@ -1698,7 +1726,8 @@
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
-
+ mAccessibilityShortcutController =
+ new AccessibilityShortcutController(mContext, new Handler());
// Init display burn-in protection
boolean burnInProtectionEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_enableBurnInProtection);
@@ -3251,6 +3280,33 @@
}
}
+ // If an accessibility shortcut might be partially complete, hold off dispatching until we
+ // know if it is complete or not
+ if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable()
+ && (flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ if (mScreenshotChordVolumeDownKeyTriggered ^ mA11yShortcutChordVolumeUpKeyTriggered) {
+ final long now = SystemClock.uptimeMillis();
+ final long timeoutTime = (mScreenshotChordVolumeDownKeyTriggered
+ ? mScreenshotChordVolumeDownKeyTime : mA11yShortcutChordVolumeUpKeyTime)
+ + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;
+ if (now < timeoutTime) {
+ return timeoutTime - now;
+ }
+ }
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mScreenshotChordVolumeDownKeyConsumed) {
+ if (!down) {
+ mScreenshotChordVolumeDownKeyConsumed = false;
+ }
+ return -1;
+ }
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mA11yShortcutChordVolumeUpKeyConsumed) {
+ if (!down) {
+ mA11yShortcutChordVolumeUpKeyConsumed = false;
+ }
+ return -1;
+ }
+ }
+
// Cancel any pending meta actions if we see any other keys being pressed between the down
// of the meta key and its corresponding up.
if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
@@ -5760,22 +5816,32 @@
mScreenshotChordVolumeDownKeyConsumed = false;
cancelPendingPowerKeyAction();
interceptScreenshotChord();
+ if (!keyguardActive) {
+ interceptAccessibilityShortcutChord();
+ }
}
} else {
mScreenshotChordVolumeDownKeyTriggered = false;
cancelPendingScreenshotChordAction();
+ cancelPendingAccessibilityShortcutAction();
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
if (down) {
- if (interactive && !mScreenshotChordVolumeUpKeyTriggered
+ if (interactive && !mA11yShortcutChordVolumeUpKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- mScreenshotChordVolumeUpKeyTriggered = true;
+ mA11yShortcutChordVolumeUpKeyTriggered = true;
+ mA11yShortcutChordVolumeUpKeyTime = event.getDownTime();
+ mA11yShortcutChordVolumeUpKeyConsumed = false;
cancelPendingPowerKeyAction();
cancelPendingScreenshotChordAction();
+ if (!keyguardActive) {
+ interceptAccessibilityShortcutChord();
+ }
}
} else {
- mScreenshotChordVolumeUpKeyTriggered = false;
+ mA11yShortcutChordVolumeUpKeyTriggered = false;
cancelPendingScreenshotChordAction();
+ cancelPendingAccessibilityShortcutAction();
}
}
if (down) {
@@ -5863,6 +5929,8 @@
}
case KeyEvent.KEYCODE_POWER: {
+ // Any activity on the power button stops the accessibility shortcut
+ cancelPendingAccessibilityShortcutAction();
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
@@ -7416,31 +7484,11 @@
}
}
- private void performAuditoryFeedbackForAccessibilityIfNeed() {
- if (!isGlobalAccessibilityGestureEnabled()) {
- return;
- }
- AudioManager audioManager = (AudioManager) mContext.getSystemService(
- Context.AUDIO_SERVICE);
- if (audioManager.isSilentMode()) {
- return;
- }
- Ringtone ringTone = RingtoneManager.getRingtone(mContext,
- Settings.System.DEFAULT_NOTIFICATION_URI);
- ringTone.setStreamType(AudioManager.STREAM_MUSIC);
- ringTone.play();
- }
-
private boolean isTheaterModeEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0) == 1;
}
- private boolean isGlobalAccessibilityGestureEnabled() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
- }
-
private boolean areSystemNavigationKeysEnabled() {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java
index 23be9a3..5a1f473 100644
--- a/services/core/java/com/android/server/storage/AppFuseBridge.java
+++ b/services/core/java/com/android/server/storage/AppFuseBridge.java
@@ -16,79 +16,95 @@
package com.android.server.storage;
-import android.annotation.CallSuper;
-import android.annotation.WorkerThread;
-import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-import com.android.internal.os.AppFuseMount;
+import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
-
import java.io.File;
-import java.io.FileDescriptor;
import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.BlockingQueue;
-public class AppFuseBridge implements Runnable {
- private static final String TAG = AppFuseBridge.class.getSimpleName();
-
- private final FileDescriptor mDeviceFd;
- private final FileDescriptor mProxyFd;
- private final CountDownLatch mMountLatch = new CountDownLatch(1);
+/**
+ * Runnable that delegates FUSE command from the kernel to application.
+ * run() blocks until all opened files on the FUSE mount point are closed. So this should be run in
+ * a separated thread.
+ */
+public class AppFuseBridge implements Runnable, AutoCloseable {
+ public static final String TAG = "AppFuseBridge";
/**
- * @param deviceFd FD of /dev/fuse. Ownership of fd is taken by AppFuseBridge.
- * @param proxyFd FD of socket pair. Ownership of fd is taken by AppFuseBridge.
+ * The path AppFuse is mounted to.
+ * The first number is UID who is mounting the FUSE.
+ * THe second number is mount ID.
+ * The path must be sync with vold.
*/
- private AppFuseBridge(FileDescriptor deviceFd, FileDescriptor proxyFd) {
- mDeviceFd = deviceFd;
+ private static final String APPFUSE_MOUNT_NAME_TEMPLATE = "/mnt/appfuse/%d_%d";
+
+ private final IMountScope mMountScope;
+ private final ParcelFileDescriptor mProxyFd;
+ private final BlockingQueue<Boolean> mChannel;
+
+ /**
+ * @param mountScope Listener to unmount mount point.
+ * @param proxyFd FD of socket pair. Ownership of FD is taken by AppFuseBridge.
+ * @param channel Channel that the runnable send mount result to.
+ */
+ public AppFuseBridge(
+ IMountScope mountScope, ParcelFileDescriptor proxyFd, BlockingQueue<Boolean> channel) {
+ Preconditions.checkNotNull(mountScope);
+ Preconditions.checkNotNull(proxyFd);
+ Preconditions.checkNotNull(channel);
+ mMountScope = mountScope;
mProxyFd = proxyFd;
- }
-
- public static AppFuseMount startMessageLoop(
- int uid,
- String name,
- FileDescriptor deviceFd,
- Handler handler,
- ParcelFileDescriptor.OnCloseListener listener)
- throws IOException, ErrnoException, InterruptedException {
- final FileDescriptor localFd = new FileDescriptor();
- final FileDescriptor remoteFd = new FileDescriptor();
- // Needs to specify OsConstants.SOCK_SEQPACKET to keep message boundaries.
- Os.socketpair(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0, remoteFd, localFd);
-
- // Caller must invoke #start() after instantiate AppFuseBridge.
- // Otherwise FDs will be leaked.
- final AppFuseBridge bridge = new AppFuseBridge(deviceFd, localFd);
- final Thread thread = new Thread(bridge, TAG);
- thread.start();
- try {
- bridge.mMountLatch.await();
- } catch (InterruptedException error) {
- throw error;
- }
- return new AppFuseMount(
- new File("/mnt/appfuse/" + uid + "_" + name),
- ParcelFileDescriptor.fromFd(remoteFd, handler, listener));
+ mChannel = channel;
}
@Override
public void run() {
- // deviceFd and proxyFd must be closed in native_start_loop.
- final int deviceFd = mDeviceFd.getInt$();
- final int proxyFd = mProxyFd.getInt$();
- mDeviceFd.setInt$(-1);
- mProxyFd.setInt$(-1);
- native_start_loop(deviceFd, proxyFd);
+ try {
+ // deviceFd and proxyFd must be closed in native_start_loop.
+ native_start_loop(
+ mMountScope.getDeviceFileDescriptor().detachFd(),
+ mProxyFd.detachFd());
+ } finally {
+ close();
+ }
+ }
+
+ public static ParcelFileDescriptor openFile(int uid, int mountId, int fileId, int mode)
+ throws FileNotFoundException {
+ final File mountPoint = getMountPoint(uid, mountId);
+ try {
+ if (Os.stat(mountPoint.getPath()).st_ino != 1) {
+ throw new FileNotFoundException("Could not find bridge mount point.");
+ }
+ } catch (ErrnoException e) {
+ throw new FileNotFoundException(
+ "Failed to stat mount point: " + mountPoint.getParent());
+ }
+ return ParcelFileDescriptor.open(new File(mountPoint, String.valueOf(fileId)), mode);
+ }
+
+ private static File getMountPoint(int uid, int mountId) {
+ return new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId));
+ }
+
+ @Override
+ public void close() {
+ IoUtils.closeQuietly(mMountScope);
+ IoUtils.closeQuietly(mProxyFd);
+ // Invoke countDown here in case where close is invoked before mount.
+ mChannel.offer(false);
}
// Used by com_android_server_storage_AppFuse.cpp.
private void onMount() {
- mMountLatch.countDown();
+ mChannel.offer(true);
+ }
+
+ public static interface IMountScope extends AutoCloseable {
+ ParcelFileDescriptor getDeviceFileDescriptor();
}
private native boolean native_start_loop(int deviceFd, int proxyFd);
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 7ba95a4..02b46ec 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -271,21 +271,20 @@
}
@Override
- public void setMultiProcessEnabledFromContext(Context context) {
- boolean enableMultiProcess = false;
- try {
- enableMultiProcess = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.WEBVIEW_MULTIPROCESS) == 1;
- } catch (Settings.SettingNotFoundException ex) {
- }
- WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
+ public int getMultiProcessSetting(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.WEBVIEW_MULTIPROCESS, 0);
}
@Override
- public void registerContentObserver(Context context, ContentObserver contentObserver) {
- context.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.WEBVIEW_MULTIPROCESS),
- false, contentObserver);
+ public void setMultiProcessSetting(Context context, int value) {
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.WEBVIEW_MULTIPROCESS, value);
+ }
+
+ @Override
+ public void notifyZygote(boolean enableMultiProcess) {
+ WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
}
// flags declaring we want extra info from the package manager for webview providers
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index 2d7a998..fd137eb 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -50,6 +50,7 @@
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
- public void setMultiProcessEnabledFromContext(Context context);
- public void registerContentObserver(Context context, ContentObserver contentObserver);
+ public int getMultiProcessSetting(Context context);
+ public void setMultiProcessSetting(Context context, int value);
+ public void notifyZygote(boolean enableMultiProcess);
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 0a7454f..311570e 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -261,6 +261,32 @@
}
}
+ @Override // Binder call
+ public boolean isMultiProcessEnabled() {
+ return WebViewUpdateService.this.mImpl.isMultiProcessEnabled();
+ }
+
+ @Override // Binder call
+ public void enableMultiProcess(boolean enable) {
+ if (getContext().checkCallingPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: enableMultiProcess() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ long callingId = Binder.clearCallingIdentity();
+ try {
+ WebViewUpdateService.this.mImpl.enableMultiProcess(enable);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 1a77c68..edfb11c 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -20,8 +20,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.util.Base64;
@@ -77,7 +75,6 @@
private SystemInterface mSystemInterface;
private WebViewUpdater mWebViewUpdater;
- private SettingsObserver mSettingsObserver;
final private Context mContext;
public WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
@@ -97,10 +94,7 @@
void prepareWebViewInSystemServer() {
updateFallbackStateOnBoot();
mWebViewUpdater.prepareWebViewInSystemServer();
-
- // Register for changes in the multiprocess developer option. This has to be done
- // here, since the update service gets created before the ContentResolver service.
- mSettingsObserver = new SettingsObserver();
+ mSystemInterface.notifyZygote(isMultiProcessEnabled());
}
private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
@@ -247,6 +241,19 @@
&& packageName.equals(fallbackProvider.packageName));
}
+ boolean isMultiProcessEnabled() {
+ return mSystemInterface.getMultiProcessSetting(mContext) != 0;
+ }
+
+ void enableMultiProcess(boolean enable) {
+ PackageInfo current = getCurrentWebViewPackage();
+ mSystemInterface.setMultiProcessSetting(mContext, enable ? 1 : 0);
+ mSystemInterface.notifyZygote(enable);
+ if (current != null) {
+ mSystemInterface.killPackageDependents(current.packageName);
+ }
+ }
+
/**
* Class that decides what WebView implementation to use and prepares that implementation for
* use.
@@ -740,31 +747,6 @@
}
/**
- * Watches for changes in the WEBVIEW_MULTIPROCESS setting and lets
- * the WebViewZygote know, so it can start or stop the zygote process
- * appropriately.
- */
- private class SettingsObserver extends ContentObserver {
- SettingsObserver() {
- super(new Handler());
-
- mSystemInterface.registerContentObserver(mContext, this);
-
- // Push the current value of the setting immediately.
- notifyZygote();
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- notifyZygote();
- }
-
- private void notifyZygote() {
- mSystemInterface.setMultiProcessEnabledFromContext(mContext);
- }
- }
-
- /**
* Dump the state of this Service.
*/
void dumpState(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 10d1d8b..ac9859d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -445,6 +445,7 @@
mService.mOpeningApps.remove(this);
mService.mUnknownAppVisibilityController.appRemoved(this);
+ mService.mTaskSnapshotController.onAppRemoved(this);
waitingToShow = false;
if (mService.mClosingApps.contains(this)) {
delayed = true;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 2d50e3a..4b680e5 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -638,6 +638,11 @@
}
@Override
+ TaskWindowContainerController getController() {
+ return (TaskWindowContainerController) super.getController();
+ }
+
+ @Override
public String toString() {
return "{taskId=" + mTaskId + " appTokens=" + mChildren + " mdr=" + mDeferRemoval + "}";
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 994a155..c86229b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -20,6 +20,8 @@
import android.app.ActivityManager.TaskSnapshot;
import android.util.ArrayMap;
+import java.io.PrintWriter;
+
/**
* Caches snapshots. See {@link TaskSnapshotController}.
* <p>
@@ -27,13 +29,65 @@
*/
class TaskSnapshotCache {
- private final ArrayMap<Task, TaskSnapshot> mCache = new ArrayMap<>();
+ private final ArrayMap<AppWindowToken, Task> mAppTaskMap = new ArrayMap<>();
+ private final ArrayMap<Task, CacheEntry> mCache = new ArrayMap<>();
void putSnapshot(Task task, TaskSnapshot snapshot) {
- mCache.put(task, snapshot);
+ final CacheEntry entry = mCache.get(task);
+ if (entry != null) {
+ mAppTaskMap.remove(entry.topApp);
+ }
+ final AppWindowToken top = task.getTopChild();
+ mAppTaskMap.put(top, task);
+ mCache.put(task, new CacheEntry(snapshot, task.getTopChild()));
}
@Nullable TaskSnapshot getSnapshot(Task task) {
- return mCache.get(task);
+ final CacheEntry entry = mCache.get(task);
+ return entry != null ? entry.snapshot : null;
+ }
+
+ /**
+ * Cleans the cache after an app window token's process died.
+ */
+ void cleanCache(AppWindowToken wtoken) {
+ final Task task = mAppTaskMap.get(wtoken);
+ if (task != null) {
+ removeEntry(task);
+ }
+ }
+
+ private void removeEntry(Task task) {
+ final CacheEntry entry = mCache.get(task);
+ if (entry != null) {
+ mAppTaskMap.remove(entry.topApp);
+ mCache.remove(task);
+ }
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ final String doublePrefix = prefix + " ";
+ final String triplePrefix = doublePrefix + " ";
+ pw.println(prefix + "SnapshotCache");
+ for (int i = mCache.size() - 1; i >= 0; i--) {
+ final CacheEntry entry = mCache.valueAt(i);
+ pw.println(doublePrefix + "Entry taskId=" + mCache.keyAt(i).mTaskId);
+ pw.println(triplePrefix + "topApp=" + entry.topApp);
+ pw.println(triplePrefix + "snapshot=" + entry.snapshot);
+ }
+ }
+
+ private static final class CacheEntry {
+
+ /** The snapshot. */
+ final TaskSnapshot snapshot;
+
+ /** The app token that was on top of the task when the snapshot was taken */
+ final AppWindowToken topApp;
+
+ CacheEntry(TaskSnapshot snapshot, AppWindowToken topApp) {
+ this.snapshot = snapshot;
+ this.topApp = topApp;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 68aceae..df8679d 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -17,23 +17,18 @@
package com.android.server.wm;
import static android.app.ActivityManager.ENABLE_TASK_SNAPSHOTS;
-import static android.graphics.Bitmap.Config.ARGB_8888;
-import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
-import static android.graphics.GraphicBuffer.USAGE_SW_READ_NEVER;
-import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_NEVER;
-import static android.graphics.PixelFormat.RGBA_8888;
import android.annotation.Nullable;
import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.GraphicBuffer;
import android.util.ArraySet;
import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
+
/**
* When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
* put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
@@ -74,6 +69,9 @@
final TaskSnapshot snapshot = snapshotTask(task);
if (snapshot != null) {
mCache.putSnapshot(task, snapshot);
+ if (task.getController() != null) {
+ task.getController().reportSnapshotChanged(snapshot);
+ }
}
}
}
@@ -92,7 +90,7 @@
}
private TaskSnapshot snapshotTask(Task task) {
- final AppWindowToken top = (AppWindowToken) task.getTop();
+ final AppWindowToken top = task.getTopChild();
if (top == null) {
return null;
}
@@ -125,4 +123,25 @@
private boolean canSnapshotTask(Task task) {
return !StackId.isHomeOrRecentsStack(task.mStack.mStackId);
}
+
+ /**
+ * Called when an {@link AppWindowToken} has been removed.
+ */
+ void onAppRemoved(AppWindowToken wtoken) {
+ // TODO: Clean from both recents and running cache.
+ mCache.cleanCache(wtoken);
+ }
+
+ /**
+ * Called when the process of an {@link AppWindowToken} has died.
+ */
+ void onAppDied(AppWindowToken wtoken) {
+
+ // TODO: Only clean from running cache.
+ mCache.cleanCache(wtoken);
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ mCache.dump(pw, prefix);
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index c3e3141..4a09423 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -35,7 +35,6 @@
import android.os.Message;
import android.os.RemoteException;
import android.util.Slog;
-import android.view.Display;
import android.view.IWindowSession;
import android.view.Surface;
import android.view.View;
@@ -147,6 +146,7 @@
if (reportNextDraw) {
reportDrawn();
}
+ mSurface.release();
}
private void reportDrawn() {
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index bbc9ed2..26e36dc 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -19,6 +19,9 @@
import android.app.ActivityManager.TaskSnapshot;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.util.EventLog;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -37,14 +40,30 @@
* Test class: {@link TaskWindowContainerControllerTests}
*/
public class TaskWindowContainerController
- extends WindowContainerController<Task, WindowContainerListener> {
+ extends WindowContainerController<Task, TaskWindowContainerListener> {
+
+ private static final int REPORT_SNAPSHOT_CHANGED = 0;
private final int mTaskId;
- public TaskWindowContainerController(int taskId, int stackId, int userId, Rect bounds,
- Configuration overrideConfig, int resizeMode, boolean homeTask, boolean isOnTopLauncher,
- boolean toTop, boolean showForAllUsers) {
- super(null, WindowManagerService.getInstance());
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case REPORT_SNAPSHOT_CHANGED:
+ if (mListener != null) {
+ mListener.onSnapshotChanged((TaskSnapshot) msg.obj);
+ }
+ break;
+ }
+ }
+ };
+
+ public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
+ int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode,
+ boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers) {
+ super(listener, WindowManagerService.getInstance());
mTaskId = taskId;
synchronized(mWindowMap) {
@@ -259,6 +278,10 @@
}
}
+ void reportSnapshotChanged(TaskSnapshot snapshot) {
+ mHandler.obtainMessage(REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
+ }
+
@Override
public String toString() {
return "{TaskWindowContainerController taskId=" + mTaskId + "}";
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
new file mode 100644
index 0000000..61b202d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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.wm;
+
+import android.app.ActivityManager.TaskSnapshot;
+
+/**
+ * Interface used by the creator of the controller to listen to changes with the container.
+ */
+public interface TaskWindowContainerListener extends WindowContainerListener {
+
+ /**
+ * Called when the snapshot of this task has changed.
+ */
+ void onSnapshotChanged(TaskSnapshot snapshot);
+}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e231da8..5b96263 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -458,9 +458,9 @@
return false;
}
- /** Returns the top child container or this container if there are no children. */
- WindowContainer getTop() {
- return mChildren.isEmpty() ? this : mChildren.peekLast();
+ /** Returns the top child container. */
+ E getTopChild() {
+ return mChildren.peekLast();
}
/** Returns true if there is still a removal being deferred */
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0c8c10b..dcc0c6f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -235,6 +235,8 @@
import java.util.HashMap;
import java.util.List;
+import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
+import static android.Manifest.permission.READ_FRAME_BUFFER;
/** {@hide} */
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
@@ -3859,7 +3861,7 @@
@Override
public Bitmap screenshotWallpaper() {
- if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER,
+ if (!checkCallingPermission(READ_FRAME_BUFFER,
"screenshotWallpaper()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
@@ -3880,7 +3882,7 @@
*/
@Override
public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
- if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER,
+ if (!checkCallingPermission(READ_FRAME_BUFFER,
"requestAssistScreenshot()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
@@ -7148,6 +7150,7 @@
mInputMonitor.dump(pw, " ");
mUnknownAppVisibilityController.dump(pw, " ");
+ mTaskSnapshotController.dump(pw, " ");
if (dumpAll) {
pw.print(" mSystemDecorLayer="); pw.print(mSystemDecorLayer);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b1bf2c6..10aebe6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2304,6 +2304,9 @@
final WindowState win = mService.windowForClientLocked(mSession, mClient, false);
Slog.i(TAG, "WIN DEATH: " + win);
if (win != null) {
+ if (win.mAppToken != null && win.mAppToken.findMainWindow() == win) {
+ mService.mTaskSnapshotController.onAppDied(win.mAppToken);
+ }
win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
if (win.mAttrs.type == TYPE_DOCK_DIVIDER) {
// The owner of the docked divider died :( We reset the docked stack,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 27efd05..6791da9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1462,7 +1462,11 @@
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
PointerIcon pointerIcon;
- android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
+ status_t result = android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
+ if (result) {
+ jniThrowRuntimeException(env, "Failed to load custom pointer icon.");
+ return;
+ }
SpriteIcon spriteIcon;
pointerIcon.bitmap.copyTo(&spriteIcon.bitmap, kN32_SkColorType);
diff --git a/services/core/jni/com_android_server_location_ContextHubService.cpp b/services/core/jni/com_android_server_location_ContextHubService.cpp
index 9106441..517fce0 100644
--- a/services/core/jni/com_android_server_location_ContextHubService.cpp
+++ b/services/core/jni/com_android_server_location_ContextHubService.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
#undef LOG_NDEBUG
#undef LOG_TAG
#define LOG_NDEBUG 0
@@ -26,11 +25,13 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/endian.h>
#include <chrono>
#include <mutex>
#include <queue>
#include <unordered_map>
+#include <utility>
#include <android-base/macros.h>
#include <android/hardware/contexthub/1.0/IContexthub.h>
@@ -39,20 +40,20 @@
#include "core_jni_helpers.h"
#include "JNIHelp.h"
-using IContexthub = android::hardware::contexthub::V1_0::IContexthub;
+using android::hardware::contexthub::V1_0::AsyncEventType;
+using android::hardware::contexthub::V1_0::ContextHub;
+using android::hardware::contexthub::V1_0::ContextHubMsg;
+using android::hardware::contexthub::V1_0::HubAppInfo;
+using android::hardware::contexthub::V1_0::IContexthub;
+using android::hardware::contexthub::V1_0::IContexthubCallback;
+using android::hardware::contexthub::V1_0::NanoAppBinary;
+using android::hardware::contexthub::V1_0::Result;
+using android::hardware::contexthub::V1_0::TransactionResult;
-using Result = android::hardware::contexthub::V1_0::Result;
-using ContextHubMsg = android::hardware::contexthub::V1_0::ContextHubMsg;
-using IContexthubCallback = android::hardware::contexthub::V1_0::IContexthubCallback;
-using AsyncEventType = android::hardware::contexthub::V1_0::AsyncEventType;
-using TransactionResult = android::hardware::contexthub::V1_0::TransactionResult;
-using ContextHub = android::hardware::contexthub::V1_0::ContextHub;
-using HubAppInfo = android::hardware::contexthub::V1_0::HubAppInfo;
-
-template<typename T>
-using Return = android::hardware::Return<T>;
+using android::hardware::Return;
using std::chrono::steady_clock;
+
// If a transaction takes longer than this, we'll allow it to be
// canceled by a new transaction. Note we do _not_ automatically
// cancel a transaction after this much time. We can have a
@@ -63,6 +64,22 @@
namespace android {
+constexpr uint32_t kNanoAppBinaryHeaderVersion = 1;
+
+// Important: this header is explicitly defined as little endian byte order, and
+// therefore may not match host endianness
+struct NanoAppBinaryHeader {
+ uint32_t headerVersion; // 0x1 for this version
+ uint32_t magic; // "NANO" (see NANOAPP_MAGIC in context_hub.h)
+ uint64_t appId; // App Id, contains vendor id
+ uint32_t appVersion; // Version of the app
+ uint32_t flags; // Signed, encrypted
+ uint64_t hwHubType; // Which hub type is this compiled for
+ uint8_t targetChreApiMajorVersion; // Which CHRE API version this is compiled for
+ uint8_t targetChreApiMinorVersion;
+ uint8_t reserved[6];
+} __attribute__((packed));
+
enum HubMessageType {
CONTEXT_HUB_APPS_ENABLE = 1, // Enables loaded nano-app(s)
CONTEXT_HUB_APPS_DISABLE = 2, // Disables loaded nano-app(s)
@@ -361,11 +378,12 @@
ContextHubServiceDb db;
-int getHubIdForHubHandle(int hubHandle) {
- if (hubHandle < 0 || hubHandle >= db.hubInfo.numHubs) {
- return -1;
+bool getHubIdForHubHandle(int hubHandle, uint32_t *hubId) {
+ if (hubHandle < 0 || hubHandle >= db.hubInfo.numHubs || hubId == nullptr) {
+ return false;
} else {
- return db.hubInfo.hubs[hubHandle].hubId;
+ *hubId = db.hubInfo.hubs[hubHandle].hubId;
+ return true;
}
}
@@ -380,17 +398,6 @@
return db.appInstances[id].hubHandle;
}
-int getHubIdForAppInstance(jint id) {
- int hubHandle = getHubHandleForAppInstance(id);
-
- if (hubHandle < 0) {
- ALOGD("Cannot find hub instance for app instance %d", id);
- return -1;
- }
-
- return db.hubInfo.hubs[hubHandle].hubId;
-}
-
jint getAppInstanceForAppId(uint64_t app_id) {
auto end = db.appInstances.end();
for (auto current = db.appInstances.begin(); current != end; ++current) {
@@ -1001,6 +1008,45 @@
return retArray;
}
+Result sendLoadNanoAppRequest(uint32_t hubId,
+ jbyte *data,
+ size_t dataBufferLength) {
+ auto header = reinterpret_cast<const NanoAppBinaryHeader *>(data);
+ Result result;
+
+ if (dataBufferLength < sizeof(NanoAppBinaryHeader)) {
+ ALOGE("Got short NanoApp, length %zu", dataBufferLength);
+ result = Result::BAD_PARAMS;
+ } else if (header->headerVersion != htole32(kNanoAppBinaryHeaderVersion)) {
+ ALOGE("Got unexpected NanoApp header version %" PRIu32,
+ letoh32(header->headerVersion));
+ result = Result::BAD_PARAMS;
+ } else {
+ NanoAppBinary nanoapp;
+
+ // Data from the common nanoapp header goes into explicit fields
+ nanoapp.appId = letoh64(header->appId);
+ nanoapp.appVersion = letoh32(header->appVersion);
+ nanoapp.flags = letoh32(header->flags);
+ nanoapp.targetChreApiMajorVersion = header->targetChreApiMajorVersion;
+ nanoapp.targetChreApiMinorVersion = header->targetChreApiMinorVersion;
+
+ // Everything past the header goes in customBinary
+ auto dataBytes = reinterpret_cast<const uint8_t *>(data);
+ std::vector<uint8_t> customBinary(
+ dataBytes + sizeof(NanoAppBinaryHeader),
+ dataBytes + dataBufferLength);
+ nanoapp.customBinary = std::move(customBinary);
+
+ ALOGW("Calling Load NanoApp on hub %d", hubId);
+ result = db.hubInfo.contextHub->loadNanoApp(hubId,
+ nanoapp,
+ CONTEXT_HUB_LOAD_APP);
+ }
+
+ return result;
+}
+
jint nativeSendMessage(JNIEnv *env,
jobject instance,
jintArray header_,
@@ -1012,19 +1058,18 @@
jint retVal = -1; // Default to failure
jint *header = env->GetIntArrayElements(header_, 0);
- unsigned int numHeaderElements = env->GetArrayLength(header_);
+ size_t numHeaderElements = env->GetArrayLength(header_);
jbyte *data = env->GetByteArrayElements(data_, 0);
- int dataBufferLength = env->GetArrayLength(data_);
+ size_t dataBufferLength = env->GetArrayLength(data_);
if (numHeaderElements < MSG_HEADER_SIZE) {
ALOGW("Malformed header len");
return -1;
}
- uint32_t appInstanceHandle = header[HEADER_FIELD_APP_INSTANCE];
+ jint appInstanceHandle = header[HEADER_FIELD_APP_INSTANCE];
uint32_t msgType = header[HEADER_FIELD_MSG_TYPE];
int hubHandle = -1;
- int hubId;
uint64_t appId;
if (msgType == CONTEXT_HUB_UNLOAD_APP) {
@@ -1042,7 +1087,8 @@
hubHandle = header[HEADER_FIELD_HUB_HANDLE];
}
- if (hubHandle < 0) {
+ uint32_t hubId = -1;
+ if (!getHubIdForHubHandle(hubHandle, &hubId)) {
ALOGD("Invalid hub Handle %d", hubHandle);
return -1;
}
@@ -1072,46 +1118,41 @@
Result result;
if (msgType == CONTEXT_HUB_UNLOAD_APP) {
- hubId = getHubIdForHubHandle(hubHandle);
- ALOGW("Calling UnLoad NanoApp for app %" PRIx64 " on hub %d",
+ ALOGW("Calling UnLoad NanoApp for app %" PRIx64 " on hub %" PRIu32,
db.appInstances[appInstanceHandle].appInfo.appId,
hubId);
result = db.hubInfo.contextHub->unloadNanoApp(
hubId, db.appInstances[appInstanceHandle].appInfo.appId, CONTEXT_HUB_UNLOAD_APP);
} else {
- if (header[HEADER_FIELD_APP_INSTANCE] == OS_APP_ID) {
+ if (appInstanceHandle == OS_APP_ID) {
if (msgType == CONTEXT_HUB_LOAD_APP) {
- std::vector<uint8_t> dataVector(reinterpret_cast<uint8_t *>(data),
- reinterpret_cast<uint8_t *>(data + dataBufferLength));
- hubId = getHubIdForHubHandle(hubHandle);
- ALOGW("Calling Load NanoApp on hub %d", hubId);
- result = db.hubInfo.contextHub->loadNanoApp(hubId,
- dataVector,
- CONTEXT_HUB_LOAD_APP);
+ result = sendLoadNanoAppRequest(hubId, data, dataBufferLength);
} else {
ALOGD("Dropping OS addresses message of type - %" PRIu32, msgType);
result = Result::BAD_PARAMS;
}
} else {
-
- appId = getAppIdForAppInstance(header[HEADER_FIELD_APP_INSTANCE]);
- hubId = getHubIdForAppInstance(header[HEADER_FIELD_APP_INSTANCE]);
-
- if (appId != static_cast<uint64_t>(INVALID_APP_ID) && hubId >= 0) {
+ appId = getAppIdForAppInstance(appInstanceHandle);
+ if (appId == static_cast<uint64_t>(INVALID_APP_ID)) {
+ ALOGD("Cannot find application instance %d", appInstanceHandle);
+ result = Result::BAD_PARAMS;
+ } else if (hubHandle != getHubHandleForAppInstance(appInstanceHandle)) {
+ ALOGE("Given hubHandle (%d) doesn't match expected for app instance (%d)",
+ hubHandle,
+ getHubHandleForAppInstance(appInstanceHandle));
+ result = Result::BAD_PARAMS;
+ } else {
ContextHubMsg msg;
msg.appName = appId;
msg.msgType = msgType;
msg.msg.setToExternal((unsigned char *)data, dataBufferLength);
- ALOGW("Sending msg of type %" PRIu32 " len %u to app %" PRIx64 " on hub %d",
+ ALOGW("Sending msg of type %" PRIu32 " len %zu to app %" PRIx64 " on hub %" PRIu32,
msgType,
dataBufferLength,
appId,
hubId);
result = db.hubInfo.contextHub->sendMessageToHub(hubId, msg);
- } else {
- ALOGD("Cannot find application instance %u", header[HEADER_FIELD_APP_INSTANCE]);
- result = Result::BAD_PARAMS;
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 040188d..111f37f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -191,6 +191,7 @@
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -235,7 +236,7 @@
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
- private static final long MS_PER_DAY = 86400 * 1000;
+ private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms
@@ -330,7 +331,7 @@
* Minimum timeout in milliseconds after which unlocking with weak auth times out,
* i.e. the user has to use a strong authentication method like password, PIN or pattern.
*/
- private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h
+ private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = TimeUnit.HOURS.toMillis(1);
/**
* Strings logged with {@link
@@ -1154,7 +1155,7 @@
} else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
keepUninstalledPackages = readPackageList(parser, tag);
} else if (TAG_USER_RESTRICTIONS.equals(tag)) {
- UserRestrictionsUtils.readRestrictions(parser, ensureUserRestrictions());
+ userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
readAttributeValues(
parser, TAG_RESTRICTION, defaultEnabledRestrictionsAlreadySet);
@@ -2663,14 +2664,14 @@
// Ignore
}
+ // Generate a list of admins from the admin map
+ policy.mAdminList.addAll(policy.mAdminMap.values());
+
// Might need to upgrade the file by rewriting it
if (needsRewrite) {
saveSettingsLocked(userHandle);
}
- // Generate a list of admins from the admin map
- policy.mAdminList.addAll(policy.mAdminMap.values());
-
validatePasswordOwnerLocked(policy);
updateMaximumTimeToLockLocked(userHandle);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
@@ -6783,6 +6784,18 @@
enforceManageUsers();
}
+ private void enforceProfileOwnerOrSystemUser(ComponentName admin) {
+ synchronized (this) {
+ if (getActiveAdminWithPolicyForUidLocked(admin,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid())
+ != null) {
+ return;
+ }
+ }
+ Preconditions.checkState(isCallerWithSystemUid(),
+ "Only profile owner, device owner and system may call this method.");
+ }
+
private void ensureCallerPackage(@Nullable String packageName) {
if (packageName == null) {
Preconditions.checkState(isCallerWithSystemUid(),
@@ -7767,7 +7780,7 @@
final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
- ActiveAdmin activeAdmin =
+ final ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
@@ -7782,7 +7795,12 @@
}
// Save the restriction to ActiveAdmin.
- activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
+ final Bundle restrictions = activeAdmin.ensureUserRestrictions();
+ if (enabledFromThisOwner) {
+ restrictions.putBoolean(key, true);
+ } else {
+ restrictions.remove(key);
+ }
saveUserRestrictionsLocked(userHandle);
}
}
@@ -7795,39 +7813,46 @@
private void pushUserRestrictions(int userId) {
synchronized (this) {
- final Bundle global;
- final Bundle local = new Bundle();
- if (mOwners.isDeviceOwnerUserId(userId)) {
- global = new Bundle();
+ final boolean isDeviceOwner = mOwners.isDeviceOwnerUserId(userId);
+ final Bundle userRestrictions;
+ // Whether device owner enforces camera restriction.
+ boolean disallowCameraGlobally = false;
+ if (isDeviceOwner) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
if (deviceOwner == null) {
return; // Shouldn't happen.
}
-
- UserRestrictionsUtils.sortToGlobalAndLocal(deviceOwner.userRestrictions,
- global, local);
+ userRestrictions = deviceOwner.userRestrictions;
// DO can disable camera globally.
- if (deviceOwner.disableCamera) {
- global.putBoolean(UserManager.DISALLOW_CAMERA, true);
- }
+ disallowCameraGlobally = deviceOwner.disableCamera;
} else {
- global = null;
+ final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+ userRestrictions = profileOwner != null ? profileOwner.userRestrictions : null;
+ }
- ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
- if (profileOwner != null) {
- UserRestrictionsUtils.merge(local, profileOwner.userRestrictions);
- }
- }
- // Also merge in *local* camera restriction.
- if (getCameraDisabled(/* who= */ null,
- userId, /* mergeDeviceOwnerRestriction= */ false)) {
- local.putBoolean(UserManager.DISALLOW_CAMERA, true);
- }
- mUserManagerInternal.setDevicePolicyUserRestrictions(userId, local, global);
+ // Whether any admin enforces camera restriction.
+ final int cameraRestrictionScope =
+ getCameraRestrictionScopeLocked(userId, disallowCameraGlobally);
+
+ mUserManagerInternal.setDevicePolicyUserRestrictions(userId, userRestrictions,
+ isDeviceOwner, cameraRestrictionScope);
}
}
+ /**
+ * Get the scope of camera restriction for a given user if any.
+ */
+ private int getCameraRestrictionScopeLocked(int userId, boolean disallowCameraGlobally) {
+ if (disallowCameraGlobally) {
+ return UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
+ } else if (getCameraDisabled(
+ /* who= */ null, userId, /* mergeDeviceOwnerRestriction= */ false)) {
+ return UserManagerInternal.CAMERA_DISABLED_LOCALLY;
+ }
+ return UserManagerInternal.CAMERA_NOT_DISABLED;
+ }
+
@Override
public Bundle getUserRestrictions(ComponentName who) {
if (!mHasFeature) {
@@ -8913,8 +8938,8 @@
PackageManager packageManager = mInjector.getPackageManager();
UserHandle user = mInjector.binderGetCallingUserHandle();
+ enforceProfileOwnerOrSystemUser(admin);
synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long ident = mInjector.binderClearCallingIdentity();
try {
int granted = mIPackageManager.checkPermission(permission,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 08fb591..b6c518b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -282,12 +282,6 @@
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
if (!mRuntimeRestart && !mFirstBoot) {
MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
- // Also report when first stage of init has started
- long initStartNs = SystemProperties.getLong("ro.boottime.init", -1);
- if (initStartNs >= 0) {
- MetricsLogger.histogram(null, "boot_android_init",
- (int)(initStartNs / 1000000));
- }
}
// In case the runtime switched since last boot (such as when
@@ -383,8 +377,13 @@
Slog.i(TAG, "Enabled StrictMode for system server main thread.");
}
if (!mRuntimeRestart && !mFirstBoot) {
- MetricsLogger.histogram(null, "boot_system_server_ready",
- (int) SystemClock.elapsedRealtime());
+ int uptimeMillis = (int) SystemClock.elapsedRealtime();
+ MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
+ final int MAX_UPTIME_MILLIS = 60 * 1000;
+ if (uptimeMillis > MAX_UPTIME_MILLIS) {
+ Slog.wtf("SystemServerTiming",
+ "SystemServer init took too long. uptimeMillis=" + uptimeMillis);
+ }
}
// Loop forever.
@@ -955,6 +954,21 @@
}
traceEnd();
+ // Wifi Service must be started first for wifi-related services.
+ traceBeginAndSlog("StartWifi");
+ mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+ traceEnd();
+ traceBeginAndSlog("StartWifiScanning");
+ mSystemServiceManager.startService(
+ "com.android.server.wifi.scanner.WifiScanningService");
+ traceEnd();
+
+ if (!disableRtt) {
+ traceBeginAndSlog("StartWifiRtt");
+ mSystemServiceManager.startService("com.android.server.wifi.RttService");
+ traceEnd();
+ }
+
if (context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_AWARE)) {
traceBeginAndSlog("StartWifiAware");
@@ -970,19 +984,6 @@
mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
traceEnd();
}
- traceBeginAndSlog("StartWifi");
- mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
- traceEnd();
- traceBeginAndSlog("StartWifiScanning");
- mSystemServiceManager.startService(
- "com.android.server.wifi.scanner.WifiScanningService");
- traceEnd();
-
- if (!disableRtt) {
- traceBeginAndSlog("StartWifiRtt");
- mSystemServiceManager.startService("com.android.server.wifi.RttService");
- traceEnd();
- }
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 1393615..133bc3d 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -138,6 +138,12 @@
android:enabled="true" android:exported="true">
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_1"/>
</activity-alias>
+ <activity-alias android:name="a.ShortcutConfigActivity"
+ android:targetActivity="com.android.server.pm.ShortcutTestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.CREATE_SHORTCUT" />
+ </intent-filter>
+ </activity-alias>
<activity-alias android:name="a.DisabledMain"
android:targetActivity="com.android.server.pm.ShortcutTestActivity"
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 4886a5f..677e468 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -28,6 +29,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.PendingHostUpdate;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContextWrapper;
@@ -39,11 +41,17 @@
import android.os.UserHandle;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.RemoteViews;
+import com.android.internal.appwidget.IAppWidgetHost;
import com.android.server.LocalServices;
import org.mockito.ArgumentCaptor;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+
/**
* Tests for {@link AppWidgetManager} and {@link AppWidgetServiceImpl}.
@@ -57,11 +65,15 @@
@SmallTest
public class AppWidgetServiceImplTest extends InstrumentationTestCase {
+ private static final int HOST_ID = 42;
+
private TestContext mTestContext;
+ private String mPkgName;
private AppWidgetServiceImpl mService;
private AppWidgetManager mManager;
private ShortcutServiceInternal mMockShortcutService;
+ private IAppWidgetHost mMockHost;
@Override
protected void setUp() throws Exception {
@@ -70,12 +82,13 @@
LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
mTestContext = new TestContext();
+ mPkgName = mTestContext.getOpPackageName();
mService = new AppWidgetServiceImpl(mTestContext);
mManager = new AppWidgetManager(mTestContext, mService);
mMockShortcutService = mock(ShortcutServiceInternal.class);
+ mMockHost = mock(IAppWidgetHost.class);
LocalServices.addService(ShortcutServiceInternal.class, mMockShortcutService);
-
mService.onStart();
}
@@ -108,6 +121,142 @@
assertEquals(provider, providerCaptor.getValue().provider);
}
+ public void testProviderUpdatesReceived() throws Exception {
+ int widgetId = setupHostAndWidget();
+ RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
+ mManager.updateAppWidget(widgetId, view);
+ mManager.updateAppWidget(widgetId, view);
+ mManager.updateAppWidget(widgetId, view);
+ mManager.updateAppWidget(widgetId, view);
+
+ flushMainThread();
+ verify(mMockHost, times(4)).updateAppWidget(eq(widgetId), any(RemoteViews.class));
+
+ reset(mMockHost);
+ mManager.notifyAppWidgetViewDataChanged(widgetId, 22);
+ flushMainThread();
+ verify(mMockHost, times(1)).viewDataChanged(eq(widgetId), eq(22));
+ }
+
+ public void testProviderUpdatesNotReceived() throws Exception {
+ int widgetId = setupHostAndWidget();
+ mService.stopListening(mPkgName, HOST_ID);
+ RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
+ mManager.updateAppWidget(widgetId, view);
+ mManager.notifyAppWidgetViewDataChanged(widgetId, 22);
+
+ flushMainThread();
+ verify(mMockHost, times(0)).updateAppWidget(anyInt(), any(RemoteViews.class));
+ verify(mMockHost, times(0)).viewDataChanged(anyInt(), eq(22));
+ }
+
+ public void testNoUpdatesReceived_queueEmpty() {
+ int widgetId = setupHostAndWidget();
+ RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
+ mManager.updateAppWidget(widgetId, view);
+ mManager.notifyAppWidgetViewDataChanged(widgetId, 22);
+ mService.stopListening(mPkgName, HOST_ID);
+
+ List<PendingHostUpdate> updates = mService.startListening(
+ mMockHost, mPkgName, HOST_ID, new int[0]).getList();
+ assertTrue(updates.isEmpty());
+ }
+
+ /**
+ * Sends dummy widget updates to {@link #mManager}.
+ * @param widgetId widget to update
+ * @param viewIds a list of view ids for which
+ * {@link AppWidgetManager#notifyAppWidgetViewDataChanged} will be called
+ */
+ private void sendDummyUpdates(int widgetId, int... viewIds) {
+ Random r = new Random();
+ RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
+ for (int i = r.nextInt(10) + 2; i >= 0; i--) {
+ mManager.updateAppWidget(widgetId, view);
+ }
+
+ for (int viewId : viewIds) {
+ mManager.notifyAppWidgetViewDataChanged(widgetId, viewId);
+ for (int i = r.nextInt(3); i >= 0; i--) {
+ mManager.updateAppWidget(widgetId, view);
+ }
+ }
+ }
+
+ public void testNoUpdatesReceived_queueNonEmpty_noWidgetId() {
+ int widgetId = setupHostAndWidget();
+ mService.stopListening(mPkgName, HOST_ID);
+
+ sendDummyUpdates(widgetId, 22, 23);
+ List<PendingHostUpdate> updates = mService.startListening(
+ mMockHost, mPkgName, HOST_ID, new int[0]).getList();
+ assertTrue(updates.isEmpty());
+ }
+
+ public void testUpdatesReceived_queueNotEmpty_widgetIdProvided() {
+ int widgetId = setupHostAndWidget();
+ int widgetId2 = bindNewWidget();
+ mService.stopListening(mPkgName, HOST_ID);
+
+ sendDummyUpdates(widgetId, 22, 23);
+ sendDummyUpdates(widgetId2, 100, 101, 102);
+
+ List<PendingHostUpdate> updates = mService.startListening(
+ mMockHost, mPkgName, HOST_ID, new int[]{widgetId}).getList();
+ // 3 updates corresponding to the first widget
+ assertEquals(3, updates.size());
+ }
+
+ public void testUpdatesReceived_queueNotEmpty_widgetIdProvided2() {
+ int widgetId = setupHostAndWidget();
+ int widgetId2 = bindNewWidget();
+ mService.stopListening(mPkgName, HOST_ID);
+
+ sendDummyUpdates(widgetId, 22, 23);
+ sendDummyUpdates(widgetId2, 100, 101, 102);
+
+ List<PendingHostUpdate> updates = mService.startListening(
+ mMockHost, mPkgName, HOST_ID, new int[]{widgetId2}).getList();
+ // 4 updates corresponding to the second widget
+ assertEquals(4, updates.size());
+ }
+
+ public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() {
+ int widgetId = setupHostAndWidget();
+ int widgetId2 = bindNewWidget();
+ mService.stopListening(mPkgName, HOST_ID);
+
+ sendDummyUpdates(widgetId, 22, 23);
+ sendDummyUpdates(widgetId2, 100, 101, 102);
+
+ List<PendingHostUpdate> updates = mService.startListening(
+ mMockHost, mPkgName, HOST_ID, new int[]{widgetId, widgetId2}).getList();
+ // 3 updates for first widget and 4 for second
+ assertEquals(7, updates.size());
+ }
+
+ private int setupHostAndWidget() {
+ List<PendingHostUpdate> updates = mService.startListening(
+ mMockHost, mPkgName, HOST_ID, new int[0]).getList();
+ assertTrue(updates.isEmpty());
+ return bindNewWidget();
+ }
+
+ private int bindNewWidget() {
+ ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+ int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID);
+ assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider));
+ assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider);
+
+ return widgetId;
+ }
+
+ private void flushMainThread() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ new Handler(mTestContext.getMainLooper()).post(latch::countDown);
+ latch.await();
+ }
+
private class TestContext extends ContextWrapper {
public TestContext() {
@@ -125,5 +274,15 @@
public void unregisterReceiver(BroadcastReceiver receiver) {
// ignore.
}
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ // ignore.
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ // ignore.
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c3eb09d..8da47c8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -15,6 +15,10 @@
*/
package com.android.server.devicepolicy;
+import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
+import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
+import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
+
import android.Manifest.permission;
import android.app.Activity;
import android.app.admin.DeviceAdminReceiver;
@@ -39,6 +43,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
@@ -61,9 +66,11 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -928,9 +935,8 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(),
- MockUtils.checkUserRestrictions()
- );
+ eq(null),
+ eq(true), eq(CAMERA_NOT_DISABLED));
assertFalse(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM));
@@ -1287,7 +1293,8 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(defaultRestrictions),
- MockUtils.checkUserRestrictions()
+ eq(true) /* isDeviceOwner */,
+ eq(CAMERA_NOT_DISABLED)
);
reset(mContext.userManagerInternal);
@@ -1296,21 +1303,21 @@
}
assertNoDeviceOwnerRestrictions();
+ reset(mContext.userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(),
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
- );
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER),
+ eq(true), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
- );
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS,
+ UserManager.DISALLOW_ADD_USER),
+ eq(true), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
@@ -1328,8 +1335,7 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
- MockUtils.checkUserRestrictions()
- );
+ eq(true), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
@@ -1345,8 +1351,7 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(),
- MockUtils.checkUserRestrictions()
- );
+ eq(true), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
assertNoDeviceOwnerRestrictions();
@@ -1358,42 +1363,38 @@
dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
- UserManager.DISALLOW_UNMUTE_MICROPHONE)
- );
+ UserManager.DISALLOW_UNMUTE_MICROPHONE),
+ eq(true), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
-
+ reset(mContext.userManagerInternal);
// More tests.
dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(),
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
- );
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER),
+ eq(true), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_FUN);
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
- UserManager.DISALLOW_ADD_USER)
- );
+ UserManager.DISALLOW_ADD_USER),
+ eq(true), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
dpm.setCameraDisabled(admin1, true);
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
// DISALLOW_CAMERA will be applied to both local and global.
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
- UserManager.DISALLOW_CAMERA, UserManager.DISALLOW_ADD_USER)
- );
+ UserManager.DISALLOW_ADD_USER),
+ eq(true), eq(CAMERA_DISABLED_GLOBALLY));
reset(mContext.userManagerInternal);
// Set up another DA and let it disable camera. Now DISALLOW_CAMERA will only be applied
@@ -1407,11 +1408,10 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
- // DISALLOW_CAMERA will be applied to both local and global.
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+ // DISALLOW_CAMERA will be applied to both local and global. <- TODO: fix this
MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
- UserManager.DISALLOW_ADD_USER)
- );
+ UserManager.DISALLOW_ADD_USER),
+ eq(true), eq(CAMERA_DISABLED_LOCALLY));
reset(mContext.userManagerInternal);
// TODO Make sure restrictions are written to the file.
}
@@ -1429,8 +1429,7 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
- isNull(Bundle.class)
- );
+ eq(false), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
@@ -1438,8 +1437,7 @@
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
UserManager.DISALLOW_OUTGOING_CALLS),
- isNull(Bundle.class)
- );
+ eq(false), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
@@ -1462,8 +1460,7 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
- isNull(Bundle.class)
- );
+ eq(false), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
@@ -1484,8 +1481,7 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(),
- isNull(Bundle.class)
- );
+ eq(false), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
DpmTestUtils.assertRestrictions(
@@ -1507,18 +1503,15 @@
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_UNMUTE_MICROPHONE),
- isNull(Bundle.class)
- );
+ eq(false), eq(CAMERA_NOT_DISABLED));
reset(mContext.userManagerInternal);
dpm.setCameraDisabled(admin1, true);
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA,
- UserManager.DISALLOW_ADJUST_VOLUME,
+ MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_UNMUTE_MICROPHONE),
- isNull(Bundle.class)
- );
+ eq(false), eq(CAMERA_DISABLED_LOCALLY));
reset(mContext.userManagerInternal);
// TODO Make sure restrictions are written to the file.
@@ -1558,7 +1551,8 @@
verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(defaultRestrictions),
- MockUtils.checkUserRestrictions()
+ eq(true) /* isDeviceOwner */,
+ eq(CAMERA_NOT_DISABLED)
);
reset(mContext.userManagerInternal);
@@ -1600,7 +1594,8 @@
verify(mContext.userManagerInternal, atLeast(1)).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(newDefaultEnabledRestriction),
- MockUtils.checkUserRestrictions()
+ eq(true) /* isDeviceOwner */,
+ eq(CAMERA_NOT_DISABLED)
);
reset(mContext.userManagerInternal);
@@ -2124,8 +2119,37 @@
setupDeviceOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h
- final long ONE_MINUTE = 60 * 1000;
+ final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = TimeUnit.HOURS.toMillis(1);
+ final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1);
+ final long MIN_PLUS_ONE_MINUTE = MINIMUM_STRONG_AUTH_TIMEOUT_MS + ONE_MINUTE;
+ final long MAX_MINUS_ONE_MINUTE = DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS
+ - ONE_MINUTE;
+
+ // verify that the minimum timeout cannot be modified on user builds (system property is
+ // not being read)
+ mContext.buildMock.isDebuggable = false;
+
+ dpm.setRequiredStrongAuthTimeout(admin1, MAX_MINUS_ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), MAX_MINUS_ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null), MAX_MINUS_ONE_MINUTE);
+
+ verify(mContext.systemProperties, never()).getLong(anyString(), anyLong());
+
+ // restore to the debuggable build state
+ mContext.buildMock.isDebuggable = true;
+
+ // Always return the default (second arg) when getting system property for long type
+ when(mContext.systemProperties.getLong(anyString(), anyLong())).thenAnswer(
+ new Answer<Long>() {
+ @Override
+ public Long answer(InvocationOnMock invocation) throws Throwable {
+ return (Long) invocation.getArguments()[1];
+ }
+ }
+ );
+
+ // reset to default (0 means the admin is not participating, so default should be returned)
+ dpm.setRequiredStrongAuthTimeout(admin1, 0);
// aggregation should be the default if unset by any admin
assertEquals(dpm.getRequiredStrongAuthTimeout(null),
@@ -2142,7 +2166,7 @@
assertEquals(dpm.getRequiredStrongAuthTimeout(null),
DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS);
- // 0 means default
+ // 0 means the admin is not participating, so default should be returned
dpm.setRequiredStrongAuthTimeout(admin1, 0);
assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), 0);
assertEquals(dpm.getRequiredStrongAuthTimeout(null),
@@ -2153,12 +2177,14 @@
assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), MINIMUM_STRONG_AUTH_TIMEOUT_MS);
assertEquals(dpm.getRequiredStrongAuthTimeout(null), MINIMUM_STRONG_AUTH_TIMEOUT_MS);
- // value within range
- dpm.setRequiredStrongAuthTimeout(admin1, MINIMUM_STRONG_AUTH_TIMEOUT_MS + ONE_MINUTE);
- assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), MINIMUM_STRONG_AUTH_TIMEOUT_MS
- + ONE_MINUTE);
- assertEquals(dpm.getRequiredStrongAuthTimeout(null), MINIMUM_STRONG_AUTH_TIMEOUT_MS
- + ONE_MINUTE);
+ // values within range
+ dpm.setRequiredStrongAuthTimeout(admin1, MIN_PLUS_ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), MIN_PLUS_ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null), MIN_PLUS_ONE_MINUTE);
+
+ dpm.setRequiredStrongAuthTimeout(admin1, MAX_MINUS_ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), MAX_MINUS_ONE_MINUTE);
+ assertEquals(dpm.getRequiredStrongAuthTimeout(null), MAX_MINUS_ONE_MINUTE);
// reset to default
dpm.setRequiredStrongAuthTimeout(admin1, 0);
@@ -3207,6 +3233,48 @@
}
}
+ public void testGetPermissionGrantState() throws Exception {
+ final String permission = "some.permission";
+ final String app1 = "com.example.app1";
+ final String app2 = "com.example.app2";
+
+ when(mContext.ipackageManager.checkPermission(eq(permission), eq(app1), anyInt()))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ doReturn(PackageManager.FLAG_PERMISSION_POLICY_FIXED).when(mContext.packageManager)
+ .getPermissionFlags(permission, app1, UserHandle.SYSTEM);
+ when(mContext.packageManager.getPermissionFlags(permission, app1,
+ UserHandle.of(DpmMockContext.CALLER_USER_HANDLE)))
+ .thenReturn(PackageManager.FLAG_PERMISSION_POLICY_FIXED);
+ when(mContext.ipackageManager.checkPermission(eq(permission), eq(app2), anyInt()))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+ doReturn(0).when(mContext.packageManager).getPermissionFlags(permission, app2,
+ UserHandle.SYSTEM);
+ when(mContext.packageManager.getPermissionFlags(permission, app2,
+ UserHandle.of(DpmMockContext.CALLER_USER_HANDLE))).thenReturn(0);
+
+ // System can retrieve permission grant state.
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
+ dpm.getPermissionGrantState(null, app1, permission));
+ assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT,
+ dpm.getPermissionGrantState(null, app2, permission));
+
+ // A regular app cannot retrieve permission grant state.
+ mMockContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ try {
+ dpm.getPermissionGrantState(null, app1, permission);
+ fail("Didn't throw IllegalStateException");
+ } catch (IllegalStateException expected) {
+ }
+
+ // Profile owner can retrieve permission grant state.
+ setAsProfileOwner(admin1);
+ assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
+ dpm.getPermissionGrantState(admin1, app1, permission));
+ assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT,
+ dpm.getPermissionGrantState(admin1, app2, permission));
+ }
+
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java
index 5ab9020..b5a6178 100644
--- a/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java
@@ -80,11 +80,19 @@
}
public void testGetAppSize() throws Exception {
+ int[] appIds = null;
+
final PackageManager pm = getContext().getPackageManager();
for (ApplicationInfo app : pm.getInstalledApplications(0)) {
final int userId = UserHandle.getUserId(app.uid);
final int appId = UserHandle.getAppId(app.uid);
+ if (ArrayUtils.contains(appIds, appId)) {
+ continue;
+ } else {
+ appIds = ArrayUtils.appendInt(appIds, appId);
+ }
+
final String[] packageNames = pm.getPackagesForUid(app.uid);
final long[] ceDataInodes = new long[packageNames.length];
final String[] codePaths = new String[packageNames.length];
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest10.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest10.java
new file mode 100644
index 0000000..ca1e6af
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest10.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
+ .assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps.PinItemRequest;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.os.Process;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests for {@link ShortcutManager#createShortcutResultIntent(ShortcutInfo)} and relevant APIs.
+ *
+ m FrameworksServicesTests &&
+ adb install \
+ -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest10 \
+ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+public class ShortcutManagerTest10 extends BaseShortcutManagerTest {
+
+ private PinItemRequest mRequest;
+
+ private PinItemRequest verifyAndGetCreateShortcutResult(Intent resultIntent) {
+ PinItemRequest request = mLauncherApps.getPinItemRequest(resultIntent);
+ assertNotNull(request);
+ assertEquals(PinItemRequest.REQUEST_TYPE_SHORTCUT, request.getRequestType());
+ return request;
+ }
+
+ public void testCreateShortcutResult_noDefaultLauncher() {
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ ShortcutInfo s1 = makeShortcut("s1");
+ assertNull(mManager.createShortcutResultIntent(s1));
+ });
+ }
+
+ public void testCreateShortcutResult_validResult() {
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ ShortcutInfo s1 = makeShortcut("s1");
+ Intent intent = mManager.createShortcutResultIntent(s1);
+ mRequest = verifyAndGetCreateShortcutResult(intent);
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertTrue(mRequest.isValid());
+ assertTrue(mRequest.accept());
+ });
+ }
+
+ public void testCreateShortcutResult_alreadyPinned() {
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
+ });
+
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ ShortcutInfo s1 = makeShortcut("s1");
+ Intent intent = mManager.createShortcutResultIntent(s1);
+ mRequest = verifyAndGetCreateShortcutResult(intent);
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertTrue(mRequest.isValid());
+ assertTrue(mRequest.getShortcutInfo().isPinned());
+ assertTrue(mRequest.accept());
+ });
+ }
+
+ public void testCreateShortcutResult_alreadyPinnedByAnother() {
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
+ });
+
+ // Initially all launchers have the shortcut permission, until we call setDefaultLauncher().
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
+ });
+
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ ShortcutInfo s1 = makeShortcut("s1");
+ Intent intent = mManager.createShortcutResultIntent(s1);
+ mRequest = verifyAndGetCreateShortcutResult(intent);
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertTrue(mRequest.isValid());
+ assertFalse(mRequest.getShortcutInfo().isPinned());
+ assertTrue(mRequest.accept());
+ });
+ }
+
+ public void testCreateShortcutResult_defaultLauncherChanges() {
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ ShortcutInfo s1 = makeShortcut("s1");
+ Intent intent = mManager.createShortcutResultIntent(s1);
+ mRequest = verifyAndGetCreateShortcutResult(intent);
+ });
+
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
+ // Verify that other launcher can't use this request
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertFalse(mRequest.isValid());
+ assertExpectException(SecurityException.class, "Calling uid mismatch",
+ mRequest::accept);
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ // Set some random caller UID.
+ mInjectedCallingUid = 12345;
+
+ assertFalse(mRequest.isValid());
+ assertExpectException(SecurityException.class, "Calling uid mismatch",
+ mRequest::accept);
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertTrue(mRequest.isValid());
+ assertTrue(mRequest.accept());
+ });
+ }
+
+ private LauncherActivityInfo setupMockActivityInfo() {
+ doReturn(getTestContext().getPackageName()).when(mServiceContext).getPackageName();
+ doReturn(getTestContext().getContentResolver()).when(mServiceContext).getContentResolver();
+
+ LauncherActivityInfo info = mock(LauncherActivityInfo.class);
+ when(info.getComponentName()).thenReturn(
+ new ComponentName(getTestContext(), "a.ShortcutConfigActivity"));
+ when(info.getUser()).thenReturn(Process.myUserHandle());
+ return info;
+ }
+
+ public void testStartConfigActivity_defaultLauncher() {
+ LauncherActivityInfo info = setupMockActivityInfo();
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+ runWithCaller(LAUNCHER_1, USER_0, () ->
+ assertNotNull(mLauncherApps.getShortcutConfigActivityIntent(info))
+ );
+ }
+
+ public void testStartConfigActivity_nonDefaultLauncher() {
+ LauncherActivityInfo info = setupMockActivityInfo();
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+ runWithCaller(LAUNCHER_2, USER_0, () ->
+ assertExpectException(SecurityException.class, null, () ->
+ mLauncherApps.getShortcutConfigActivityIntent(info))
+ );
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
index bcd72fc..df275d2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
@@ -1421,6 +1421,35 @@
});
}
+ public void testRequestPinShortcut_wrongLauncherCannotAccept() {
+ setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ ShortcutInfo s1 = makeShortcut("s1");
+ assertTrue(mManager.requestPinShortcut(s1, null));
+ verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+ });
+
+ final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+ verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+ final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+ // Verify that other launcher can't use this request
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ // Set some random caller UID.
+ mInjectedCallingUid = 12345;
+
+ assertFalse(request.isValid());
+ assertExpectException(SecurityException.class, "Calling uid mismatch", request::accept);
+ });
+
+ // The default launcher can still use this request
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertTrue(request.isValid());
+ assertTrue(request.accept());
+ });
+ }
+
// TODO More tests:
// Cancel previous pending request and release memory?
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index 11f9ebb..480be2e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -16,13 +16,16 @@
package com.android.server.pm;
+import static com.android.server.devicepolicy.DpmTestUtils.assertRestrictions;
+import static com.android.server.devicepolicy.DpmTestUtils.newRestrictions;
+
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.devicepolicy.DpmTestUtils;
+import android.util.SparseArray;
/**
* Tests for {@link com.android.server.pm.UserRestrictionsUtils}.
@@ -49,14 +52,14 @@
public void testIsEmpty() {
assertTrue(UserRestrictionsUtils.isEmpty(null));
assertTrue(UserRestrictionsUtils.isEmpty(new Bundle()));
- assertFalse(UserRestrictionsUtils.isEmpty(DpmTestUtils.newRestrictions("a")));
+ assertFalse(UserRestrictionsUtils.isEmpty(newRestrictions("a")));
}
public void testClone() {
Bundle in = new Bundle();
Bundle out = UserRestrictionsUtils.clone(in);
assertNotSame(in, out);
- DpmTestUtils.assertRestrictions(out, new Bundle());
+ assertRestrictions(out, new Bundle());
out = UserRestrictionsUtils.clone(null);
assertNotNull(out);
@@ -64,16 +67,16 @@
}
public void testMerge() {
- Bundle a = DpmTestUtils.newRestrictions("a", "d");
- Bundle b = DpmTestUtils.newRestrictions("b", "d", "e");
+ Bundle a = newRestrictions("a", "d");
+ Bundle b = newRestrictions("b", "d", "e");
UserRestrictionsUtils.merge(a, b);
- DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+ assertRestrictions(newRestrictions("a", "b", "d", "e"), a);
UserRestrictionsUtils.merge(a, null);
- DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+ assertRestrictions(newRestrictions("a", "b", "d", "e"), a);
try {
UserRestrictionsUtils.merge(a, a);
@@ -114,25 +117,32 @@
final Bundle local = new Bundle();
final Bundle global = new Bundle();
- UserRestrictionsUtils.sortToGlobalAndLocal(null, global, local);
+ UserRestrictionsUtils.sortToGlobalAndLocal(null, false /* isDeviceOwner */,
+ UserManagerInternal.CAMERA_NOT_DISABLED, global, local);
assertEquals(0, global.size());
assertEquals(0, local.size());
- UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, global, local);
+ UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, false /* isDeviceOwner */,
+ UserManagerInternal.CAMERA_NOT_DISABLED, global, local);
assertEquals(0, global.size());
assertEquals(0, local.size());
- UserRestrictionsUtils.sortToGlobalAndLocal(DpmTestUtils.newRestrictions(
+ // Restrictions set by DO.
+ UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(
UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_UNMUTE_MICROPHONE,
UserManager.DISALLOW_USB_FILE_TRANSFER,
UserManager.DISALLOW_CONFIG_TETHERING,
UserManager.DISALLOW_OUTGOING_BEAM,
- UserManager.DISALLOW_APPS_CONTROL
- ), global, local);
+ UserManager.DISALLOW_APPS_CONTROL,
+ UserManager.ENSURE_VERIFY_APPS
+ ), true /* isDeviceOwner */, UserManagerInternal.CAMERA_NOT_DISABLED, global, local);
- DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+ assertRestrictions(newRestrictions(
+ // This one is global no matter who sets it.
+ UserManager.ENSURE_VERIFY_APPS,
+
// These can be set by PO too, but when DO sets them, they're global.
UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_UNMUTE_MICROPHONE,
@@ -142,11 +152,117 @@
UserManager.DISALLOW_CONFIG_TETHERING
), global);
- DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+ assertRestrictions(newRestrictions(
// They can be set by both DO/PO.
UserManager.DISALLOW_OUTGOING_BEAM,
UserManager.DISALLOW_APPS_CONTROL
), local);
+
+ local.clear();
+ global.clear();
+
+ // Restrictions set by PO.
+ UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE,
+ UserManager.DISALLOW_USB_FILE_TRANSFER,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_OUTGOING_BEAM,
+ UserManager.DISALLOW_APPS_CONTROL,
+ UserManager.ENSURE_VERIFY_APPS
+ ), false /* isDeviceOwner */, UserManagerInternal.CAMERA_NOT_DISABLED, global, local);
+
+ assertRestrictions(newRestrictions(
+ // This one is global no matter who sets it.
+ UserManager.ENSURE_VERIFY_APPS
+ ), global);
+
+ assertRestrictions(newRestrictions(
+ // These can be set by PO too, but when PO sets them, they're local.
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_UNMUTE_MICROPHONE,
+
+ // They can be set by both DO/PO.
+ UserManager.DISALLOW_OUTGOING_BEAM,
+ UserManager.DISALLOW_APPS_CONTROL,
+
+ // These can only be set by DO.
+ UserManager.DISALLOW_USB_FILE_TRANSFER,
+ UserManager.DISALLOW_CONFIG_TETHERING
+ ), local);
+
+ }
+
+ public void testSortToLocalAndGlobalWithCameraDisabled() {
+ final Bundle local = new Bundle();
+ final Bundle global = new Bundle();
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, false,
+ UserManagerInternal.CAMERA_DISABLED_GLOBALLY, global, local);
+ assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), global);
+ assertEquals(0, local.size());
+ global.clear();
+
+ UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, false,
+ UserManagerInternal.CAMERA_DISABLED_LOCALLY, global, local);
+ assertEquals(0, global.size());
+ assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), local);
+ }
+
+ public void testMergeAll() {
+ SparseArray<Bundle> restrictions = new SparseArray<>();
+ assertNull(UserRestrictionsUtils.mergeAll(restrictions));
+
+ restrictions.put(0, newRestrictions(UserManager.DISALLOW_ADJUST_VOLUME));
+ restrictions.put(1, newRestrictions(UserManager.DISALLOW_USB_FILE_TRANSFER));
+ restrictions.put(2, newRestrictions(UserManager.DISALLOW_APPS_CONTROL));
+
+ Bundle result = UserRestrictionsUtils.mergeAll(restrictions);
+ assertRestrictions(
+ newRestrictions(
+ UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_USB_FILE_TRANSFER,
+ UserManager.DISALLOW_APPS_CONTROL),
+ result);
+ }
+
+ public void testMoveRestriction() {
+ SparseArray<Bundle> localRestrictions = new SparseArray<>();
+ SparseArray<Bundle> globalRestrictions = new SparseArray<>();
+
+ // User 0 has only local restrictions, nothing should change.
+ localRestrictions.put(0, newRestrictions(UserManager.DISALLOW_ADJUST_VOLUME));
+ // User 1 has a local restriction to be moved to global and some global already. Local
+ // restrictions should be removed for this user.
+ localRestrictions.put(1, newRestrictions(UserManager.ENSURE_VERIFY_APPS));
+ globalRestrictions.put(1, newRestrictions(UserManager.DISALLOW_ADD_USER));
+ // User 2 has a local restriction to be moved and one to leave local.
+ localRestrictions.put(2, newRestrictions(
+ UserManager.ENSURE_VERIFY_APPS,
+ UserManager.DISALLOW_CONFIG_VPN));
+
+ UserRestrictionsUtils.moveRestriction(
+ UserManager.ENSURE_VERIFY_APPS, localRestrictions, globalRestrictions);
+
+ // Check user 0.
+ assertRestrictions(
+ newRestrictions(UserManager.DISALLOW_ADJUST_VOLUME),
+ localRestrictions.get(0));
+ assertNull(globalRestrictions.get(0));
+
+ // Check user 1.
+ assertNull(localRestrictions.get(1));
+ assertRestrictions(
+ newRestrictions(UserManager.ENSURE_VERIFY_APPS, UserManager.DISALLOW_ADD_USER),
+ globalRestrictions.get(1));
+
+ // Check user 2.
+ assertRestrictions(
+ newRestrictions(UserManager.DISALLOW_CONFIG_VPN),
+ localRestrictions.get(2));
+ assertRestrictions(
+ newRestrictions(UserManager.ENSURE_VERIFY_APPS),
+ globalRestrictions.get(2));
}
public void testAreEqual() {
@@ -172,33 +288,33 @@
assertFalse(UserRestrictionsUtils.areEqual(
null,
- DpmTestUtils.newRestrictions("a")));
+ newRestrictions("a")));
assertFalse(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("a"),
+ newRestrictions("a"),
null));
assertTrue(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("a"),
- DpmTestUtils.newRestrictions("a")));
+ newRestrictions("a"),
+ newRestrictions("a")));
assertFalse(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("a"),
- DpmTestUtils.newRestrictions("a", "b")));
+ newRestrictions("a"),
+ newRestrictions("a", "b")));
assertFalse(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("a", "b"),
- DpmTestUtils.newRestrictions("a")));
+ newRestrictions("a", "b"),
+ newRestrictions("a")));
assertFalse(UserRestrictionsUtils.areEqual(
- DpmTestUtils.newRestrictions("b", "a"),
- DpmTestUtils.newRestrictions("a", "a")));
+ newRestrictions("b", "a"),
+ newRestrictions("a", "a")));
// Make sure false restrictions are handled correctly.
- final Bundle a = DpmTestUtils.newRestrictions("a");
+ final Bundle a = newRestrictions("a");
a.putBoolean("b", true);
- final Bundle b = DpmTestUtils.newRestrictions("a");
+ final Bundle b = newRestrictions("a");
b.putBoolean("b", false);
assertFalse(UserRestrictionsUtils.areEqual(a, b));
diff --git a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
new file mode 100644
index 0000000..e2aff16
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.policy;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.provider.Settings;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.test.mock.MockContentResolver;
+import android.text.TextUtils;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.widget.Toast;
+import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.policy.AccessibilityShortcutController.FrameworkObjectProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.Whitebox;
+
+import java.util.Collections;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityShortcutControllerTest {
+ private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
+
+ private @Mock Context mContext;
+ private @Mock FrameworkObjectProvider mFrameworkObjectProvider;
+ private @Mock IAccessibilityManager mAccessibilityManagerService;
+ private @Mock Handler mHandler;
+ private @Mock AlertDialog.Builder mAlertDialogBuilder;
+ private @Mock AlertDialog mAlertDialog;
+ private @Mock AccessibilityServiceInfo mServiceInfo;
+ private @Mock Resources mResources;
+ private @Mock Toast mToast;
+
+ private MockContentResolver mContentResolver;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContentResolver = new MockContentResolver(mContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.getResources()).thenReturn(mResources);
+
+ when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(anyInt()))
+ .thenReturn(Collections.singletonList(mServiceInfo));
+
+ // Use the extra level of indirection in the object to mock framework objects
+ AccessibilityManager accessibilityManager =
+ new AccessibilityManager(mHandler, mAccessibilityManagerService, 0);
+ when(mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext))
+ .thenReturn(accessibilityManager);
+ when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext))
+ .thenReturn(mAlertDialogBuilder);
+ when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt()))
+ .thenReturn(mToast);
+
+ when(mResources.getString(anyInt())).thenReturn("Howdy %s");
+ ResolveInfo resolveInfo = mock(ResolveInfo.class);
+ when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name");
+ when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
+ when(mServiceInfo.getComponentName())
+ .thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING));
+
+ when(mAlertDialogBuilder.setTitle(anyInt())).thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject()))
+ .thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setNegativeButton(anyInt(), anyObject()))
+ .thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setOnCancelListener(anyObject())).thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.create()).thenReturn(mAlertDialog);
+
+ Window window = mock(Window.class);
+ Whitebox.setInternalState(window, "mWindowAttributes", new WindowManager.LayoutParams());
+ when(mAlertDialog.getWindow()).thenReturn(window);
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ @Test
+ public void testShortcutAvailable_withNullServiceIdWhenCreated_shouldReturnFalse() {
+ configureShortcutDisabled();
+ assertFalse(getController().isAccessibilityShortcutAvailable());
+ }
+
+ @Test
+ public void testShortcutAvailable_withNonNullServiceIdWhenCreated_shouldReturnTrue() {
+ configureShortcutEnabled();
+ assertTrue(getController().isAccessibilityShortcutAvailable());
+ }
+
+ @Test
+ public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse() {
+ configureShortcutEnabled();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
+ accessibilityShortcutController.onSettingsChanged();
+ assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable());
+ }
+
+ @Test
+ public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue() {
+ configureShortcutDisabled();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ configureShortcutEnabled();
+ accessibilityShortcutController.onSettingsChanged();
+ assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable());
+ }
+
+ @Test
+ public void testOnAccessibilityShortcut_firstTime_showsWarningDialog()
+ throws Exception {
+ configureShortcutEnabled();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+ accessibilityShortcutController.performAccessibilityShortcut();
+
+ assertEquals(1, Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0));
+ verify(mResources).getString(R.string.accessibility_shortcut_toogle_warning);
+ verify(mAlertDialog).show();
+ verify(mAccessibilityManagerService).getInstalledAccessibilityServiceList(anyInt());
+ verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
+ }
+
+ @Test
+ public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
+ throws Exception {
+ configureShortcutEnabled();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+ accessibilityShortcutController.performAccessibilityShortcut();
+ accessibilityShortcutController.performAccessibilityShortcut();
+ verify(mToast).show();
+ verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
+ }
+
+ @Test
+ public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
+ throws Exception {
+ configureShortcutEnabled();
+ AccessibilityShortcutController accessibilityShortcutController = getController();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+ accessibilityShortcutController.performAccessibilityShortcut();
+ ArgumentCaptor<AlertDialog.OnCancelListener> cancelListenerCaptor =
+ ArgumentCaptor.forClass(AlertDialog.OnCancelListener.class);
+ verify(mAlertDialogBuilder).setOnCancelListener(cancelListenerCaptor.capture());
+ // Call the cancel callback
+ cancelListenerCaptor.getValue().onCancel(null);
+
+ accessibilityShortcutController.performAccessibilityShortcut();
+ verify(mAlertDialog, times(2)).show();
+ }
+
+ @Test
+ public void testClickingDisableButtonInDialog_shouldClearShortcutId() {
+ configureShortcutEnabled();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.disable_accessibility_shortcut),
+ captor.capture());
+ // Call the button callback
+ captor.getValue().onClick(null, 0);
+ assertTrue(TextUtils.isEmpty(
+ Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)));
+ }
+
+ @Test
+ public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
+ configureShortcutEnabled();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.leave_accessibility_shortcut_on),
+ captor.capture());
+ // Call the button callback, if one exists
+ if (captor.getValue() != null) {
+ captor.getValue().onClick(null, 0);
+ }
+ assertEquals(SERVICE_NAME_STRING,
+ Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
+ assertEquals(1, Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
+ }
+
+ @Test
+ public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
+ configureShortcutEnabled();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
+ getController().performAccessibilityShortcut();
+
+ verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
+ verify(mToast).show();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ }
+
+ private void configureShortcutDisabled() {
+ Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
+ }
+
+ private void configureShortcutEnabled() {
+ Settings.Secure.putString(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
+ }
+
+ private AccessibilityShortcutController getController() {
+ AccessibilityShortcutController accessibilityShortcutController =
+ new AccessibilityShortcutController(mContext, mHandler);
+ accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
+ return accessibilityShortcutController;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index d2512ac..83a61ca 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.ContentObserver;
import android.webkit.WebViewProviderInfo;
import java.util.HashMap;
@@ -31,6 +30,7 @@
private boolean mFallbackLogicEnabled;
private final int mNumRelros;
private final boolean mIsDebuggable;
+ private int mMultiProcessSetting;
public TestSystemImpl(WebViewProviderInfo[] packageConfigs, boolean fallbackLogicEnabled,
int numRelros, boolean isDebuggable) {
@@ -121,8 +121,15 @@
}
@Override
- public void setMultiProcessEnabledFromContext(Context context) {}
+ public int getMultiProcessSetting(Context context) {
+ return mMultiProcessSetting;
+ }
@Override
- public void registerContentObserver(Context context, ContentObserver contentObserver) {}
+ public void setMultiProcessSetting(Context context, int value) {
+ mMultiProcessSetting = value;
+ }
+
+ @Override
+ public void notifyZygote(boolean enableMultiProcess) {}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
new file mode 100644
index 0000000..48d4770
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.wm;
+
+import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
+import static android.graphics.GraphicBuffer.USAGE_SW_READ_NEVER;
+import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_NEVER;
+import static android.graphics.GraphicBuffer.create;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskSnapshotCache}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotCacheTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotCacheTest extends WindowTestsBase {
+
+ @Test
+ public void testCleanCache() throws Exception {
+ TaskSnapshotCache snapshotCache = new TaskSnapshotCache();
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ snapshotCache.putSnapshot(window.getTask(), createSnapshot());
+ assertNotNull(snapshotCache.getSnapshot(window.getTask()));
+ snapshotCache.cleanCache(window.mAppToken);
+ assertNull(snapshotCache.getSnapshot(window.getTask()));
+ }
+
+ private TaskSnapshot createSnapshot() {
+ GraphicBuffer buffer = create(1, 1, PixelFormat.RGBA_8888,
+ USAGE_HW_TEXTURE | USAGE_SW_WRITE_NEVER | USAGE_SW_READ_NEVER);
+ return new TaskSnapshot(buffer, Configuration.ORIENTATION_PORTRAIT, new Rect());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 44d5055..dd5077b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -23,6 +23,8 @@
import org.junit.Assert;
import org.junit.Before;
+import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
import android.content.Context;
import android.os.IBinder;
import android.support.test.InstrumentationRegistry;
@@ -238,7 +240,7 @@
}
TestTaskWindowContainerController(int stackId) {
- super(sNextTaskId++, stackId, 0 /* userId */, null /* bounds */,
+ super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */,
EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/,
false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */);
}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 3be04d4..68765b6 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -31,9 +31,10 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemService;
@@ -66,6 +67,7 @@
private final UserManager mUser;
private final PackageManager mPackage;
private final StorageManager mStorage;
+
private final Installer mInstaller;
public StorageStatsService(Context context) {
@@ -74,8 +76,28 @@
mUser = context.getSystemService(UserManager.class);
mPackage = context.getSystemService(PackageManager.class);
mStorage = context.getSystemService(StorageManager.class);
+
mInstaller = new Installer(context);
mInstaller.onStart();
+ invalidateMounts();
+
+ mStorage.registerListener(new StorageEventListener() {
+ @Override
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ if ((vol.type == VolumeInfo.TYPE_PRIVATE)
+ && (newState == VolumeInfo.STATE_MOUNTED)) {
+ invalidateMounts();
+ }
+ }
+ });
+ }
+
+ private void invalidateMounts() {
+ try {
+ mInstaller.invalidateMounts();
+ } catch (InstallerException e) {
+ Slog.wtf(TAG, "Failed to invalidate mounts", e);
+ }
}
private void enforcePermission(int callingUid, String callingPackage) {
@@ -242,7 +264,7 @@
private static void checkEquals(String msg, long expected, long actual) {
if (expected != actual) {
- Log.e(TAG, msg + " expected " + expected + " actual " + actual);
+ Slog.e(TAG, msg + " expected " + expected + " actual " + actual);
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4bfc3df..d243bf2 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.IUidObserver;
import android.app.admin.DevicePolicyManager;
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
@@ -38,9 +39,9 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
@@ -49,6 +50,7 @@
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
@@ -80,6 +82,7 @@
import java.io.File;
import java.io.FileDescriptor;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -103,6 +106,8 @@
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
+ private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
+
long mAppIdleScreenThresholdMillis;
long mCheckIdleIntervalMillis;
long mAppIdleWallclockThresholdMillis;
@@ -134,6 +139,7 @@
private IBatteryStats mBatteryStats;
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
+ private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
private File mUsageStatsDir;
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
@@ -235,6 +241,19 @@
postOneTimeCheckIdleStates();
}
+ if (KERNEL_COUNTER_FILE.exists()) {
+ try {
+ ActivityManager.getService().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE
+ | ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
+ }
+
mSystemServicesReady = true;
} else if (phase == PHASE_BOOT_COMPLETED) {
setChargingState(getContext().getSystemService(BatteryManager.class).isCharging());
@@ -311,6 +330,39 @@
}
};
+ private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+ @Override
+ public void onUidStateChanged(int uid, int procState) {
+ final int newCounter = (procState <= ActivityManager.PROCESS_STATE_TOP) ? 0 : 1;
+ synchronized (mUidToKernelCounter) {
+ final int oldCounter = mUidToKernelCounter.get(uid, 0);
+ if (newCounter != oldCounter) {
+ mUidToKernelCounter.put(uid, newCounter);
+ try {
+ FileUtils.stringToFile(KERNEL_COUNTER_FILE, uid + " " + newCounter);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to update counter set: " + e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onUidIdle(int uid, boolean disabled) throws RemoteException {
+ // Ignored
+ }
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) throws RemoteException {
+ onUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
+ }
+
+ @Override
+ public void onUidActive(int uid) throws RemoteException {
+ // Ignored
+ }
+ };
+
@Override
public void onStatsUpdated() {
mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index b2a9a49..00420e9 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.tethering;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -114,6 +116,9 @@
mUNM.registerMobileNetworkRequest();
assertTrue(mUNM.mobileNetworkRequested());
assertEquals(1, mCM.requested.size());
+ assertEquals(1, mCM.legacyTypeMap.size());
+ assertEquals(Integer.valueOf(TYPE_MOBILE_HIPRI),
+ mCM.legacyTypeMap.values().iterator().next());
assertFalse(mCM.isDunRequested());
mUNM.stop();
@@ -137,6 +142,9 @@
mUNM.registerMobileNetworkRequest();
assertTrue(mUNM.mobileNetworkRequested());
assertEquals(1, mCM.requested.size());
+ assertEquals(1, mCM.legacyTypeMap.size());
+ assertEquals(Integer.valueOf(TYPE_MOBILE_DUN),
+ mCM.legacyTypeMap.values().iterator().next());
assertTrue(mCM.isDunRequested());
mUNM.stop();
@@ -148,6 +156,7 @@
public Set<NetworkCallback> trackingDefault = new HashSet<>();
public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
+ public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
super(ctx, svc);
@@ -156,7 +165,8 @@
boolean isEmpty() {
return trackingDefault.isEmpty() &&
listening.isEmpty() &&
- requested.isEmpty();
+ requested.isEmpty() &&
+ legacyTypeMap.isEmpty();
}
boolean isListeningForDun() {
@@ -184,6 +194,17 @@
}
@Override
+ public void requestNetwork(NetworkRequest req, NetworkCallback cb,
+ int timeoutMs, int legacyType) {
+ assertFalse(requested.containsKey(cb));
+ requested.put(cb, req);
+ assertFalse(legacyTypeMap.containsKey(cb));
+ if (legacyType != ConnectivityManager.TYPE_NONE) {
+ legacyTypeMap.put(cb, legacyType);
+ }
+ }
+
+ @Override
public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
assertFalse(listening.containsKey(cb));
listening.put(cb, req);
@@ -203,6 +224,7 @@
listening.remove(cb);
} else if (requested.containsKey(cb)) {
requested.remove(cb);
+ legacyTypeMap.remove(cb);
}
assertFalse(trackingDefault.contains(cb));
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
index b071093..9dceba2 100755
--- a/tools/localedata/extract_icu_data.py
+++ b/tools/localedata/extract_icu_data.py
@@ -48,6 +48,8 @@
# they may be used by apps for other purposes.)
"en_XA": "~~~A",
"ar_XB": "~~~B",
+ # Removed data from later versions of ICU
+ "ji": "Hebr", # Old code for Yiddish, still used in Java and Android
}
representative_locales = {
# Android's additions
@@ -69,7 +71,7 @@
_, to_scr, to_region = get_locale_parts(to_locale)
if from_lang == 'und':
continue # not very useful for our purposes
- if from_region is None and to_region != '001':
+ if from_region is None and to_region not in ['001', 'ZZ']:
representative_locales.add(to_locale)
if from_scr is None:
likely_script_dict[from_locale] = to_scr