Merge "Import translations. DO NOT MERGE"
diff --git a/api/current.txt b/api/current.txt
index 96c47ed..4e7d2dc 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>);
@@ -29888,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;
@@ -30305,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";
}
@@ -35249,6 +35271,7 @@
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+ field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
@@ -35454,7 +35477,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> {
diff --git a/api/system-current.txt b/api/system-current.txt
index 497b963..0acfea2 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();
@@ -5758,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";
@@ -5777,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);
@@ -9392,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";
@@ -10215,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();
@@ -10891,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>);
@@ -21907,6 +21919,7 @@
method public int getClientPid();
method public int getClientUid();
method public int getPlayerInterfaceId();
+ method public android.media.PlayerProxy getPlayerProxy();
method public int getPlayerState();
method public int getPlayerType();
method public void writeToParcel(android.os.Parcel, int);
@@ -23735,6 +23748,13 @@
field public static final android.os.Parcelable.Creator<android.media.PlaybackParams> CREATOR;
}
+ public class PlayerProxy {
+ method public void pause() throws java.lang.IllegalStateException;
+ method public void setVolume(float) throws java.lang.IllegalStateException;
+ method public void start() throws java.lang.IllegalStateException;
+ method public void stop() throws java.lang.IllegalStateException;
+ }
+
public final class Rating implements android.os.Parcelable {
method public int describeContents();
method public float getPercentRating();
@@ -32501,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;
@@ -33013,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";
}
@@ -38140,6 +38170,7 @@
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+ field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
@@ -38345,7 +38376,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> {
diff --git a/api/test-current.txt b/api/test-current.txt
index 96ddfc7..8abaf84 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>);
@@ -29998,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;
@@ -30416,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";
}
@@ -35368,6 +35390,7 @@
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+ field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
@@ -35573,7 +35596,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> {
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/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/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/Notification.java b/core/java/android/app/Notification.java
index c6f0b66..5b74e23 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);
}
}
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/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/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/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/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/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/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/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index c2e980c..805d8e5 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -61,6 +61,19 @@
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+ /**
+ * Name under which a AutoFillService component publishes information about itself.
+ * This meta-data should reference an XML resource containing a
+ * <code><{@link
+ * android.R.styleable#AutoFillService autofill-service}></code> tag.
+ * This is a a sample XML file configuring an AutoFillService:
+ * <pre> <autofill-service
+ * android:settingsActivity="foo.bar.SettingsActivity"
+ * . . .
+ * /></pre>
+ */
+ public static final String SERVICE_META_DATA = "android.autofill";
+
// Internal bundle keys.
/** @hide */ public static final String KEY_CALLBACK = "callback";
/** @hide */ public static final String KEY_SAVABLE_IDS = "savable_ids";
diff --git a/core/java/android/service/autofill/AutoFillServiceInfo.java b/core/java/android/service/autofill/AutoFillServiceInfo.java
index fe21615..ab86580 100644
--- a/core/java/android/service/autofill/AutoFillServiceInfo.java
+++ b/core/java/android/service/autofill/AutoFillServiceInfo.java
@@ -16,20 +16,34 @@
package android.service.autofill;
import android.Manifest;
+import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
/** @hide */
public final class AutoFillServiceInfo {
+ static final String TAG = "AutoFillServiceInfo";
private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
throws PackageManager.NameNotFoundException {
try {
- final ServiceInfo si =
- AppGlobals.getPackageManager().getServiceInfo(comp, 0, userHandle);
+ ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
+ comp,
+ PackageManager.GET_META_DATA,
+ userHandle);
if (si != null) {
return si;
}
@@ -38,11 +52,21 @@
throw new PackageManager.NameNotFoundException(comp.toString());
}
+ @Nullable
private String mParseError;
+ @Nullable
private ServiceInfo mServiceInfo;
+ @Nullable
+ private String mSettingsActivity;
- private AutoFillServiceInfo(ServiceInfo si) {
+ public AutoFillServiceInfo(PackageManager pm, ComponentName comp, int userHandle)
+ throws PackageManager.NameNotFoundException {
+ this(pm, getServiceInfoOrThrow(comp, userHandle));
+ }
+
+ public AutoFillServiceInfo(PackageManager pm, ServiceInfo si)
+ throws PackageManager.NameNotFoundException{
if (si == null) {
mParseError = "Service not available";
return;
@@ -53,19 +77,57 @@
return;
}
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, AutoFillService.SERVICE_META_DATA);
+ if (parser == null) {
+ mParseError = "No " + AutoFillService.SERVICE_META_DATA
+ + " meta-data for " + si.packageName;
+ return;
+ }
+
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"autofill-service".equals(nodeName)) {
+ mParseError = "Meta-data does not start with autofill-service tag";
+ return;
+ }
+
+ TypedArray array = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AutoFillService);
+ mSettingsActivity = array.getString(
+ com.android.internal.R.styleable.AutoFillService_settingsActivity);
+ array.recycle();
+ } catch (XmlPullParserException | IOException | PackageManager.NameNotFoundException e) {
+ mParseError = "Error parsing auto fill service meta-data: " + e;
+ Log.w(TAG, "error parsing auto fill service meta-data", e);
+ return;
+ } finally {
+ if (parser != null) parser.close();
+ }
mServiceInfo = si;
}
- public AutoFillServiceInfo(ComponentName comp, int userHandle)
- throws PackageManager.NameNotFoundException {
- this(getServiceInfoOrThrow(comp, userHandle));
- }
-
+ @Nullable
public String getParseError() {
return mParseError;
}
+ @Nullable
public ServiceInfo getServiceInfo() {
return mServiceInfo;
}
+
+ @Nullable
+ public String getSettingsActivity() {
+ return mSettingsActivity;
+ }
}
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/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/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..a5c1a94 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
@@ -7615,6 +7617,21 @@
</declare-styleable>
<!-- =============================== -->
+ <!-- AutoFill attributes -->
+ <!-- =============================== -->
+ <eat-comment />
+
+ <!-- Use <code>autofill-service</code> as the root tag of the XML resource that describes a
+ {@link android.service.autofill.AutoFillService}, which is referenced from its
+ {@link android.service.autofill#SERVICE_META_DATA} meta-data entry.
+ -->
+ <declare-styleable name="AutoFillService">
+ <!-- Fully qualified class name of an activity that allows the user to modify
+ the settings for this service. -->
+ <attr name="settingsActivity" />
+ </declare-styleable>
+
+ <!-- =============================== -->
<!-- Contacts meta-data attributes -->
<!-- =============================== -->
<eat-comment />
@@ -8428,7 +8445,7 @@
<!-- @hide Attributes which will be read by the Activity to intialize the
base activity TaskDescription. -->
<declare-styleable name="ActivityTaskDescription">
- <!-- @hide From Theme.colorPrimary, used for the TaskDescription primary
+ <!-- @hide From Theme.colorPrimary, used for the TaskDescription primary
color. -->
<attr name="colorPrimary" />
<!-- @hide From Theme.colorBackground, used for the TaskDescription background
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/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index b38a07f..dd9c6a7 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -283,10 +283,19 @@
/**
* @hide
- * FIXME return a player proxy instead, make systemApi
- * @return
+ * Return a proxy for the player associated with this playback configuration
+ * @return a proxy player
*/
- public IPlayer getPlayerProxy() {
+ @SystemApi
+ public PlayerProxy getPlayerProxy() {
+ return mIPlayerShell == null ? null : new PlayerProxy(this);
+ }
+
+ /**
+ * @hide
+ * @return the IPlayer interface for the associated player
+ */
+ IPlayer getIPlayer() {
return mIPlayerShell == null ? null : mIPlayerShell.getIPlayer();
}
diff --git a/media/java/android/media/PlayerProxy.java b/media/java/android/media/PlayerProxy.java
new file mode 100644
index 0000000..171be27
--- /dev/null
+++ b/media/java/android/media/PlayerProxy.java
@@ -0,0 +1,109 @@
+/*
+ * 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 android.media;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.IllegalArgumentException;
+import java.util.Objects;
+
+/**
+ * Class to remotely control a player.
+ * @hide
+ */
+@SystemApi
+public class PlayerProxy {
+
+ private final static String TAG = "PlayerProxy";
+ private final static boolean DEBUG = false;
+
+ private final AudioPlaybackConfiguration mConf; // never null
+
+ /**
+ * @hide
+ * Constructor. Proxy for this player associated with this AudioPlaybackConfiguration
+ * @param conf the configuration being proxied.
+ */
+ PlayerProxy(@NonNull AudioPlaybackConfiguration apc) {
+ if (apc == null) {
+ throw new IllegalArgumentException("Illegal null AudioPlaybackConfiguration");
+ }
+ mConf = apc;
+ };
+
+ //=====================================================================
+ // Methods matching the IPlayer interface
+ /**
+ * @hide
+ * @throws IllegalStateException
+ */
+ @SystemApi
+ public void start() throws IllegalStateException {
+ try {
+ mConf.getIPlayer().start();
+ } catch (NullPointerException|RemoteException e) {
+ throw new IllegalStateException(
+ "No player to proxy for start operation, player already released?", e);
+ }
+ }
+
+ /**
+ * @hide
+ * @throws IllegalStateException
+ */
+ @SystemApi
+ public void pause() throws IllegalStateException {
+ try {
+ mConf.getIPlayer().pause();
+ } catch (NullPointerException|RemoteException e) {
+ throw new IllegalStateException(
+ "No player to proxy for pause operation, player already released?", e);
+ }
+ }
+
+ /**
+ * @hide
+ * @throws IllegalStateException
+ */
+ @SystemApi
+ public void stop() throws IllegalStateException {
+ try {
+ mConf.getIPlayer().stop();
+ } catch (NullPointerException|RemoteException e) {
+ throw new IllegalStateException(
+ "No player to proxy for stop operation, player already released?", e);
+ }
+ }
+
+ /**
+ * @hide
+ * @throws IllegalStateException
+ */
+ @SystemApi
+ public void setVolume(float vol) throws IllegalStateException {
+ try {
+ mConf.getIPlayer().setVolume(vol);
+ } catch (NullPointerException|RemoteException e) {
+ throw new IllegalStateException(
+ "No player to proxy for setVolume operation, player already released?", e);
+ }
+ }
+
+}
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/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 2f8a78b..cf6357b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -745,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,
@@ -777,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/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/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/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/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 19034f8..faf143e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1132,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);
@@ -1519,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());
@@ -1599,6 +1605,7 @@
View bigContentViewLocal = null;
View headsUpContentViewLocal = null;
View publicViewLocal = null;
+ View ambientViewLocal = null;
try {
contentViewLocal = contentView.apply(
sbn.getPackageContext(mContext),
@@ -1621,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);
@@ -1638,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());
@@ -2134,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();
@@ -2171,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());
}
@@ -2383,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 9ce2115..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);
}
@@ -339,6 +357,10 @@
return mHeadsUpChild;
}
+ public View getAmbientChild() {
+ return mAmbientChild;
+ }
+
public void setContractedChild(View child) {
if (mContractedChild != null) {
mContractedChild.animate().cancel();
@@ -373,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);
@@ -447,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 =
@@ -651,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);
}
}
@@ -717,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);
}
}
@@ -779,6 +810,8 @@
return mHeadsUpWrapper;
case VISIBLE_TYPE_SINGLELINE:
return mSingleLineView;
+ case VISIBLE_TYPE_AMBIENT:
+ return mAmbientWrapper;
default:
return mContractedWrapper;
}
@@ -796,6 +829,8 @@
return mHeadsUpChild;
case VISIBLE_TYPE_SINGLELINE:
return mSingleLineView;
+ case VISIBLE_TYPE_AMBIENT:
+ return mAmbientChild;
default:
return mContractedChild;
}
@@ -809,6 +844,8 @@
return mHeadsUpWrapper;
case VISIBLE_TYPE_CONTRACTED:
return mContractedWrapper;
+ case VISIBLE_TYPE_AMBIENT:
+ return mAmbientWrapper;
default:
return null;
}
@@ -818,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 */)
@@ -890,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) {
@@ -942,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 */);
@@ -1128,6 +1173,9 @@
if (header == null && mHeadsUpChild != null) {
header = mHeadsUpWrapper.getNotificationHeader();
}
+ if (header == null && mAmbientChild != null) {
+ header = mAmbientWrapper.getNotificationHeader();
+ }
return header;
}
@@ -1195,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 c06e639..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;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 64c4949..e8e9d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -129,6 +129,7 @@
} else {
mViewInvertHelper.update(dark);
}
+ mShelfIcons.setAmbient(dark);
}
@Override
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/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 5998ed2..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,
@@ -2401,6 +2401,7 @@
return getBarState() == StatusBarState.KEYGUARD;
}
+ @Override
public boolean isDozing() {
return mDozing;
}
@@ -2487,6 +2488,9 @@
}
} else {
updateNotificationRanking(null);
+ if (isHeadsUp) {
+ mDozeServiceHost.fireNotificationHeadsUp();
+ }
}
}
@@ -2886,9 +2890,6 @@
@Override // CommandQueue
public void buzzBeepBlinked() {
- if (mDozeServiceHost != null) {
- mDozeServiceHost.fireBuzzBeepBlinked();
- }
}
@Override
@@ -3518,6 +3519,9 @@
if (mSecurityController != null) {
mSecurityController.onUserSwitched(mCurrentUserId);
}
+ if (mNetworkController != null) {
+ mNetworkController.onUserSwitched(mCurrentUserId);
+ }
}
private void resetUserSetupObserver() {
@@ -4234,6 +4238,7 @@
mDozeScrimController.setDozing(mDozing &&
mFingerprintUnlockController.getMode()
!= FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, animate);
+ updateRowStates();
Trace.endSection();
}
@@ -4904,9 +4909,9 @@
}
}
- public void fireBuzzBeepBlinked() {
+ public void fireNotificationHeadsUp() {
for (Callback callback : mCallbacks) {
- callback.onBuzzBeepBlinked();
+ callback.onNotificationHeadsUp();
}
}
@@ -4950,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/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 9545fd8..395e8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1883,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;
}
@@ -3486,6 +3490,8 @@
updateBackground();
setWillNotDraw(false);
}
+ updateContentHeight();
+ notifyHeightChangeListener(mShelf);
}
private void setBackgroundFadeAmount(float fadeAmount) {
@@ -3921,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/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index dc92f56..690eb88 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3191,8 +3191,100 @@
// 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;
+
+ // OPEN: Settings > Apps > Default Apps > Default auto-fill app
+ DEFAULT_AUTO_FILL_PICKER = 792;
+
// ---- 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/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/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index 3de8a8b..ae21b07 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -202,7 +202,7 @@
final AutoFillServiceInfo info;
try {
- info = new AutoFillServiceInfo(component, mUserId);
+ info = new AutoFillServiceInfo(context.getPackageManager(), component, mUserId);
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Auto-fill service not found: " + component, e);
mInfo = null;
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/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/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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f3f9ce..df5f01d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -144,7 +144,8 @@
*/
public class AudioService extends IAudioService.Stub
implements AccessibilityManager.TouchExplorationStateChangeListener,
- AccessibilityManager.AccessibilityStateChangeListener{
+ AccessibilityManager.AccessibilityStateChangeListener,
+ AccessibilityManager.AccessibilityServicesStateChangeListener {
private static final String TAG = "AudioService";
@@ -780,7 +781,7 @@
TAG,
SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
- initA11yMonitoring(mContext);
+ initA11yMonitoring();
onIndicateSystemReady();
}
@@ -5925,13 +5926,25 @@
//==========================================================================================
// Accessibility
- private void initA11yMonitoring(Context ctxt) {
- AccessibilityManager accessibilityManager =
- (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ /**
+ * Compile-time constant to enable the use of an independent a11y volume:
+ * - set to true to listen to a11y services state changes and read
+ * the whether any exposes the FLAG_ENABLE_ACCESSIBILITY_VOLUME flag
+ * - set to false to listen to when accessibility services are started (e.g. "TalkBack started")
+ */
+ private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = true;
+
+ private void initA11yMonitoring() {
+ final AccessibilityManager accessibilityManager =
+ (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
updateDefaultStreamOverrideDelay(accessibilityManager.isTouchExplorationEnabled());
updateA11yVolumeAlias(accessibilityManager.isEnabled());
accessibilityManager.addTouchExplorationStateChangeListener(this);
- accessibilityManager.addAccessibilityStateChangeListener(this);
+ if (USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME) {
+ accessibilityManager.addAccessibilityServicesStateChangeListener(this);
+ } else {
+ accessibilityManager.addAccessibilityStateChangeListener(this);
+ }
}
//---------------------------------------------------------------------------------
@@ -5969,21 +5982,31 @@
private static boolean sIndependentA11yVolume = false;
+ // implementation of AccessibilityStateChangeListener
@Override
public void onAccessibilityStateChanged(boolean enabled) {
updateA11yVolumeAlias(enabled);
}
- private void updateA11yVolumeAlias(boolean a11Enabled) {
- if (DEBUG_VOL) Log.d(TAG, "Accessibility mode changed to " + a11Enabled);
- // a11y has its own volume stream when a11y service is enabled
- sIndependentA11yVolume = a11Enabled;
- // update the volume mapping scheme
- updateStreamVolumeAlias(true /*updateVolumes*/, TAG);
- // update the volume controller behavior
- mVolumeController.setA11yMode(sIndependentA11yVolume ?
- VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
- VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
+ // implementation of AccessibilityServicesStateChangeListener
+ @Override
+ public void onAccessibilityServicesStateChanged() {
+ final AccessibilityManager accessibilityManager =
+ (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ updateA11yVolumeAlias(accessibilityManager.isAccessibilityVolumeStreamActive());
+ }
+
+ private void updateA11yVolumeAlias(boolean a11VolEnabled) {
+ if (DEBUG_VOL) Log.d(TAG, "Accessibility volume enabled = " + a11VolEnabled);
+ if (sIndependentA11yVolume != a11VolEnabled) {
+ sIndependentA11yVolume = a11VolEnabled;
+ // update the volume mapping scheme
+ updateStreamVolumeAlias(true /*updateVolumes*/, TAG);
+ // update the volume controller behavior
+ mVolumeController.setA11yMode(sIndependentA11yVolume ?
+ VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+ VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
+ }
}
//==========================================================================================
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/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 66c3d44..af1e007 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12217,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,
@@ -12238,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/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/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7325daab..4b680e5 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -637,6 +637,7 @@
return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
}
+ @Override
TaskWindowContainerController getController() {
return (TaskWindowContainerController) super.getController();
}
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 0ca1887..26e36dc 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -52,7 +52,9 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case REPORT_SNAPSHOT_CHANGED:
- mListener.onSnapshotChanged((TaskSnapshot) msg.obj);
+ if (mListener != null) {
+ mListener.onSnapshotChanged((TaskSnapshot) msg.obj);
+ }
break;
}
}
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1eeac82..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
@@ -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(),
@@ -8925,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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index b0665d5..8da47c8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -66,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;
@@ -2117,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),
@@ -2135,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),
@@ -2146,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);
@@ -3200,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/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/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;
+ }
+}