diff options
122 files changed, 4905 insertions, 2005 deletions
diff --git a/api/current.txt b/api/current.txt index 876c06ca6bf7..4c882315c3e0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5162,6 +5162,7 @@ package android.app { 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_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; field public static final java.lang.String EXTRA_COLORIZED = "android.colorized"; @@ -5577,8 +5578,11 @@ package android.app { method public android.app.NotificationChannelGroup clone(); method public int describeContents(); method public java.util.List<android.app.NotificationChannel> getChannels(); + method public java.lang.String getDescription(); method public java.lang.String getId(); method public java.lang.CharSequence getName(); + method public boolean isBlocked(); + method public void setDescription(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR; } @@ -6533,6 +6537,7 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 + field public static final int START_USER_IN_BACKGROUND = 8; // 0x8 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 } @@ -30679,6 +30684,7 @@ package android.os { field public static final int N_MR1 = 25; // 0x19 field public static final int O = 26; // 0x1a field public static final int O_MR1 = 27; // 0x1b + field public static final int P = 10000; // 0x2710 } public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { @@ -34890,6 +34896,7 @@ package android.provider { field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS"; field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS"; field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS"; + field public static final java.lang.String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS"; field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS"; field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS"; field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS"; @@ -34949,6 +34956,7 @@ package android.provider { field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE"; field public static final java.lang.String EXTRA_AUTHORITIES = "authorities"; field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled"; + field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled"; field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes"; @@ -37081,13 +37089,14 @@ package android.service.autofill { public final class FillEventHistory implements android.os.Parcelable { method public int describeContents(); - method public android.os.Bundle getClientState(); + method public deprecated android.os.Bundle getClientState(); method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR; } public static final class FillEventHistory.Event { + method public android.os.Bundle getClientState(); method public java.lang.String getDatasetId(); method public int getType(); field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index 271093ea1817..9437dfe575fd 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5347,6 +5347,7 @@ package android.app { 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_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; field public static final java.lang.String EXTRA_COLORIZED = "android.colorized"; @@ -5783,8 +5784,11 @@ package android.app { method public android.app.NotificationChannelGroup clone(); method public int describeContents(); method public java.util.List<android.app.NotificationChannel> getChannels(); + method public java.lang.String getDescription(); method public java.lang.String getId(); method public java.lang.CharSequence getName(); + method public boolean isBlocked(); + method public void setDescription(java.lang.String); method public org.json.JSONObject toJson() throws org.json.JSONException; method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR; @@ -6777,6 +6781,7 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 + field public static final int START_USER_IN_BACKGROUND = 8; // 0x8 field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4 field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2 field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3 @@ -33396,6 +33401,7 @@ package android.os { field public static final int N_MR1 = 25; // 0x19 field public static final int O = 26; // 0x1a field public static final int O_MR1 = 27; // 0x1b + field public static final int P = 10000; // 0x2710 } public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { @@ -37942,6 +37948,7 @@ package android.provider { field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS"; field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS"; field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS"; + field public static final java.lang.String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS"; field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS"; field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS"; field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS"; @@ -38002,6 +38009,7 @@ package android.provider { field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE"; field public static final java.lang.String EXTRA_AUTHORITIES = "authorities"; field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled"; + field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled"; field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes"; @@ -40172,13 +40180,14 @@ package android.service.autofill { public final class FillEventHistory implements android.os.Parcelable { method public int describeContents(); - method public android.os.Bundle getClientState(); + method public deprecated android.os.Bundle getClientState(); method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR; } public static final class FillEventHistory.Event { + method public android.os.Bundle getClientState(); method public java.lang.String getDatasetId(); method public int getType(); field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2 diff --git a/api/test-current.txt b/api/test-current.txt index f16995378081..20a15f801790 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5175,6 +5175,7 @@ package android.app { 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_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; field public static final java.lang.String EXTRA_COLORIZED = "android.colorized"; @@ -5590,8 +5591,12 @@ package android.app { method public android.app.NotificationChannelGroup clone(); method public int describeContents(); method public java.util.List<android.app.NotificationChannel> getChannels(); + method public java.lang.String getDescription(); method public java.lang.String getId(); method public java.lang.CharSequence getName(); + method public boolean isBlocked(); + method public void setBlocked(boolean); + method public void setDescription(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR; } @@ -6564,6 +6569,7 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 + field public static final int START_USER_IN_BACKGROUND = 8; // 0x8 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 } @@ -30823,6 +30829,7 @@ package android.os { field public static final int N_MR1 = 25; // 0x19 field public static final int O = 26; // 0x1a field public static final int O_MR1 = 27; // 0x1b + field public static final int P = 10000; // 0x2710 } public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { @@ -35067,6 +35074,7 @@ package android.provider { field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS"; field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS"; field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS"; + field public static final java.lang.String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS"; field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS"; field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS"; field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS"; @@ -35127,6 +35135,7 @@ package android.provider { field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE"; field public static final java.lang.String EXTRA_AUTHORITIES = "authorities"; field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled"; + field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID"; field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled"; field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes"; @@ -37279,13 +37288,14 @@ package android.service.autofill { public final class FillEventHistory implements android.os.Parcelable { method public int describeContents(); - method public android.os.Bundle getClientState(); + method public deprecated android.os.Bundle getClientState(); method public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR; } public static final class FillEventHistory.Event { + method public android.os.Bundle getClientState(); method public java.lang.String getDatasetId(); method public int getType(); field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2 diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 337219fd707e..e76e1903a529 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -16,6 +16,10 @@ package android.app; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_DOCKED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -874,6 +878,26 @@ public class ActivityManager { } return windowingMode; } + + /** Returns the activity type that should be used for this input stack id. */ + // TODO: To be removed once we are not using stack id for stuff... + public static int getActivityTypeForStackId(int stackId) { + final int activityType; + switch (stackId) { + case HOME_STACK_ID: + activityType = ACTIVITY_TYPE_HOME; + break; + case RECENTS_STACK_ID: + activityType = ACTIVITY_TYPE_RECENTS; + break; + case ASSISTANT_STACK_ID: + activityType = ACTIVITY_TYPE_ASSISTANT; + break; + default : + activityType = ACTIVITY_TYPE_STANDARD; + } + return activityType; + } } /** diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 08821bebd57e..d4752a771492 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -61,6 +61,8 @@ interface INotificationManager void createNotificationChannelsForPackage(String pkg, int uid, in ParceledListSlice channelsList); ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted); NotificationChannelGroup getNotificationChannelGroupForPackage(String groupId, String pkg, int uid); + NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(String pkg, int uid, String groupId, boolean includeDeleted); + void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group); void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel); NotificationChannel getNotificationChannel(String pkg, String channelId); NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted); @@ -103,6 +105,7 @@ interface INotificationManager void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim); void setInterruptionFilter(String pkg, int interruptionFilter); + void updateNotificationChannelGroupFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannelGroup group); void updateNotificationChannelFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannel channel); ParceledListSlice getNotificationChannelsFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user); ParceledListSlice getNotificationChannelGroupsFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 9511f3fd7cef..841b9611fa20 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -124,6 +124,13 @@ public class Notification implements Parcelable /** * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will + * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down + * what settings should be shown in the target app. + */ + public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; + + /** + * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)} * that can be used to narrow down what settings should be shown in the target app. */ diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index d6e36914ac6c..163a8dcae2f4 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -830,24 +830,24 @@ public final class NotificationChannel implements Parcelable { @Override public String toString() { - return "NotificationChannel{" + - "mId='" + mId + '\'' + - ", mName=" + mName + - ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") + - ", mImportance=" + mImportance + - ", mBypassDnd=" + mBypassDnd + - ", mLockscreenVisibility=" + mLockscreenVisibility + - ", mSound=" + mSound + - ", mLights=" + mLights + - ", mLightColor=" + mLightColor + - ", mVibration=" + Arrays.toString(mVibration) + - ", mUserLockedFields=" + mUserLockedFields + - ", mVibrationEnabled=" + mVibrationEnabled + - ", mShowBadge=" + mShowBadge + - ", mDeleted=" + mDeleted + - ", mGroup='" + mGroup + '\'' + - ", mAudioAttributes=" + mAudioAttributes + - ", mBlockableSystem=" + mBlockableSystem + - '}'; + return "NotificationChannel{" + + "mId='" + mId + '\'' + + ", mName=" + mName + + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") + + ", mImportance=" + mImportance + + ", mBypassDnd=" + mBypassDnd + + ", mLockscreenVisibility=" + mLockscreenVisibility + + ", mSound=" + mSound + + ", mLights=" + mLights + + ", mLightColor=" + mLightColor + + ", mVibration=" + Arrays.toString(mVibration) + + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields) + + ", mVibrationEnabled=" + mVibrationEnabled + + ", mShowBadge=" + mShowBadge + + ", mDeleted=" + mDeleted + + ", mGroup='" + mGroup + '\'' + + ", mAudioAttributes=" + mAudioAttributes + + ", mBlockableSystem=" + mBlockableSystem + + '}'; } } diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index 18ad9cf3d8e3..51733114f8b9 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -16,6 +16,7 @@ package android.app; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; @@ -23,6 +24,7 @@ import android.text.TextUtils; import org.json.JSONException; import org.json.JSONObject; +import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; @@ -42,10 +44,14 @@ public final class NotificationChannelGroup implements Parcelable { private static final String TAG_GROUP = "channelGroup"; private static final String ATT_NAME = "name"; + private static final String ATT_DESC = "desc"; private static final String ATT_ID = "id"; + private static final String ATT_BLOCKED = "blocked"; private final String mId; private CharSequence mName; + private String mDescription; + private boolean mBlocked; private List<NotificationChannel> mChannels = new ArrayList<>(); /** @@ -73,7 +79,13 @@ public final class NotificationChannelGroup implements Parcelable { mId = null; } mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + if (in.readByte() != 0) { + mDescription = in.readString(); + } else { + mDescription = null; + } in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader()); + mBlocked = in.readBoolean(); } private String getTrimmedString(String input) { @@ -92,24 +104,38 @@ public final class NotificationChannelGroup implements Parcelable { dest.writeByte((byte) 0); } TextUtils.writeToParcel(mName, dest, flags); + if (mDescription != null) { + dest.writeByte((byte) 1); + dest.writeString(mDescription); + } else { + dest.writeByte((byte) 0); + } dest.writeParcelableList(mChannels, flags); + dest.writeBoolean(mBlocked); } /** - * Returns the id of this channel. + * Returns the id of this group. */ public String getId() { return mId; } /** - * Returns the user visible name of this channel. + * Returns the user visible name of this group. */ public CharSequence getName() { return mName; } /** + * Returns the user visible description of this group. + */ + public String getDescription() { + return mDescription; + } + + /** * Returns the list of channels that belong to this group */ public List<NotificationChannel> getChannels() { @@ -117,6 +143,32 @@ public final class NotificationChannelGroup implements Parcelable { } /** + * Returns whether or not notifications posted to {@link NotificationChannel channels} belonging + * to this group are blocked. + */ + public boolean isBlocked() { + return mBlocked; + } + + /** + * Sets the user visible description of this group. + * + * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too + * long. + */ + public void setDescription(String description) { + mDescription = getTrimmedString(description); + } + + /** + * @hide + */ + @TestApi + public void setBlocked(boolean blocked) { + mBlocked = blocked; + } + + /** * @hide */ public void addChannel(NotificationChannel channel) { @@ -126,6 +178,28 @@ public final class NotificationChannelGroup implements Parcelable { /** * @hide */ + public void setChannels(List<NotificationChannel> channels) { + mChannels = channels; + } + + /** + * @hide + */ + public void populateFromXml(XmlPullParser parser) { + // Name, id, and importance are set in the constructor. + setDescription(parser.getAttributeValue(null, ATT_DESC)); + setBlocked(safeBool(parser, ATT_BLOCKED, false)); + } + + private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) { + final String value = parser.getAttributeValue(null, att); + if (TextUtils.isEmpty(value)) return defValue; + return Boolean.parseBoolean(value); + } + + /** + * @hide + */ public void writeXml(XmlSerializer out) throws IOException { out.startTag(null, TAG_GROUP); @@ -133,6 +207,10 @@ public final class NotificationChannelGroup implements Parcelable { if (getName() != null) { out.attribute(null, ATT_NAME, getName().toString()); } + if (getDescription() != null) { + out.attribute(null, ATT_DESC, getDescription().toString()); + } + out.attribute(null, ATT_BLOCKED, Boolean.toString(isBlocked())); out.endTag(null, TAG_GROUP); } @@ -145,6 +223,8 @@ public final class NotificationChannelGroup implements Parcelable { JSONObject record = new JSONObject(); record.put(ATT_ID, getId()); record.put(ATT_NAME, getName()); + record.put(ATT_DESC, getDescription()); + record.put(ATT_BLOCKED, isBlocked()); return record; } @@ -173,31 +253,46 @@ public final class NotificationChannelGroup implements Parcelable { NotificationChannelGroup that = (NotificationChannelGroup) o; + if (isBlocked() != that.isBlocked()) return false; if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false; if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { return false; } - return true; - } - - @Override - public NotificationChannelGroup clone() { - return new NotificationChannelGroup(getId(), getName()); + if (getDescription() != null ? !getDescription().equals(that.getDescription()) + : that.getDescription() != null) { + return false; + } + return getChannels() != null ? getChannels().equals(that.getChannels()) + : that.getChannels() == null; } @Override public int hashCode() { int result = getId() != null ? getId().hashCode() : 0; result = 31 * result + (getName() != null ? getName().hashCode() : 0); + result = 31 * result + (getDescription() != null ? getDescription().hashCode() : 0); + result = 31 * result + (isBlocked() ? 1 : 0); + result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0); return result; } @Override + public NotificationChannelGroup clone() { + NotificationChannelGroup cloned = new NotificationChannelGroup(getId(), getName()); + cloned.setDescription(getDescription()); + cloned.setBlocked(isBlocked()); + cloned.setChannels(getChannels()); + return cloned; + } + + @Override public String toString() { - return "NotificationChannelGroup{" + - "mId='" + mId + '\'' + - ", mName=" + mName + - ", mChannels=" + mChannels + - '}'; + return "NotificationChannelGroup{" + + "mId='" + mId + '\'' + + ", mName=" + mName + + ", mDescription=" + (!TextUtils.isEmpty(mDescription) ? "hasDescription " : "") + + ", mBlocked=" + mBlocked + + ", mChannels=" + mChannels + + '}'; } } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 34343e9e106a..8fa7d6c3b4f7 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -421,7 +421,7 @@ public class NotificationManager { * Creates a notification channel that notifications can be posted to. * * This can also be used to restore a deleted channel and to update an existing channel's - * name, description, and/or importance. + * name, description, group, and/or importance. * * <p>The name and description should only be changed if the locale changes * or in response to the user renaming this channel. For example, if a user has a channel @@ -431,6 +431,9 @@ public class NotificationManager { * <p>The importance of an existing channel will only be changed if the new importance is lower * than the current value and the user has not altered any settings on this channel. * + * <p>The group an existing channel will only be changed if the channel does not already + * belong to a group. + * * All other fields are ignored for channels that already exist. * * @param channel the channel to create. Note that the created channel may differ from this diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 595ecd201e57..fb11272d7e62 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -964,7 +964,7 @@ public class ResourcesManager { // TODO(adamlesinski): Make this accept more than just overlay directories. final void applyNewResourceDirsLocked(@NonNull final String baseCodePath, - @NonNull final String[] newResourceDirs) { + @Nullable final String[] newResourceDirs) { try { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#applyNewResourceDirsLocked"); diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 11922ec804aa..187237d240f3 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -55,7 +55,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu /** Can be freely resized within its parent container. */ public static final int WINDOWING_MODE_FREEFORM = 4; - @IntDef(value = { + @IntDef({ WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_PINNED, @@ -64,15 +64,41 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu }) public @interface WindowingMode {} + /** The current activity type of the configuration. */ + private @ActivityType int mActivityType; + + /** Activity type is currently not defined. */ + public static final int ACTIVITY_TYPE_UNDEFINED = 0; + /** Standard activity type. Nothing special about the activity... */ + public static final int ACTIVITY_TYPE_STANDARD = 1; + /** Home/Launcher activity type. */ + public static final int ACTIVITY_TYPE_HOME = 2; + /** Recents/Overview activity type. */ + public static final int ACTIVITY_TYPE_RECENTS = 3; + /** Assistant activity type. */ + public static final int ACTIVITY_TYPE_ASSISTANT = 4; + + @IntDef({ + ACTIVITY_TYPE_UNDEFINED, + ACTIVITY_TYPE_STANDARD, + ACTIVITY_TYPE_HOME, + ACTIVITY_TYPE_RECENTS, + ACTIVITY_TYPE_ASSISTANT, + }) + public @interface ActivityType {} + /** Bit that indicates that the {@link #mAppBounds} changed. */ public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 0; /** Bit that indicates that the {@link #mWindowingMode} changed. */ public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 1; + /** Bit that indicates that the {@link #mActivityType} changed. */ + public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 2; @IntDef(flag = true, value = { WINDOW_CONFIG_APP_BOUNDS, WINDOW_CONFIG_WINDOWING_MODE, + WINDOW_CONFIG_ACTIVITY_TYPE, }) public @interface WindowConfig {} @@ -92,11 +118,13 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(mAppBounds, flags); dest.writeInt(mWindowingMode); + dest.writeInt(mActivityType); } private void readFromParcel(Parcel source) { mAppBounds = source.readParcelable(Rect.class.getClassLoader()); mWindowingMode = source.readInt(); + mActivityType = source.readInt(); } @Override @@ -158,9 +186,27 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu return mWindowingMode; } + public void setActivityType(@ActivityType int activityType) { + if (mActivityType == activityType) { + return; + } + if (mActivityType != ACTIVITY_TYPE_UNDEFINED + && activityType != ACTIVITY_TYPE_UNDEFINED) { + throw new IllegalStateException("Can't change activity type once set: " + this + + " activityType=" + activityTypeToString(activityType)); + } + mActivityType = activityType; + } + + @ActivityType + public int getActivityType() { + return mActivityType; + } + public void setTo(WindowConfiguration other) { setAppBounds(other.mAppBounds); setWindowingMode(other.mWindowingMode); + setActivityType(other.mActivityType); } /** Set this object to completely undefined. */ @@ -171,6 +217,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu public void setToDefaults() { setAppBounds(null); setWindowingMode(WINDOWING_MODE_UNDEFINED); + setActivityType(ACTIVITY_TYPE_UNDEFINED); } /** @@ -191,6 +238,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu changed |= WINDOW_CONFIG_WINDOWING_MODE; setWindowingMode(delta.mWindowingMode); } + if (delta.mActivityType != ACTIVITY_TYPE_UNDEFINED + && mActivityType != delta.mActivityType) { + changed |= WINDOW_CONFIG_ACTIVITY_TYPE; + setActivityType(delta.mActivityType); + } return changed; } @@ -219,6 +271,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu changes |= WINDOW_CONFIG_WINDOWING_MODE; } + if ((compareUndefined || other.mActivityType != ACTIVITY_TYPE_UNDEFINED) + && mActivityType != other.mActivityType) { + changes |= WINDOW_CONFIG_ACTIVITY_TYPE; + } + return changes; } @@ -241,6 +298,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } n = mWindowingMode - that.mWindowingMode; if (n != 0) return n; + n = mActivityType - that.mActivityType; + if (n != 0) return n; // if (n != 0) return n; return n; @@ -263,13 +322,15 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu result = 31 * result + mAppBounds.hashCode(); } result = 31 * result + mWindowingMode; + result = 31 * result + mActivityType; return result; } @Override public String toString() { return "{mAppBounds=" + mAppBounds - + " mWindowingMode=" + windowingModeToString(mWindowingMode) + "}"; + + " mWindowingMode=" + windowingModeToString(mWindowingMode) + + " mActivityType=" + activityTypeToString(mActivityType) + "}"; } /** @@ -366,4 +427,15 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } return String.valueOf(windowingMode); } + + public static String activityTypeToString(@ActivityType int applicationType) { + switch (applicationType) { + case ACTIVITY_TYPE_UNDEFINED: return "undefined"; + case ACTIVITY_TYPE_STANDARD: return "standard"; + case ACTIVITY_TYPE_HOME: return "home"; + case ACTIVITY_TYPE_RECENTS: return "recents"; + case ACTIVITY_TYPE_ASSISTANT: return "assistant"; + } + return String.valueOf(applicationType); + } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index dd2024178c94..833af1b538d0 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -5983,6 +5983,26 @@ public class DevicePolicyManager { public static final int MAKE_USER_DEMO = 0x0004; /** + * Flag used by {@link #createAndManageUser} to specificy that the newly created user should be + * started in the background as part of the user creation. + */ + // TODO: Investigate solutions for the case where reboot happens before setup is completed. + public static final int START_USER_IN_BACKGROUND = 0x0008; + + /** + * @hide + */ + @IntDef( + flag = true, + prefix = {"SKIP_", "MAKE_USER_", "START_"}, + value = {SKIP_SETUP_WIZARD, MAKE_USER_EPHEMERAL, MAKE_USER_DEMO, + START_USER_IN_BACKGROUND} + ) + @Retention(RetentionPolicy.SOURCE) + public @interface CreateAndManageUserFlags {} + + + /** * Called by a device owner to create a user with the specified name and a given component of * the calling package as profile owner. The UserHandle returned by this method should not be * persisted as user handles are recycled as users are removed and created. If you need to @@ -6013,7 +6033,7 @@ public class DevicePolicyManager { public @Nullable UserHandle createAndManageUser(@NonNull ComponentName admin, @NonNull String name, @NonNull ComponentName profileOwner, @Nullable PersistableBundle adminExtras, - int flags) { + @CreateAndManageUserFlags int flags) { throwIfParentInstance("createAndManageUser"); try { return mService.createAndManageUser(admin, name, profileOwner, adminExtras, flags); @@ -6772,6 +6792,10 @@ public class DevicePolicyManager { * password, pin or pattern is set after the keyguard was disabled, the keyguard stops being * disabled. * + * <p> + * As of {@link android.os.Build.VERSION_CODES#P}, this call also dismisses the + * keyguard if it is currently shown. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled {@code true} disables the keyguard, {@code false} reenables it. * @return {@code false} if attempting to disable the keyguard while a lock password was in diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 1242cb0fbdfa..8a1eae2da976 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -40,7 +40,6 @@ import android.util.SparseArray; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Adapter; import android.widget.AdapterView; @@ -469,7 +468,9 @@ public class AppWidgetHostView extends FrameLayout { // We've already done this -- nothing to do. return ; } - Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception); + if (exception != null) { + Log.w(TAG, "Error inflating RemoteViews : " + exception.toString()); + } content = getErrorView(); mViewMode = VIEW_MODE_ERROR; } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 062799891ffd..935f5f3b78a7 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -774,6 +774,11 @@ public class Build { * O MR1. */ public static final int O_MR1 = 27; + + /** + * P. + */ + public static final int P = CUR_DEVELOPMENT; // STOPSHIP Replace with the real version. } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 3e071437b097..4703af06c06e 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -53,31 +53,25 @@ import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; /** - * <p>StrictMode is a developer tool which detects things you might be - * doing by accident and brings them to your attention so you can fix - * them. + * StrictMode is a developer tool which detects things you might be doing by accident and brings + * them to your attention so you can fix them. * - * <p>StrictMode is most commonly used to catch accidental disk or - * network access on the application's main thread, where UI - * operations are received and animations take place. Keeping disk - * and network operations off the main thread makes for much smoother, - * more responsive applications. By keeping your application's main thread - * responsive, you also prevent - * <a href="{@docRoot}guide/practices/design/responsiveness.html">ANR dialogs</a> - * from being shown to users. + * <p>StrictMode is most commonly used to catch accidental disk or network access on the + * application's main thread, where UI operations are received and animations take place. Keeping + * disk and network operations off the main thread makes for much smoother, more responsive + * applications. By keeping your application's main thread responsive, you also prevent <a + * href="{@docRoot}guide/practices/design/responsiveness.html">ANR dialogs</a> from being shown to + * users. * - * <p class="note">Note that even though an Android device's disk is - * often on flash memory, many devices run a filesystem on top of that - * memory with very limited concurrency. It's often the case that - * almost all disk accesses are fast, but may in individual cases be - * dramatically slower when certain I/O is happening in the background - * from other processes. If possible, it's best to assume that such - * things are not fast.</p> + * <p class="note">Note that even though an Android device's disk is often on flash memory, many + * devices run a filesystem on top of that memory with very limited concurrency. It's often the case + * that almost all disk accesses are fast, but may in individual cases be dramatically slower when + * certain I/O is happening in the background from other processes. If possible, it's best to assume + * that such things are not fast. * - * <p>Example code to enable from early in your - * {@link android.app.Application}, {@link android.app.Activity}, or - * other application component's - * {@link android.app.Application#onCreate} method: + * <p>Example code to enable from early in your {@link android.app.Application}, {@link + * android.app.Activity}, or other application component's {@link android.app.Application#onCreate} + * method: * * <pre> * public void onCreate() { @@ -99,36 +93,32 @@ import java.util.concurrent.atomic.AtomicInteger; * } * </pre> * - * <p>You can decide what should happen when a violation is detected. - * For example, using {@link ThreadPolicy.Builder#penaltyLog} you can - * watch the output of <code>adb logcat</code> while you use your - * application to see the violations as they happen. + * <p>You can decide what should happen when a violation is detected. For example, using {@link + * ThreadPolicy.Builder#penaltyLog} you can watch the output of <code>adb logcat</code> while you + * use your application to see the violations as they happen. * - * <p>If you find violations that you feel are problematic, there are - * a variety of tools to help solve them: threads, {@link android.os.Handler}, - * {@link android.os.AsyncTask}, {@link android.app.IntentService}, etc. - * But don't feel compelled to fix everything that StrictMode finds. In particular, - * many cases of disk access are often necessary during the normal activity lifecycle. Use - * StrictMode to find things you did by accident. Network requests on the UI thread + * <p>If you find violations that you feel are problematic, there are a variety of tools to help + * solve them: threads, {@link android.os.Handler}, {@link android.os.AsyncTask}, {@link + * android.app.IntentService}, etc. But don't feel compelled to fix everything that StrictMode + * finds. In particular, many cases of disk access are often necessary during the normal activity + * lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread * are almost always a problem, though. * - * <p class="note">StrictMode is not a security mechanism and is not - * guaranteed to find all disk or network accesses. While it does - * propagate its state across process boundaries when doing - * {@link android.os.Binder} calls, it's still ultimately a best - * effort mechanism. Notably, disk or network access from JNI calls - * won't necessarily trigger it. Future versions of Android may catch - * more (or fewer) operations, so you should never leave StrictMode - * enabled in applications distributed on Google Play. + * <p class="note">StrictMode is not a security mechanism and is not guaranteed to find all disk or + * network accesses. While it does propagate its state across process boundaries when doing {@link + * android.os.Binder} calls, it's still ultimately a best effort mechanism. Notably, disk or network + * access from JNI calls won't necessarily trigger it. Future versions of Android may catch more (or + * fewer) operations, so you should never leave StrictMode enabled in applications distributed on + * Google Play. */ public final class StrictMode { private static final String TAG = "StrictMode"; private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE); /** - * Boolean system property to disable strict mode checks outright. - * Set this to 'true' to force disable; 'false' has no effect on other - * enable/disable policy. + * Boolean system property to disable strict mode checks outright. Set this to 'true' to force + * disable; 'false' has no effect on other enable/disable policy. + * * @hide */ public static final String DISABLE_PROPERTY = "persist.sys.strictmode.disable"; @@ -141,9 +131,9 @@ public final class StrictMode { public static final String VISUAL_PROPERTY = "persist.sys.strictmode.visual"; /** - * Temporary property used to include {@link #DETECT_VM_CLEARTEXT_NETWORK} - * in {@link VmPolicy.Builder#detectAll()}. Apps can still always opt-into - * detection using {@link VmPolicy.Builder#detectCleartextNetwork()}. + * Temporary property used to include {@link #DETECT_VM_CLEARTEXT_NETWORK} in {@link + * VmPolicy.Builder#detectAll()}. Apps can still always opt-into detection using {@link + * VmPolicy.Builder#detectCleartextNetwork()}. */ private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear"; @@ -162,105 +152,96 @@ public final class StrictMode { // Byte 1: Thread-policy - /** - * @hide - */ - public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy + /** @hide */ + public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy - /** - * @hide - */ - public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy + /** @hide */ + public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy - /** - * @hide - */ - public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy + /** @hide */ + public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy /** * For StrictMode.noteSlowCall() * * @hide */ - public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy + public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy /** * For StrictMode.noteResourceMismatch() * * @hide */ - public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy + public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy - /** - * @hide - */ - public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy + /** @hide */ + public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy private static final int ALL_THREAD_DETECT_BITS = - DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM | - DETECT_RESOURCE_MISMATCH | DETECT_UNBUFFERED_IO; + DETECT_DISK_WRITE + | DETECT_DISK_READ + | DETECT_NETWORK + | DETECT_CUSTOM + | DETECT_RESOURCE_MISMATCH + | DETECT_UNBUFFERED_IO; // Byte 2: Process-policy /** * Note, a "VM_" bit, not thread. + * * @hide */ - public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy + public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy /** * Note, a "VM_" bit, not thread. + * * @hide */ - public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy + public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy /** * Note, a "VM_" bit, not thread. + * * @hide */ - public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy + public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy - /** - * @hide - */ - private static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy + /** @hide */ + private static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy - /** - * @hide - */ - public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy + /** @hide */ + public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy - /** - * @hide - */ - private static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy + /** @hide */ + private static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy - /** - * @hide - */ - private static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy + /** @hide */ + private static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy - /** - * @hide - */ - private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy + /** @hide */ + private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy - /** - * @hide - */ - private static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy + /** @hide */ + private static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy private static final int ALL_VM_DETECT_BITS = - DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS | - DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_INSTANCE_LEAKS | - DETECT_VM_REGISTRATION_LEAKS | DETECT_VM_FILE_URI_EXPOSURE | - DETECT_VM_CLEARTEXT_NETWORK | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION | - DETECT_VM_UNTAGGED_SOCKET; + DETECT_VM_CURSOR_LEAKS + | DETECT_VM_CLOSABLE_LEAKS + | DETECT_VM_ACTIVITY_LEAKS + | DETECT_VM_INSTANCE_LEAKS + | DETECT_VM_REGISTRATION_LEAKS + | DETECT_VM_FILE_URI_EXPOSURE + | DETECT_VM_CLEARTEXT_NETWORK + | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION + | DETECT_VM_UNTAGGED_SOCKET; // Byte 3: Penalty /** {@hide} */ - public static final int PENALTY_LOG = 0x01 << 16; // normal android.util.Log + public static final int PENALTY_LOG = 0x01 << 16; // normal android.util.Log /** {@hide} */ public static final int PENALTY_DIALOG = 0x02 << 16; /** {@hide} */ @@ -271,13 +252,11 @@ public final class StrictMode { public static final int PENALTY_DROPBOX = 0x20 << 16; /** - * Non-public penalty mode which overrides all the other penalty - * bits and signals that we're in a Binder call and we should - * ignore the other penalty bits and instead serialize back all - * our offending stack traces to the caller to ultimately handle - * in the originating process. + * Non-public penalty mode which overrides all the other penalty bits and signals that we're in + * a Binder call and we should ignore the other penalty bits and instead serialize back all our + * offending stack traces to the caller to ultimately handle in the originating process. * - * This must be kept in sync with the constant in libs/binder/Parcel.cpp + * <p>This must be kept in sync with the constant in libs/binder/Parcel.cpp * * @hide */ @@ -308,18 +287,23 @@ public final class StrictMode { // CAUTION: we started stealing the top bits of Byte 4 for VM above - /** - * Mask of all the penalty bits valid for thread policies. - */ + /** Mask of all the penalty bits valid for thread policies. */ private static final int THREAD_PENALTY_MASK = - PENALTY_LOG | PENALTY_DIALOG | PENALTY_DEATH | PENALTY_DROPBOX | PENALTY_GATHER | - PENALTY_DEATH_ON_NETWORK | PENALTY_FLASH; - - /** - * Mask of all the penalty bits valid for VM policies. - */ - private static final int VM_PENALTY_MASK = PENALTY_LOG | PENALTY_DEATH | PENALTY_DROPBOX - | PENALTY_DEATH_ON_CLEARTEXT_NETWORK | PENALTY_DEATH_ON_FILE_URI_EXPOSURE; + PENALTY_LOG + | PENALTY_DIALOG + | PENALTY_DEATH + | PENALTY_DROPBOX + | PENALTY_GATHER + | PENALTY_DEATH_ON_NETWORK + | PENALTY_FLASH; + + /** Mask of all the penalty bits valid for VM policies. */ + private static final int VM_PENALTY_MASK = + PENALTY_LOG + | PENALTY_DEATH + | PENALTY_DROPBOX + | PENALTY_DEATH_ON_CLEARTEXT_NETWORK + | PENALTY_DEATH_ON_FILE_URI_EXPOSURE; /** {@hide} */ public static final int NETWORK_POLICY_ACCEPT = 0; @@ -330,14 +314,16 @@ public final class StrictMode { // TODO: wrap in some ImmutableHashMap thing. // Note: must be before static initialization of sVmPolicy. - private static final HashMap<Class, Integer> EMPTY_CLASS_LIMIT_MAP = new HashMap<Class, Integer>(); + private static final HashMap<Class, Integer> EMPTY_CLASS_LIMIT_MAP = + new HashMap<Class, Integer>(); /** * The current VmPolicy in effect. * - * TODO: these are redundant (mask is in VmPolicy). Should remove sVmPolicyMask. + * <p>TODO: these are redundant (mask is in VmPolicy). Should remove sVmPolicyMask. */ private static volatile int sVmPolicyMask = 0; + private static volatile VmPolicy sVmPolicy = VmPolicy.LAX; /** {@hide} */ @@ -355,8 +341,8 @@ public final class StrictMode { } /** - * The number of threads trying to do an async dropbox write. - * Just to limit ourselves out of paranoia. + * The number of threads trying to do an async dropbox write. Just to limit ourselves out of + * paranoia. */ private static final AtomicInteger sDropboxCallsInFlight = new AtomicInteger(0); @@ -365,18 +351,15 @@ public final class StrictMode { /** * {@link StrictMode} policy applied to a certain thread. * - * <p>The policy is enabled by {@link #setThreadPolicy}. The current policy - * can be retrieved with {@link #getThreadPolicy}. + * <p>The policy is enabled by {@link #setThreadPolicy}. The current policy can be retrieved + * with {@link #getThreadPolicy}. * - * <p>Note that multiple penalties may be provided and they're run - * in order from least to most severe (logging before process - * death, for example). There's currently no mechanism to choose + * <p>Note that multiple penalties may be provided and they're run in order from least to most + * severe (logging before process death, for example). There's currently no mechanism to choose * different penalties for different detected actions. */ public static final class ThreadPolicy { - /** - * The default, lax policy which doesn't catch anything. - */ + /** The default, lax policy which doesn't catch anything. */ public static final ThreadPolicy LAX = new ThreadPolicy(0); final int mask; @@ -391,16 +374,15 @@ public final class StrictMode { } /** - * Creates {@link ThreadPolicy} instances. Methods whose names start - * with {@code detect} specify what problems we should look - * for. Methods whose names start with {@code penalty} specify what - * we should do when we detect a problem. + * Creates {@link ThreadPolicy} instances. Methods whose names start with {@code detect} + * specify what problems we should look for. Methods whose names start with {@code penalty} + * specify what we should do when we detect a problem. * - * <p>You can call as many {@code detect} and {@code penalty} - * methods as you like. Currently order is insignificant: all - * penalties apply to all detected problems. + * <p>You can call as many {@code detect} and {@code penalty} methods as you like. Currently + * order is insignificant: all penalties apply to all detected problems. * * <p>For example, detect everything and log anything that's found: + * * <pre> * StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder() * .detectAll() @@ -413,18 +395,15 @@ public final class StrictMode { private int mMask = 0; /** - * Create a Builder that detects nothing and has no - * violations. (but note that {@link #build} will default - * to enabling {@link #penaltyLog} if no other penalties - * are specified) + * Create a Builder that detects nothing and has no violations. (but note that {@link + * #build} will default to enabling {@link #penaltyLog} if no other penalties are + * specified) */ public Builder() { mMask = 0; } - /** - * Initialize a Builder from an existing ThreadPolicy. - */ + /** Initialize a Builder from an existing ThreadPolicy. */ public Builder(ThreadPolicy policy) { mMask = policy.mask; } @@ -432,8 +411,8 @@ public final class StrictMode { /** * Detect everything that's potentially suspect. * - * <p>As of the Gingerbread release this includes network and - * disk operations but will likely expand in future releases. + * <p>As of the Gingerbread release this includes network and disk operations but will + * likely expand in future releases. */ public Builder detectAll() { detectDiskReads(); @@ -453,135 +432,106 @@ public final class StrictMode { return this; } - /** - * Disable the detection of everything. - */ + /** Disable the detection of everything. */ public Builder permitAll() { return disable(ALL_THREAD_DETECT_BITS); } - /** - * Enable detection of network operations. - */ + /** Enable detection of network operations. */ public Builder detectNetwork() { return enable(DETECT_NETWORK); } - /** - * Disable detection of network operations. - */ + /** Disable detection of network operations. */ public Builder permitNetwork() { return disable(DETECT_NETWORK); } - /** - * Enable detection of disk reads. - */ + /** Enable detection of disk reads. */ public Builder detectDiskReads() { return enable(DETECT_DISK_READ); } - /** - * Disable detection of disk reads. - */ + /** Disable detection of disk reads. */ public Builder permitDiskReads() { return disable(DETECT_DISK_READ); } - /** - * Enable detection of slow calls. - */ + /** Enable detection of slow calls. */ public Builder detectCustomSlowCalls() { return enable(DETECT_CUSTOM); } - /** - * Disable detection of slow calls. - */ + /** Disable detection of slow calls. */ public Builder permitCustomSlowCalls() { return disable(DETECT_CUSTOM); } - /** - * Disable detection of mismatches between defined resource types - * and getter calls. - */ + /** Disable detection of mismatches between defined resource types and getter calls. */ public Builder permitResourceMismatches() { return disable(DETECT_RESOURCE_MISMATCH); } - /** - * Detect unbuffered input/output operations. - */ + /** Detect unbuffered input/output operations. */ public Builder detectUnbufferedIo() { return enable(DETECT_UNBUFFERED_IO); } - /** - * Disable detection of unbuffered input/output operations. - */ + /** Disable detection of unbuffered input/output operations. */ public Builder permitUnbufferedIo() { return disable(DETECT_UNBUFFERED_IO); } /** - * Enables detection of mismatches between defined resource types - * and getter calls. - * <p> - * This helps detect accidental type mismatches and potentially - * expensive type conversions when obtaining typed resources. - * <p> - * For example, a strict mode violation would be thrown when - * calling {@link android.content.res.TypedArray#getInt(int, int)} - * on an index that contains a String-type resource. If the string - * value can be parsed as an integer, this method call will return - * a value without crashing; however, the developer should format - * the resource as an integer to avoid unnecessary type conversion. + * Enables detection of mismatches between defined resource types and getter calls. + * + * <p>This helps detect accidental type mismatches and potentially expensive type + * conversions when obtaining typed resources. + * + * <p>For example, a strict mode violation would be thrown when calling {@link + * android.content.res.TypedArray#getInt(int, int)} on an index that contains a + * String-type resource. If the string value can be parsed as an integer, this method + * call will return a value without crashing; however, the developer should format the + * resource as an integer to avoid unnecessary type conversion. */ public Builder detectResourceMismatches() { return enable(DETECT_RESOURCE_MISMATCH); } - /** - * Enable detection of disk writes. - */ + /** Enable detection of disk writes. */ public Builder detectDiskWrites() { return enable(DETECT_DISK_WRITE); } - /** - * Disable detection of disk writes. - */ + /** Disable detection of disk writes. */ public Builder permitDiskWrites() { return disable(DETECT_DISK_WRITE); } /** - * Show an annoying dialog to the developer on detected - * violations, rate-limited to be only a little annoying. + * Show an annoying dialog to the developer on detected violations, rate-limited to be + * only a little annoying. */ public Builder penaltyDialog() { return enable(PENALTY_DIALOG); } /** - * Crash the whole process on violation. This penalty runs at - * the end of all enabled penalties so you'll still get - * see logging or other violations before the process dies. + * Crash the whole process on violation. This penalty runs at the end of all enabled + * penalties so you'll still get see logging or other violations before the process + * dies. * - * <p>Unlike {@link #penaltyDeathOnNetwork}, this applies - * to disk reads, disk writes, and network usage if their - * corresponding detect flags are set. + * <p>Unlike {@link #penaltyDeathOnNetwork}, this applies to disk reads, disk writes, + * and network usage if their corresponding detect flags are set. */ public Builder penaltyDeath() { return enable(PENALTY_DEATH); } /** - * Crash the whole process on any network usage. Unlike - * {@link #penaltyDeath}, this penalty runs - * <em>before</em> anything else. You must still have - * called {@link #detectNetwork} to enable this. + * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this + * penalty runs <em>before</em> anything else. You must still have called {@link + * #detectNetwork} to enable this. * * <p>In the Honeycomb or later SDKs, this is on by default. */ @@ -589,25 +539,20 @@ public final class StrictMode { return enable(PENALTY_DEATH_ON_NETWORK); } - /** - * Flash the screen during a violation. - */ + /** Flash the screen during a violation. */ public Builder penaltyFlashScreen() { return enable(PENALTY_FLASH); } - /** - * Log detected violations to the system log. - */ + /** Log detected violations to the system log. */ public Builder penaltyLog() { return enable(PENALTY_LOG); } /** - * Enable detected violations log a stacktrace and timing data - * to the {@link android.os.DropBoxManager DropBox} on policy - * violation. Intended mostly for platform integrators doing - * beta user field data collection. + * Enable detected violations log a stacktrace and timing data to the {@link + * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform + * integrators doing beta user field data collection. */ public Builder penaltyDropBox() { return enable(PENALTY_DROPBOX); @@ -626,16 +571,19 @@ public final class StrictMode { /** * Construct the ThreadPolicy instance. * - * <p>Note: if no penalties are enabled before calling - * <code>build</code>, {@link #penaltyLog} is implicitly - * set. + * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link + * #penaltyLog} is implicitly set. */ public ThreadPolicy build() { // If there are detection bits set but no violation bits // set, enable simple logging. - if (mMask != 0 && - (mMask & (PENALTY_DEATH | PENALTY_LOG | - PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) { + if (mMask != 0 + && (mMask + & (PENALTY_DEATH + | PENALTY_LOG + | PENALTY_DROPBOX + | PENALTY_DIALOG)) + == 0) { penaltyLog(); } return new ThreadPolicy(mMask); @@ -649,9 +597,7 @@ public final class StrictMode { * <p>The policy is enabled by {@link #setVmPolicy}. */ public static final class VmPolicy { - /** - * The default, lax policy which doesn't catch anything. - */ + /** The default, lax policy which doesn't catch anything. */ public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP); final int mask; @@ -673,16 +619,15 @@ public final class StrictMode { } /** - * Creates {@link VmPolicy} instances. Methods whose names start - * with {@code detect} specify what problems we should look - * for. Methods whose names start with {@code penalty} specify what - * we should do when we detect a problem. + * Creates {@link VmPolicy} instances. Methods whose names start with {@code detect} specify + * what problems we should look for. Methods whose names start with {@code penalty} specify + * what we should do when we detect a problem. * - * <p>You can call as many {@code detect} and {@code penalty} - * methods as you like. Currently order is insignificant: all - * penalties apply to all detected problems. + * <p>You can call as many {@code detect} and {@code penalty} methods as you like. Currently + * order is insignificant: all penalties apply to all detected problems. * * <p>For example, detect everything and log anything that's found: + * * <pre> * StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder() * .detectAll() @@ -694,16 +639,14 @@ public final class StrictMode { public static final class Builder { private int mMask; - private HashMap<Class, Integer> mClassInstanceLimit; // null until needed - private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write + private HashMap<Class, Integer> mClassInstanceLimit; // null until needed + private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write public Builder() { mMask = 0; } - /** - * Build upon an existing VmPolicy. - */ + /** Build upon an existing VmPolicy. */ public Builder(VmPolicy base) { mMask = base.mask; mClassInstanceLimitNeedCow = true; @@ -711,16 +654,16 @@ public final class StrictMode { } /** - * Set an upper bound on how many instances of a class can be in memory - * at once. Helps to prevent object leaks. + * Set an upper bound on how many instances of a class can be in memory at once. Helps + * to prevent object leaks. */ public Builder setClassInstanceLimit(Class klass, int instanceLimit) { if (klass == null) { throw new NullPointerException("klass == null"); } if (mClassInstanceLimitNeedCow) { - if (mClassInstanceLimit.containsKey(klass) && - mClassInstanceLimit.get(klass) == instanceLimit) { + if (mClassInstanceLimit.containsKey(klass) + && mClassInstanceLimit.get(klass) == instanceLimit) { // no-op; don't break COW return this; } @@ -734,9 +677,7 @@ public final class StrictMode { return this; } - /** - * Detect leaks of {@link android.app.Activity} subclasses. - */ + /** Detect leaks of {@link android.app.Activity} subclasses. */ public Builder detectActivityLeaks() { return enable(DETECT_VM_ACTIVITY_LEAKS); } @@ -744,9 +685,8 @@ public final class StrictMode { /** * Detect everything that's potentially suspect. * - * <p>In the Honeycomb release this includes leaks of - * SQLite cursors, Activities, and other closable objects - * but will likely expand in future releases. + * <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and + * other closable objects but will likely expand in future releases. */ public Builder detectAll() { detectLeakedSqlLiteObjects(); @@ -777,53 +717,46 @@ public final class StrictMode { } /** - * Detect when an - * {@link android.database.sqlite.SQLiteCursor} or other - * SQLite object is finalized without having been closed. + * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is + * finalized without having been closed. * - * <p>You always want to explicitly close your SQLite - * cursors to avoid unnecessary database contention and - * temporary memory leaks. + * <p>You always want to explicitly close your SQLite cursors to avoid unnecessary + * database contention and temporary memory leaks. */ public Builder detectLeakedSqlLiteObjects() { return enable(DETECT_VM_CURSOR_LEAKS); } /** - * Detect when an {@link java.io.Closeable} or other - * object with a explict termination method is finalized - * without having been closed. + * Detect when an {@link java.io.Closeable} or other object with a explict termination + * method is finalized without having been closed. * - * <p>You always want to explicitly close such objects to - * avoid unnecessary resources leaks. + * <p>You always want to explicitly close such objects to avoid unnecessary resources + * leaks. */ public Builder detectLeakedClosableObjects() { return enable(DETECT_VM_CLOSABLE_LEAKS); } /** - * Detect when a {@link BroadcastReceiver} or - * {@link ServiceConnection} is leaked during {@link Context} - * teardown. + * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during + * {@link Context} teardown. */ public Builder detectLeakedRegistrationObjects() { return enable(DETECT_VM_REGISTRATION_LEAKS); } /** - * Detect when the calling application exposes a {@code file://} - * {@link android.net.Uri} to another app. - * <p> - * This exposure is discouraged since the receiving app may not have - * access to the shared path. For example, the receiving app may not - * have requested the - * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} runtime - * permission, or the platform may be sharing the - * {@link android.net.Uri} across user profile boundaries. - * <p> - * Instead, apps should use {@code content://} Uris so the platform - * can extend temporary permission for the receiving app to access - * the resource. + * Detect when the calling application exposes a {@code file://} {@link android.net.Uri} + * to another app. + * + * <p>This exposure is discouraged since the receiving app may not have access to the + * shared path. For example, the receiving app may not have requested the {@link + * android.Manifest.permission#READ_EXTERNAL_STORAGE} runtime permission, or the + * platform may be sharing the {@link android.net.Uri} across user profile boundaries. + * + * <p>Instead, apps should use {@code content://} Uris so the platform can extend + * temporary permission for the receiving app to access the resource. * * @see android.support.v4.content.FileProvider * @see Intent#FLAG_GRANT_READ_URI_PERMISSION @@ -833,34 +766,32 @@ public final class StrictMode { } /** - * Detect any network traffic from the calling app which is not - * wrapped in SSL/TLS. This can help you detect places that your app - * is inadvertently sending cleartext data across the network. - * <p> - * Using {@link #penaltyDeath()} or - * {@link #penaltyDeathOnCleartextNetwork()} will block further - * traffic on that socket to prevent accidental data leakage, in - * addition to crashing your process. - * <p> - * Using {@link #penaltyDropBox()} will log the raw contents of the - * packet that triggered the violation. - * <p> - * This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it - * may be subject to false positives, such as when STARTTLS - * protocols or HTTP proxies are used. + * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This + * can help you detect places that your app is inadvertently sending cleartext data + * across the network. + * + * <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will + * block further traffic on that socket to prevent accidental data leakage, in addition + * to crashing your process. + * + * <p>Using {@link #penaltyDropBox()} will log the raw contents of the packet that + * triggered the violation. + * + * <p>This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it may be subject to + * false positives, such as when STARTTLS protocols or HTTP proxies are used. */ public Builder detectCleartextNetwork() { return enable(DETECT_VM_CLEARTEXT_NETWORK); } /** - * Detect when the calling application sends a {@code content://} - * {@link android.net.Uri} to another app without setting - * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} or - * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. - * <p> - * Forgetting to include one or more of these flags when sending an - * intent is typically an app bug. + * Detect when the calling application sends a {@code content://} {@link + * android.net.Uri} to another app without setting {@link + * Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link + * Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. + * + * <p>Forgetting to include one or more of these flags when sending an intent is + * typically an app bug. * * @see Intent#FLAG_GRANT_READ_URI_PERMISSION * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION @@ -870,12 +801,11 @@ public final class StrictMode { } /** - * Detect any sockets in the calling app which have not been tagged - * using {@link TrafficStats}. Tagging sockets can help you - * investigate network usage inside your app, such as a narrowing - * down heavy usage to a specific library or component. - * <p> - * This currently does not detect sockets created in native code. + * Detect any sockets in the calling app which have not been tagged using {@link + * TrafficStats}. Tagging sockets can help you investigate network usage inside your + * app, such as a narrowing down heavy usage to a specific library or component. + * + * <p>This currently does not detect sockets created in native code. * * @see TrafficStats#setThreadStatsTag(int) * @see TrafficStats#tagSocket(java.net.Socket) @@ -886,17 +816,16 @@ public final class StrictMode { } /** - * Crashes the whole process on violation. This penalty runs at the - * end of all enabled penalties so you'll still get your logging or - * other violations before the process dies. + * Crashes the whole process on violation. This penalty runs at the end of all enabled + * penalties so you'll still get your logging or other violations before the process + * dies. */ public Builder penaltyDeath() { return enable(PENALTY_DEATH); } /** - * Crashes the whole process when cleartext network traffic is - * detected. + * Crashes the whole process when cleartext network traffic is detected. * * @see #detectCleartextNetwork() */ @@ -905,8 +834,8 @@ public final class StrictMode { } /** - * Crashes the whole process when a {@code file://} - * {@link android.net.Uri} is exposed beyond this app. + * Crashes the whole process when a {@code file://} {@link android.net.Uri} is exposed + * beyond this app. * * @see #detectFileUriExposure() */ @@ -914,18 +843,15 @@ public final class StrictMode { return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE); } - /** - * Log detected violations to the system log. - */ + /** Log detected violations to the system log. */ public Builder penaltyLog() { return enable(PENALTY_LOG); } /** - * Enable detected violations log a stacktrace and timing data - * to the {@link android.os.DropBoxManager DropBox} on policy - * violation. Intended mostly for platform integrators doing - * beta user field data collection. + * Enable detected violations log a stacktrace and timing data to the {@link + * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform + * integrators doing beta user field data collection. */ public Builder penaltyDropBox() { return enable(PENALTY_DROPBOX); @@ -944,48 +870,51 @@ public final class StrictMode { /** * Construct the VmPolicy instance. * - * <p>Note: if no penalties are enabled before calling - * <code>build</code>, {@link #penaltyLog} is implicitly - * set. + * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link + * #penaltyLog} is implicitly set. */ public VmPolicy build() { // If there are detection bits set but no violation bits // set, enable simple logging. - if (mMask != 0 && - (mMask & (PENALTY_DEATH | PENALTY_LOG | - PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) { + if (mMask != 0 + && (mMask + & (PENALTY_DEATH + | PENALTY_LOG + | PENALTY_DROPBOX + | PENALTY_DIALOG)) + == 0) { penaltyLog(); } - return new VmPolicy(mMask, + return new VmPolicy( + mMask, mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP); } } } /** - * Log of strict mode violation stack traces that have occurred - * during a Binder call, to be serialized back later to the caller - * via Parcel.writeNoException() (amusingly) where the caller can - * choose how to react. + * Log of strict mode violation stack traces that have occurred during a Binder call, to be + * serialized back later to the caller via Parcel.writeNoException() (amusingly) where the + * caller can choose how to react. */ private static final ThreadLocal<ArrayList<ViolationInfo>> gatheredViolations = new ThreadLocal<ArrayList<ViolationInfo>>() { - @Override protected ArrayList<ViolationInfo> initialValue() { - // Starts null to avoid unnecessary allocations when - // checking whether there are any violations or not in - // hasGatheredViolations() below. - return null; - } - }; + @Override + protected ArrayList<ViolationInfo> initialValue() { + // Starts null to avoid unnecessary allocations when + // checking whether there are any violations or not in + // hasGatheredViolations() below. + return null; + } + }; /** - * Sets the policy for what actions on the current thread should - * be detected, as well as the penalty if such actions occur. + * Sets the policy for what actions on the current thread should be detected, as well as the + * penalty if such actions occur. * - * <p>Internally this sets a thread-local variable which is - * propagated across cross-process IPC calls, meaning you can - * catch violations when a system service or another process - * accesses the disk or network on your behalf. + * <p>Internally this sets a thread-local variable which is propagated across cross-process IPC + * calls, meaning you can catch violations when a system service or another process accesses the + * disk or network on your behalf. * * @param policy the policy to put into place */ @@ -1016,7 +945,7 @@ public final class StrictMode { if (policy instanceof AndroidBlockGuardPolicy) { androidPolicy = (AndroidBlockGuardPolicy) policy; } else { - androidPolicy = threadAndroidPolicy.get(); + androidPolicy = THREAD_ANDROID_POLICY.get(); BlockGuard.setThreadPolicy(androidPolicy); } androidPolicy.setPolicyMask(policyMask); @@ -1030,63 +959,49 @@ public final class StrictMode { CloseGuard.setEnabled(enabled); } - /** - * @hide - */ + /** @hide */ public static class StrictModeViolation extends BlockGuard.BlockGuardPolicyException { public StrictModeViolation(int policyState, int policyViolated, String message) { super(policyState, policyViolated, message); } } - /** - * @hide - */ + /** @hide */ public static class StrictModeNetworkViolation extends StrictModeViolation { public StrictModeNetworkViolation(int policyMask) { super(policyMask, DETECT_NETWORK, null); } } - /** - * @hide - */ + /** @hide */ private static class StrictModeDiskReadViolation extends StrictModeViolation { public StrictModeDiskReadViolation(int policyMask) { super(policyMask, DETECT_DISK_READ, null); } } - /** - * @hide - */ - private static class StrictModeDiskWriteViolation extends StrictModeViolation { + /** @hide */ + private static class StrictModeDiskWriteViolation extends StrictModeViolation { public StrictModeDiskWriteViolation(int policyMask) { super(policyMask, DETECT_DISK_WRITE, null); } } - /** - * @hide - */ + /** @hide */ private static class StrictModeCustomViolation extends StrictModeViolation { public StrictModeCustomViolation(int policyMask, String name) { super(policyMask, DETECT_CUSTOM, name); } } - /** - * @hide - */ + /** @hide */ private static class StrictModeResourceMismatchViolation extends StrictModeViolation { public StrictModeResourceMismatchViolation(int policyMask, Object tag) { super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null); } } - /** - * @hide - */ + /** @hide */ private static class StrictModeUnbufferedIOViolation extends StrictModeViolation { public StrictModeUnbufferedIOViolation(int policyMask) { super(policyMask, DETECT_UNBUFFERED_IO, null); @@ -1097,16 +1012,13 @@ public final class StrictMode { * Returns the bitmask of the current thread's policy. * * @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled - * * @hide */ public static int getThreadPolicyMask() { return BlockGuard.getThreadPolicy().getPolicyMask(); } - /** - * Returns the current thread's policy. - */ + /** Returns the current thread's policy. */ public static ThreadPolicy getThreadPolicy() { // TODO: this was a last minute Gingerbread API change (to // introduce VmPolicy cleanly) but this isn't particularly @@ -1116,14 +1028,13 @@ public final class StrictMode { } /** - * A convenience wrapper that takes the current - * {@link ThreadPolicy} from {@link #getThreadPolicy}, modifies it - * to permit both disk reads & writes, and sets the new policy - * with {@link #setThreadPolicy}, returning the old policy so you - * can restore it at the end of a block. + * A convenience wrapper that takes the current {@link ThreadPolicy} from {@link + * #getThreadPolicy}, modifies it to permit both disk reads & writes, and sets the new + * policy with {@link #setThreadPolicy}, returning the old policy so you can restore it at the + * end of a block. * - * @return the old policy, to be passed to {@link #setThreadPolicy} to - * restore the policy at the end of a block + * @return the old policy, to be passed to {@link #setThreadPolicy} to restore the policy at the + * end of a block */ public static ThreadPolicy allowThreadDiskWrites() { int oldPolicyMask = getThreadPolicyMask(); @@ -1135,14 +1046,11 @@ public final class StrictMode { } /** - * A convenience wrapper that takes the current - * {@link ThreadPolicy} from {@link #getThreadPolicy}, modifies it - * to permit disk reads, and sets the new policy - * with {@link #setThreadPolicy}, returning the old policy so you - * can restore it at the end of a block. + * A convenience wrapper that takes the current {@link ThreadPolicy} from {@link + * #getThreadPolicy}, modifies it to permit disk reads, and sets the new policy with {@link + * #setThreadPolicy}, returning the old policy so you can restore it at the end of a block. * - * @return the old policy, to be passed to setThreadPolicy to - * restore the policy. + * @return the old policy, to be passed to setThreadPolicy to restore the policy. */ public static ThreadPolicy allowThreadDiskReads() { int oldPolicyMask = getThreadPolicyMask(); @@ -1182,8 +1090,8 @@ public final class StrictMode { * @hide */ public static boolean conditionallyEnableDebugLogging() { - boolean doFlashes = SystemProperties.getBoolean(VISUAL_PROPERTY, false) - && !amTheSystemServerProcess(); + boolean doFlashes = + SystemProperties.getBoolean(VISUAL_PROPERTY, false) && !amTheSystemServerProcess(); final boolean suppress = SystemProperties.getBoolean(DISABLE_PROPERTY, false); // For debug builds, log event loop stalls to dropbox for analysis. @@ -1201,9 +1109,10 @@ public final class StrictMode { } // Thread policy controls BlockGuard. - int threadPolicyMask = StrictMode.DETECT_DISK_WRITE | - StrictMode.DETECT_DISK_READ | - StrictMode.DETECT_NETWORK; + int threadPolicyMask = + StrictMode.DETECT_DISK_WRITE + | StrictMode.DETECT_DISK_READ + | StrictMode.DETECT_NETWORK; if (!Build.IS_USER) { threadPolicyMask |= StrictMode.PENALTY_DROPBOX; @@ -1243,8 +1152,7 @@ public final class StrictMode { } /** - * Used by the framework to make network usage on the main - * thread a fatal error. + * Used by the framework to make network usage on the main thread a fatal error. * * @hide */ @@ -1264,8 +1172,8 @@ public final class StrictMode { } /** - * Used by lame internal apps that haven't done the hard work to get - * themselves off file:// Uris yet. + * Used by lame internal apps that haven't done the hard work to get themselves off file:// Uris + * yet. * * @hide */ @@ -1274,17 +1182,14 @@ public final class StrictMode { } /** - * Parses the BlockGuard policy mask out from the Exception's - * getMessage() String value. Kinda gross, but least - * invasive. :/ + * Parses the BlockGuard policy mask out from the Exception's getMessage() String value. Kinda + * gross, but least invasive. :/ * - * Input is of the following forms: - * "policy=137 violation=64" - * "policy=137 violation=64 msg=Arbitrary text" + * <p>Input is of the following forms: "policy=137 violation=64" "policy=137 violation=64 + * msg=Arbitrary text" * - * Returns 0 on failure, which is a valid policy, but not a - * valid policy during a violation (else there must've been - * some policy in effect to violate). + * <p>Returns 0 on failure, which is a valid policy, but not a valid policy during a violation + * (else there must've been some policy in effect to violate). */ private static int parsePolicyFromMessage(String message) { if (message == null || !message.startsWith("policy=")) { @@ -1302,9 +1207,7 @@ public final class StrictMode { } } - /** - * Like parsePolicyFromMessage(), but returns the violation. - */ + /** Like parsePolicyFromMessage(), but returns the violation. */ private static int parseViolationFromMessage(String message) { if (message == null) { return 0; @@ -1328,25 +1231,28 @@ public final class StrictMode { private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed = new ThreadLocal<ArrayList<ViolationInfo>>() { - @Override protected ArrayList<ViolationInfo> initialValue() { - return new ArrayList<ViolationInfo>(); - } - }; + @Override + protected ArrayList<ViolationInfo> initialValue() { + return new ArrayList<ViolationInfo>(); + } + }; // Note: only access this once verifying the thread has a Looper. - private static final ThreadLocal<Handler> threadHandler = new ThreadLocal<Handler>() { - @Override protected Handler initialValue() { - return new Handler(); - } - }; + private static final ThreadLocal<Handler> THREAD_HANDLER = + new ThreadLocal<Handler>() { + @Override + protected Handler initialValue() { + return new Handler(); + } + }; - private static final ThreadLocal<AndroidBlockGuardPolicy> - threadAndroidPolicy = new ThreadLocal<AndroidBlockGuardPolicy>() { - @Override - protected AndroidBlockGuardPolicy initialValue() { - return new AndroidBlockGuardPolicy(0); - } - }; + private static final ThreadLocal<AndroidBlockGuardPolicy> THREAD_ANDROID_POLICY = + new ThreadLocal<AndroidBlockGuardPolicy>() { + @Override + protected AndroidBlockGuardPolicy initialValue() { + return new AndroidBlockGuardPolicy(0); + } + }; private static boolean tooManyViolationsThisLoop() { return violationsBeingTimed.get().size() >= MAX_OFFENSES_PER_LOOP; @@ -1395,7 +1301,8 @@ public final class StrictMode { if (tooManyViolationsThisLoop()) { return; } - BlockGuard.BlockGuardPolicyException e = new StrictModeCustomViolation(mPolicyMask, name); + BlockGuard.BlockGuardPolicyException e = + new StrictModeCustomViolation(mPolicyMask, name); e.fillInStackTrace(); startHandlingViolationException(e); } @@ -1496,9 +1403,8 @@ public final class StrictMode { // // TODO: if in gather mode, ignore Looper.myLooper() and always // go into this immediate mode? - if (looper == null || - (info.policy & THREAD_PENALTY_MASK) == PENALTY_DEATH) { - info.durationMillis = -1; // unknown (redundant, already set) + if (looper == null || (info.policy & THREAD_PENALTY_MASK) == PENALTY_DEATH) { + info.durationMillis = -1; // unknown (redundant, already set) handleViolation(info); return; } @@ -1516,8 +1422,8 @@ public final class StrictMode { return; } - final IWindowManager windowManager = (info.policy & PENALTY_FLASH) != 0 ? - sWindowManager.get() : null; + final IWindowManager windowManager = + (info.policy & PENALTY_FLASH) != 0 ? sWindowManager.get() : null; if (windowManager != null) { try { windowManager.showStrictModeViolation(true); @@ -1534,31 +1440,34 @@ public final class StrictMode { // throttled back to 60fps via SurfaceFlinger/View // invalidates, _not_ by posting frame updates every 16 // milliseconds. - threadHandler.get().postAtFrontOfQueue(new Runnable() { - public void run() { - long loopFinishTime = SystemClock.uptimeMillis(); - - // Note: we do this early, before handling the - // violation below, as handling the violation - // may include PENALTY_DEATH and we don't want - // to keep the red border on. - if (windowManager != null) { - try { - windowManager.showStrictModeViolation(false); - } catch (RemoteException unused) { - } - } - - for (int n = 0; n < records.size(); ++n) { - ViolationInfo v = records.get(n); - v.violationNumThisLoop = n + 1; - v.durationMillis = - (int) (loopFinishTime - v.violationUptimeMillis); - handleViolation(v); - } - records.clear(); - } - }); + THREAD_HANDLER + .get() + .postAtFrontOfQueue( + new Runnable() { + public void run() { + long loopFinishTime = SystemClock.uptimeMillis(); + + // Note: we do this early, before handling the + // violation below, as handling the violation + // may include PENALTY_DEATH and we don't want + // to keep the red border on. + if (windowManager != null) { + try { + windowManager.showStrictModeViolation(false); + } catch (RemoteException unused) { + } + } + + for (int n = 0; n < records.size(); ++n) { + ViolationInfo v = records.get(n); + v.violationNumThisLoop = n + 1; + v.durationMillis = + (int) (loopFinishTime - v.violationUptimeMillis); + handleViolation(v); + } + records.clear(); + } + }); } // Note: It's possible (even quite likely) that the @@ -1603,17 +1512,21 @@ public final class StrictMode { } long now = SystemClock.uptimeMillis(); mLastViolationTime.put(crashFingerprint, now); - long timeSinceLastViolationMillis = lastViolationTime == 0 ? - Long.MAX_VALUE : (now - lastViolationTime); + long timeSinceLastViolationMillis = + lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime); if ((info.policy & PENALTY_LOG) != 0 && sListener != null) { sListener.onViolation(info.crashInfo.stackTrace); } - if ((info.policy & PENALTY_LOG) != 0 && - timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) { + if ((info.policy & PENALTY_LOG) != 0 + && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) { if (info.durationMillis != -1) { - Log.d(TAG, "StrictMode policy violation; ~duration=" + - info.durationMillis + " ms: " + info.crashInfo.stackTrace); + Log.d( + TAG, + "StrictMode policy violation; ~duration=" + + info.durationMillis + + " ms: " + + info.crashInfo.stackTrace); } else { Log.d(TAG, "StrictMode policy violation: " + info.crashInfo.stackTrace); } @@ -1625,8 +1538,8 @@ public final class StrictMode { // by the ActivityManagerService remaining set. int violationMaskSubset = 0; - if ((info.policy & PENALTY_DIALOG) != 0 && - timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) { + if ((info.policy & PENALTY_DIALOG) != 0 + && timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) { violationMaskSubset |= PENALTY_DIALOG; } @@ -1659,10 +1572,9 @@ public final class StrictMode { // We restore the current policy below, in the finally block. setThreadPolicyMask(0); - ActivityManager.getService().handleApplicationStrictModeViolation( - RuntimeInit.getApplicationObject(), - violationMaskSubset, - info); + ActivityManager.getService() + .handleApplicationStrictModeViolation( + RuntimeInit.getApplicationObject(), violationMaskSubset, info); } catch (RemoteException e) { if (e instanceof DeadObjectException) { // System process is dead; ignore @@ -1687,12 +1599,10 @@ public final class StrictMode { } /** - * In the common case, as set by conditionallyEnableDebugLogging, - * we're just dropboxing any violations but not showing a dialog, - * not loggging, and not killing the process. In these cases we - * don't need to do a synchronous call to the ActivityManager. - * This is used by both per-thread and vm-wide violations when - * applicable. + * In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any + * violations but not showing a dialog, not loggging, and not killing the process. In these + * cases we don't need to do a synchronous call to the ActivityManager. This is used by both + * per-thread and vm-wide violations when applicable. */ private static void dropboxViolationAsync( final int violationMaskSubset, final ViolationInfo info) { @@ -1715,9 +1625,7 @@ public final class StrictMode { Log.d(TAG, "No activity manager; failed to Dropbox violation."); } else { am.handleApplicationStrictModeViolation( - RuntimeInit.getApplicationObject(), - violationMaskSubset, - info); + RuntimeInit.getApplicationObject(), violationMaskSubset, info); } } catch (RemoteException e) { if (e instanceof DeadObjectException) { @@ -1738,25 +1646,20 @@ public final class StrictMode { } } - /** - * Called from Parcel.writeNoException() - */ + /** Called from Parcel.writeNoException() */ /* package */ static boolean hasGatheredViolations() { return gatheredViolations.get() != null; } /** - * Called from Parcel.writeException(), so we drop this memory and - * don't incorrectly attribute it to the wrong caller on the next - * Binder call on this thread. + * Called from Parcel.writeException(), so we drop this memory and don't incorrectly attribute + * it to the wrong caller on the next Binder call on this thread. */ /* package */ static void clearGatheredViolations() { gatheredViolations.set(null); } - /** - * @hide - */ + /** @hide */ public static void conditionallyCheckInstanceCounts() { VmPolicy policy = getVmPolicy(); int policySize = policy.classInstanceLimit.size(); @@ -1784,7 +1687,7 @@ public final class StrictMode { } private static long sLastInstanceCountCheckMillis = 0; - private static boolean sIsIdlerRegistered = false; // guarded by StrictMode.class + private static boolean sIsIdlerRegistered = false; // guarded by StrictMode.class private static final MessageQueue.IdleHandler sProcessIdleHandler = new MessageQueue.IdleHandler() { public boolean queueIdle() { @@ -1798,9 +1701,8 @@ public final class StrictMode { }; /** - * Sets the policy for what actions in the VM process (on any - * thread) should be detected, as well as the penalty if such - * actions occur. + * Sets the policy for what actions in the VM process (on any thread) should be detected, as + * well as the penalty if such actions occur. * * @param policy the policy to put into place */ @@ -1813,8 +1715,8 @@ public final class StrictMode { Looper looper = Looper.getMainLooper(); if (looper != null) { MessageQueue mq = looper.mQueue; - if (policy.classInstanceLimit.size() == 0 || - (sVmPolicyMask & VM_PENALTY_MASK) == 0) { + if (policy.classInstanceLimit.size() == 0 + || (sVmPolicyMask & VM_PENALTY_MASK) == 0) { mq.removeIdleHandler(sProcessIdleHandler); sIsIdlerRegistered = false; } else if (!sIsIdlerRegistered) { @@ -1833,8 +1735,9 @@ public final class StrictMode { } } - final INetworkManagementService netd = INetworkManagementService.Stub.asInterface( - ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + final INetworkManagementService netd = + INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); if (netd != null) { try { netd.setUidCleartextNetworkPolicy(android.os.Process.myUid(), networkPolicy); @@ -1846,9 +1749,7 @@ public final class StrictMode { } } - /** - * Gets the current VM policy. - */ + /** Gets the current VM policy. */ public static VmPolicy getVmPolicy() { synchronized (StrictMode.class) { return sVmPolicy; @@ -1858,102 +1759,72 @@ public final class StrictMode { /** * Enable the recommended StrictMode defaults, with violations just being logged. * - * <p>This catches disk and network access on the main thread, as - * well as leaked SQLite cursors and unclosed resources. This is - * simply a wrapper around {@link #setVmPolicy} and {@link + * <p>This catches disk and network access on the main thread, as well as leaked SQLite cursors + * and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link * #setThreadPolicy}. */ public static void enableDefaults() { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectAll() - .penaltyLog() - .build()); - StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() - .detectAll() - .penaltyLog() - .build()); + StrictMode.setThreadPolicy( + new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build()); } - /** - * @hide - */ + /** @hide */ public static boolean vmSqliteObjectLeaksEnabled() { return (sVmPolicyMask & DETECT_VM_CURSOR_LEAKS) != 0; } - /** - * @hide - */ + /** @hide */ public static boolean vmClosableObjectLeaksEnabled() { return (sVmPolicyMask & DETECT_VM_CLOSABLE_LEAKS) != 0; } - /** - * @hide - */ + /** @hide */ public static boolean vmRegistrationLeaksEnabled() { return (sVmPolicyMask & DETECT_VM_REGISTRATION_LEAKS) != 0; } - /** - * @hide - */ + /** @hide */ public static boolean vmFileUriExposureEnabled() { return (sVmPolicyMask & DETECT_VM_FILE_URI_EXPOSURE) != 0; } - /** - * @hide - */ + /** @hide */ public static boolean vmCleartextNetworkEnabled() { return (sVmPolicyMask & DETECT_VM_CLEARTEXT_NETWORK) != 0; } - /** - * @hide - */ + /** @hide */ public static boolean vmContentUriWithoutPermissionEnabled() { return (sVmPolicyMask & DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION) != 0; } - /** - * @hide - */ + /** @hide */ public static boolean vmUntaggedSocketEnabled() { return (sVmPolicyMask & DETECT_VM_UNTAGGED_SOCKET) != 0; } - /** - * @hide - */ + /** @hide */ public static void onSqliteObjectLeaked(String message, Throwable originStack) { onVmPolicyViolation(message, originStack); } - /** - * @hide - */ + /** @hide */ public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) { onVmPolicyViolation(null, originStack); } - /** - * @hide - */ + /** @hide */ public static void onIntentReceiverLeaked(Throwable originStack) { onVmPolicyViolation(null, originStack); } - /** - * @hide - */ + /** @hide */ public static void onServiceConnectionLeaked(Throwable originStack) { onVmPolicyViolation(null, originStack); } - /** - * @hide - */ + /** @hide */ public static void onFileUriExposed(Uri uri, String location) { final String message = uri + " exposed beyond app through " + location; if ((sVmPolicyMask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) { @@ -1963,19 +1834,18 @@ public final class StrictMode { } } - /** - * @hide - */ + /** @hide */ public static void onContentUriWithoutPermission(Uri uri, String location) { - final String message = uri + " exposed beyond app through " + location - + " without permission grant flags; did you forget" - + " FLAG_GRANT_READ_URI_PERMISSION?"; + final String message = + uri + + " exposed beyond app through " + + location + + " without permission grant flags; did you forget" + + " FLAG_GRANT_READ_URI_PERMISSION?"; onVmPolicyViolation(null, new Throwable(message)); } - /** - * @hide - */ + /** @hide */ public static void onCleartextNetworkDetected(byte[] firstPacket) { byte[] rawAddr = null; if (firstPacket != null) { @@ -1994,40 +1864,40 @@ public final class StrictMode { String msg = "Detected cleartext network traffic from UID " + uid; if (rawAddr != null) { try { - msg = "Detected cleartext network traffic from UID " + uid + " to " - + InetAddress.getByAddress(rawAddr); + msg = + "Detected cleartext network traffic from UID " + + uid + + " to " + + InetAddress.getByAddress(rawAddr); } catch (UnknownHostException ignored) { } } final boolean forceDeath = (sVmPolicyMask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0; - onVmPolicyViolation(HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), - forceDeath); + onVmPolicyViolation( + HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), forceDeath); } - /** - * @hide - */ + /** @hide */ public static void onUntaggedSocket() { - onVmPolicyViolation(null, new Throwable("Untagged socket detected; use" - + " TrafficStats.setThreadSocketTag() to track all network usage")); + onVmPolicyViolation( + null, + new Throwable( + "Untagged socket detected; use" + + " TrafficStats.setThreadSocketTag() to track all network usage")); } // Map from VM violation fingerprint to uptime millis. private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>(); - /** - * @hide - */ + /** @hide */ public static void onVmPolicyViolation(String message, Throwable originStack) { onVmPolicyViolation(message, originStack, false); } - /** - * @hide - */ - public static void onVmPolicyViolation(String message, Throwable originStack, - boolean forceDeath) { + /** @hide */ + public static void onVmPolicyViolation( + String message, Throwable originStack, boolean forceDeath) { final boolean penaltyDropbox = (sVmPolicyMask & PENALTY_DROPBOX) != 0; final boolean penaltyDeath = ((sVmPolicyMask & PENALTY_DEATH) != 0) || forceDeath; final boolean penaltyLog = (sVmPolicyMask & PENALTY_LOG) != 0; @@ -2082,10 +1952,9 @@ public final class StrictMode { // We restore the current policy below, in the finally block. setThreadPolicyMask(0); - ActivityManager.getService().handleApplicationStrictModeViolation( - RuntimeInit.getApplicationObject(), - violationMaskSubset, - info); + ActivityManager.getService() + .handleApplicationStrictModeViolation( + RuntimeInit.getApplicationObject(), violationMaskSubset, info); } catch (RemoteException e) { if (e instanceof DeadObjectException) { // System process is dead; ignore @@ -2105,9 +1974,7 @@ public final class StrictMode { } } - /** - * Called from Parcel.writeNoException() - */ + /** Called from Parcel.writeNoException() */ /* package */ static void writeGatheredViolationsToParcel(Parcel p) { ArrayList<ViolationInfo> violations = gatheredViolations.get(); if (violations == null) { @@ -2128,8 +1995,8 @@ public final class StrictMode { private static class LogStackTrace extends Exception {} /** - * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, - * we here read back all the encoded violations. + * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, we here + * read back all the encoded violations. */ /* package */ static void readAndHandleBinderCallViolations(Parcel p) { // Our own stack trace to append @@ -2155,22 +2022,20 @@ public final class StrictMode { } /** - * Called from android_util_Binder.cpp's - * android_os_Parcel_enforceInterface when an incoming Binder call - * requires changing the StrictMode policy mask. The role of this - * function is to ask Binder for its current (native) thread-local - * policy value and synchronize it to libcore's (Java) - * thread-local policy value. + * Called from android_util_Binder.cpp's android_os_Parcel_enforceInterface when an incoming + * Binder call requires changing the StrictMode policy mask. The role of this function is to ask + * Binder for its current (native) thread-local policy value and synchronize it to libcore's + * (Java) thread-local policy value. */ private static void onBinderStrictModePolicyChange(int newPolicy) { setBlockGuardPolicy(newPolicy); } /** - * A tracked, critical time span. (e.g. during an animation.) + * A tracked, critical time span. (e.g. during an animation.) * - * The object itself is a linked list node, to avoid any allocations - * during rapid span entries and exits. + * <p>The object itself is a linked list node, to avoid any allocations during rapid span + * entries and exits. * * @hide */ @@ -2178,7 +2043,7 @@ public final class StrictMode { private String mName; private long mCreateMillis; private Span mNext; - private Span mPrev; // not used when in freeList, only active + private Span mPrev; // not used when in freeList, only active private final ThreadSpanState mContainerState; Span(ThreadSpanState threadState) { @@ -2191,12 +2056,10 @@ public final class StrictMode { } /** - * To be called when the critical span is complete (i.e. the - * animation is done animating). This can be called on any - * thread (even a different one from where the animation was - * taking place), but that's only a defensive implementation - * measure. It really makes no sense for you to call this on - * thread other than that where you created it. + * To be called when the critical span is complete (i.e. the animation is done animating). + * This can be called on any thread (even a different one from where the animation was + * taking place), but that's only a defensive implementation measure. It really makes no + * sense for you to call this on thread other than that where you created it. * * @hide */ @@ -2240,53 +2103,52 @@ public final class StrictMode { } // The no-op span that's used in user builds. - private static final Span NO_OP_SPAN = new Span() { - public void finish() { - // Do nothing. - } - }; + private static final Span NO_OP_SPAN = + new Span() { + public void finish() { + // Do nothing. + } + }; /** * Linked lists of active spans and a freelist. * - * Locking notes: there's one of these structures per thread and - * all members of this structure (as well as the Span nodes under - * it) are guarded by the ThreadSpanState object instance. While - * in theory there'd be no locking required because it's all local - * per-thread, the finish() method above is defensive against - * people calling it on a different thread from where they created - * the Span, hence the locking. + * <p>Locking notes: there's one of these structures per thread and all members of this + * structure (as well as the Span nodes under it) are guarded by the ThreadSpanState object + * instance. While in theory there'd be no locking required because it's all local per-thread, + * the finish() method above is defensive against people calling it on a different thread from + * where they created the Span, hence the locking. */ private static class ThreadSpanState { - public Span mActiveHead; // doubly-linked list. + public Span mActiveHead; // doubly-linked list. public int mActiveSize; - public Span mFreeListHead; // singly-linked list. only changes at head. + public Span mFreeListHead; // singly-linked list. only changes at head. public int mFreeListSize; } private static final ThreadLocal<ThreadSpanState> sThisThreadSpanState = new ThreadLocal<ThreadSpanState>() { - @Override protected ThreadSpanState initialValue() { - return new ThreadSpanState(); - } - }; + @Override + protected ThreadSpanState initialValue() { + return new ThreadSpanState(); + } + }; - private static Singleton<IWindowManager> sWindowManager = new Singleton<IWindowManager>() { - protected IWindowManager create() { - return IWindowManager.Stub.asInterface(ServiceManager.getService("window")); - } - }; + private static Singleton<IWindowManager> sWindowManager = + new Singleton<IWindowManager>() { + protected IWindowManager create() { + return IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + } + }; /** * Enter a named critical span (e.g. an animation) * - * <p>The name is an arbitary label (or tag) that will be applied - * to any strictmode violation that happens while this span is - * active. You must call finish() on the span when done. + * <p>The name is an arbitary label (or tag) that will be applied to any strictmode violation + * that happens while this span is active. You must call finish() on the span when done. * - * <p>This will never return null, but on devices without debugging - * enabled, this may return a dummy object on which the finish() - * method is a no-op. + * <p>This will never return null, but on devices without debugging enabled, this may return a + * dummy object on which the finish() method is a no-op. * * <p>TODO: add CloseGuard to this, verifying callers call finish. * @@ -2325,13 +2187,11 @@ public final class StrictMode { } /** - * For code to note that it's slow. This is a no-op unless the - * current thread's {@link android.os.StrictMode.ThreadPolicy} has - * {@link android.os.StrictMode.ThreadPolicy.Builder#detectCustomSlowCalls} - * enabled. + * For code to note that it's slow. This is a no-op unless the current thread's {@link + * android.os.StrictMode.ThreadPolicy} has {@link + * android.os.StrictMode.ThreadPolicy.Builder#detectCustomSlowCalls} enabled. * - * @param name a short string for the exception stack trace that's - * built if when this fires. + * @param name a short string for the exception stack trace that's built if when this fires. */ public static void noteSlowCall(String name) { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); @@ -2343,14 +2203,11 @@ public final class StrictMode { } /** - * For code to note that a resource was obtained using a type other than - * its defined type. This is a no-op unless the current thread's - * {@link android.os.StrictMode.ThreadPolicy} has - * {@link android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()} - * enabled. + * For code to note that a resource was obtained using a type other than its defined type. This + * is a no-op unless the current thread's {@link android.os.StrictMode.ThreadPolicy} has {@link + * android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()} enabled. * - * @param tag an object for the exception stack trace that's - * built if when this fires. + * @param tag an object for the exception stack trace that's built if when this fires. * @hide */ public static void noteResourceMismatch(Object tag) { @@ -2362,9 +2219,7 @@ public final class StrictMode { ((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag); } - /** - * @hide - */ + /** @hide */ public static void noteUnbufferedIO() { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (!(policy instanceof AndroidBlockGuardPolicy)) { @@ -2374,9 +2229,7 @@ public final class StrictMode { ((AndroidBlockGuardPolicy) policy).onUnbufferedIO(); } - /** - * @hide - */ + /** @hide */ public static void noteDiskRead() { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (!(policy instanceof AndroidBlockGuardPolicy)) { @@ -2386,9 +2239,7 @@ public final class StrictMode { ((AndroidBlockGuardPolicy) policy).onReadFromDisk(); } - /** - * @hide - */ + /** @hide */ public static void noteDiskWrite() { BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (!(policy instanceof AndroidBlockGuardPolicy)) { @@ -2403,17 +2254,16 @@ public final class StrictMode { new HashMap<Class, Integer>(); /** - * Returns an object that is used to track instances of activites. - * The activity should store a reference to the tracker object in one of its fields. + * Returns an object that is used to track instances of activites. The activity should store a + * reference to the tracker object in one of its fields. + * * @hide */ public static Object trackActivity(Object instance) { return new InstanceTracker(instance); } - /** - * @hide - */ + /** @hide */ public static void incrementExpectedActivityCount(Class klass) { if (klass == null) { return; @@ -2430,9 +2280,7 @@ public final class StrictMode { } } - /** - * @hide - */ + /** @hide */ public static void decrementExpectedActivityCount(Class klass) { if (klass == null) { return; @@ -2483,70 +2331,49 @@ public final class StrictMode { } /** - * Parcelable that gets sent in Binder call headers back to callers - * to report violations that happened during a cross-process call. + * Parcelable that gets sent in Binder call headers back to callers to report violations that + * happened during a cross-process call. * * @hide */ public static class ViolationInfo implements Parcelable { public final String message; - /** - * Stack and other stuff info. - */ + /** Stack and other stuff info. */ public final ApplicationErrorReport.CrashInfo crashInfo; - /** - * The strict mode policy mask at the time of violation. - */ + /** The strict mode policy mask at the time of violation. */ public final int policy; - /** - * The wall time duration of the violation, when known. -1 when - * not known. - */ + /** The wall time duration of the violation, when known. -1 when not known. */ public int durationMillis = -1; - /** - * The number of animations currently running. - */ + /** The number of animations currently running. */ public int numAnimationsRunning = 0; - /** - * List of tags from active Span instances during this - * violation, or null for none. - */ + /** List of tags from active Span instances during this violation, or null for none. */ public String[] tags; /** - * Which violation number this was (1-based) since the last Looper loop, - * from the perspective of the root caller (if it crossed any processes - * via Binder calls). The value is 0 if the root caller wasn't on a Looper - * thread. + * Which violation number this was (1-based) since the last Looper loop, from the + * perspective of the root caller (if it crossed any processes via Binder calls). The value + * is 0 if the root caller wasn't on a Looper thread. */ public int violationNumThisLoop; - /** - * The time (in terms of SystemClock.uptimeMillis()) that the - * violation occurred. - */ + /** The time (in terms of SystemClock.uptimeMillis()) that the violation occurred. */ public long violationUptimeMillis; /** - * The action of the Intent being broadcast to somebody's onReceive - * on this thread right now, or null. + * The action of the Intent being broadcast to somebody's onReceive on this thread right + * now, or null. */ public String broadcastIntentAction; - /** - * If this is a instance count violation, the number of instances in memory, - * else -1. - */ + /** If this is a instance count violation, the number of instances in memory, else -1. */ public long numInstances = -1; - /** - * Create an uninitialized instance of ViolationInfo - */ + /** Create an uninitialized instance of ViolationInfo */ public ViolationInfo() { message = null; crashInfo = null; @@ -2557,9 +2384,7 @@ public final class StrictMode { this(null, tr, policy); } - /** - * Create an instance of ViolationInfo initialized from an exception. - */ + /** Create an instance of ViolationInfo initialized from an exception. */ public ViolationInfo(String message, Throwable tr, int policy) { this.message = message; crashInfo = new ApplicationErrorReport.CrashInfo(tr); @@ -2612,9 +2437,7 @@ public final class StrictMode { return result; } - /** - * Create an instance of ViolationInfo initialized from a Parcel. - */ + /** Create an instance of ViolationInfo initialized from a Parcel. */ public ViolationInfo(Parcel in) { this(in, false); } @@ -2622,8 +2445,8 @@ public final class StrictMode { /** * Create an instance of ViolationInfo initialized from a Parcel. * - * @param unsetGatheringBit if true, the caller is the root caller - * and the gathering penalty should be removed. + * @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty + * should be removed. */ public ViolationInfo(Parcel in, boolean unsetGatheringBit) { message = in.readString(); @@ -2647,9 +2470,7 @@ public final class StrictMode { tags = in.readStringArray(); } - /** - * Save a ViolationInfo instance to a parcel. - */ + /** Save a ViolationInfo instance to a parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(message); @@ -2668,23 +2489,29 @@ public final class StrictMode { dest.writeLong(numInstances); dest.writeString(broadcastIntentAction); dest.writeStringArray(tags); - int total = dest.dataPosition()-start; - if (Binder.CHECK_PARCEL_SIZE && total > 10*1024) { - Slog.d(TAG, "VIO: policy=" + policy + " dur=" + durationMillis - + " numLoop=" + violationNumThisLoop - + " anim=" + numAnimationsRunning - + " uptime=" + violationUptimeMillis - + " numInst=" + numInstances); + int total = dest.dataPosition() - start; + if (Binder.CHECK_PARCEL_SIZE && total > 10 * 1024) { + Slog.d( + TAG, + "VIO: policy=" + + policy + + " dur=" + + durationMillis + + " numLoop=" + + violationNumThisLoop + + " anim=" + + numAnimationsRunning + + " uptime=" + + violationUptimeMillis + + " numInst=" + + numInstances); Slog.d(TAG, "VIO: action=" + broadcastIntentAction); Slog.d(TAG, "VIO: tags=" + Arrays.toString(tags)); - Slog.d(TAG, "VIO: TOTAL BYTES WRITTEN: " + (dest.dataPosition()-start)); + Slog.d(TAG, "VIO: TOTAL BYTES WRITTEN: " + (dest.dataPosition() - start)); } } - - /** - * Dump a ViolationInfo instance to a Printer. - */ + /** Dump a ViolationInfo instance to a Printer. */ public void dump(Printer pw, String prefix) { if (crashInfo != null) { crashInfo.dump(pw, prefix); @@ -2743,8 +2570,8 @@ public final class StrictMode { final int mLimit; private static final StackTraceElement[] FAKE_STACK = { - new StackTraceElement("android.os.StrictMode", "setClassInstanceLimit", - "StrictMode.java", 1) + new StackTraceElement( + "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1) }; public InstanceCountViolation(Class klass, long instances, int limit) { diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index fe9e8c67e566..da0ed54e003e 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -149,14 +149,43 @@ public abstract class VibrationEffect implements Parcelable { * provide a better experience than you could otherwise build using the generic building * blocks. * + * This will fallback to a generic pattern if one exists and there does not exist a + * hardware-specific implementation of the effect. + * * @param effectId The ID of the effect to perform: - * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}. + * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} * * @return The desired effect. * @hide */ public static VibrationEffect get(int effectId) { - VibrationEffect effect = new Prebaked(effectId); + return get(effectId, true); + } + + /** + * Get a predefined vibration effect. + * + * Predefined effects are a set of common vibration effects that should be identical, regardless + * of the app they come from, in order to provide a cohesive experience for users across + * the entire device. They also may be custom tailored to the device hardware in order to + * provide a better experience than you could otherwise build using the generic building + * blocks. + * + * Some effects you may only want to play if there's a hardware specific implementation because + * they may, for example, be too disruptive to the user without tuning. The {@code fallback} + * parameter allows you to decide whether you want to fallback to the generic implementation or + * only play if there's a tuned, hardware specific one available. + * + * @param effectId The ID of the effect to perform: + * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} + * @param fallback Whether to fallback to a generic pattern if a hardware specific + * implementation doesn't exist. + * + * @return The desired effect. + * @hide + */ + public static VibrationEffect get(int effectId, boolean fallback) { + VibrationEffect effect = new Prebaked(effectId, fallback); effect.validate(); return effect; } @@ -374,19 +403,29 @@ public abstract class VibrationEffect implements Parcelable { /** @hide */ public static class Prebaked extends VibrationEffect implements Parcelable { private int mEffectId; + private boolean mFallback; public Prebaked(Parcel in) { - this(in.readInt()); + this(in.readInt(), in.readByte() != 0); } - public Prebaked(int effectId) { + public Prebaked(int effectId, boolean fallback) { mEffectId = effectId; + mFallback = fallback; } public int getId() { return mEffectId; } + /** + * Whether the effect should fall back to a generic pattern if there's no hardware specific + * implementation of it. + */ + public boolean shouldFallback() { + return mFallback; + } + @Override public void validate() { switch (mEffectId) { @@ -406,7 +445,7 @@ public abstract class VibrationEffect implements Parcelable { return false; } VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o; - return mEffectId == other.mEffectId; + return mEffectId == other.mEffectId && mFallback == other.mFallback; } @Override @@ -416,7 +455,7 @@ public abstract class VibrationEffect implements Parcelable { @Override public String toString() { - return "Prebaked{mEffectId=" + mEffectId + "}"; + return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}"; } @@ -424,6 +463,7 @@ public abstract class VibrationEffect implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_EFFECT); out.writeInt(mEffectId); + out.writeByte((byte) (mFallback ? 1 : 0)); } public static final Parcelable.Creator<Prebaked> CREATOR = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fa523f36a113..c44b0bcf9acc 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -31,6 +31,7 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.Application; import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.SearchManager; import android.app.WallpaperManager; @@ -1311,6 +1312,18 @@ public final class Settings { = "android.settings.CHANNEL_NOTIFICATION_SETTINGS"; /** + * Activity Action: Show notification settings for a single {@link NotificationChannelGroup}. + * <p> + * Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel group to display. + * Input: {@link #EXTRA_CHANNEL_GROUP_ID}, the id of the channel group to display. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = + "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS"; + + /** * Activity Extra: The package owner of the notification channel settings to display. * <p> * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}. @@ -1326,6 +1339,15 @@ public final class Settings { public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; /** + * Activity Extra: The {@link NotificationChannelGroup#getId()} of the notification channel + * group settings to display. + * <p> + * This must be passed as an extra field to the + * {@link #ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS}. + */ + public static final String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID"; + + /** * Activity Action: Show notification redaction settings. * * @hide @@ -9334,6 +9356,25 @@ public final class Settings { public static final String ANOMALY_DETECTION_CONSTANTS = "anomaly_detection_constants"; /** + * Always on display(AOD) specific settings + * This is encoded as a key=value list, separated by commas. Ex: + * + * "prox_screen_off_delay=10000,screen_brightness_array=0:1:2:3:4" + * + * The following keys are supported: + * + * <pre> + * screen_brightness_array (string) + * dimming_scrim_array (string) + * prox_screen_off_delay (long) + * prox_cooldown_trigger (long) + * prox_cooldown_period (long) + * </pre> + * @hide + */ + public static final String ALWAYS_ON_DISPLAY_CONSTANTS = "always_on_display_constants"; + + /** * App standby (app idle) specific settings. * This is encoded as a key=value list, separated by commas. Ex: * diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index f7dc1c58ade1..60c1c9a7e87a 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -81,10 +81,13 @@ public final class FillEventHistory implements Parcelable { /** * Returns the client state set in the previous {@link FillResponse}. * - * <p><b>NOTE: </b>the state is associated with the app that was autofilled in the previous + * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} * , which is not necessary the same app being autofilled now. + * + * @deprecated use {@link #getEvents()} then {@link Event#getClientState()} instead. */ + @Deprecated @Nullable public Bundle getClientState() { return mClientState; } @@ -126,7 +129,6 @@ public final class FillEventHistory implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeBundle(mClientState); - if (mEvents == null) { dest.writeInt(0); } else { @@ -137,6 +139,7 @@ public final class FillEventHistory implements Parcelable { Event event = mEvents.get(i); dest.writeInt(event.getType()); dest.writeString(event.getDatasetId()); + dest.writeBundle(event.getClientState()); } } } @@ -177,6 +180,7 @@ public final class FillEventHistory implements Parcelable { @EventIds private final int mEventType; @Nullable private final String mDatasetId; + @Nullable private final Bundle mClientState; /** * Returns the type of the event. @@ -197,18 +201,32 @@ public final class FillEventHistory implements Parcelable { } /** + * Returns the client state from the {@link FillResponse} used to generate this event. + * + * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous + * {@link + * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}, + * which is not necessary the same app being autofilled now. + */ + @Nullable public Bundle getClientState() { + return mClientState; + } + + /** * Creates a new event. * * @param eventType The type of the event * @param datasetId The dataset the event was on, or {@code null} if the event was on the * whole response. + * @param clientState The client state associated with the event. * * @hide */ - public Event(int eventType, String datasetId) { + public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState) { mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN, "eventType"); mDatasetId = datasetId; + mClientState = clientState; } } @@ -220,7 +238,8 @@ public final class FillEventHistory implements Parcelable { int numEvents = parcel.readInt(); for (int i = 0; i < numEvents; i++) { - selection.addEvent(new Event(parcel.readInt(), parcel.readString())); + selection.addEvent(new Event(parcel.readInt(), parcel.readString(), + parcel.readBundle())); } return selection; diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index b6a9a2648470..3b09c678ba8e 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -302,7 +302,7 @@ public final class FillResponse implements Parcelable { // TODO: create a dump() method instead return new StringBuilder( "FillResponse : [mRequestId=" + mRequestId) - .append(", datasets=").append(mDatasets) + .append(", datasets=").append(mDatasets == null ? "N/A" : mDatasets.getList()) .append(", saveInfo=").append(mSaveInfo) .append(", clientState=").append(mClientState != null) .append(", hasPresentation=").append(mPresentation != null) diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java index 44019c32560d..da7387fcae70 100644 --- a/core/java/android/util/ExceptionUtils.java +++ b/core/java/android/util/ExceptionUtils.java @@ -78,4 +78,12 @@ public class ExceptionUtils { propagateIfInstanceOf(t, RuntimeException.class); throw new RuntimeException(t); } + + /** + * Gets the root {@link Throwable#getCause() cause} of {@code t} + */ + public static @NonNull Throwable getRootCause(@NonNull Throwable t) { + while (t.getCause() != null) t = t.getCause(); + return t; + } } diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java new file mode 100644 index 000000000000..0be1a8cfabae --- /dev/null +++ b/core/java/android/util/StatsLog.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 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.util; + +/** + * Logging access for platform metrics. + * + * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})! + * These diagnostic stats are for system integrators, not application authors. + * + * <p>Stats use integer tag codes. + * They carry a payload of one or more int, long, or String values. + * @hide + */ +public class StatsLog { + /** @hide */ public StatsLog() {} + + private static final String TAG = "StatsLog"; + + // We assume that the native methods deal with any concurrency issues. + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param value A value to log + * @return The number of bytes written + */ + public static native int writeInt(int tag, int value); + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param value A value to log + * @return The number of bytes written + */ + public static native int writeLong(int tag, long value); + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param value A value to log + * @return The number of bytes written + */ + public static native int writeFloat(int tag, float value); + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param str A value to log + * @return The number of bytes written + */ + public static native int writeString(int tag, String str); + + /** + * Records an stats log message. + * @param tag The stats type tag code + * @param list A list of values to log. All values should + * be of type int, long, float or String. + * @return The number of bytes written + */ + public static native int writeArray(int tag, Object... list); +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ffb3203688eb..e5bd5ac076f3 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7621,6 +7621,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>Call * {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int, AutofillValue)} * when the value of a virtual child changed. + * <li>Call {@link + * android.view.autofill.AutofillManager#notifyViewVisibilityChanged(View, int, boolean)} + * when the visibility of a virtual child changed. * <li>Call {@link AutofillManager#commit()} when the autofill context of the view structure * changed and the current context should be committed (for example, when the user tapped * a {@code SUBMIT} button in an HTML page). diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 5adbdbe8ab4f..46742b8852ff 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -73,6 +73,9 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -188,17 +191,12 @@ public class RemoteViews implements Parcelable, Filter { private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); - private static final Object[] sMethodsLock = new Object[0]; - private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods = - new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>(); - private static final ArrayMap<Method, Method> sAsyncMethods = new ArrayMap<>(); + private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); - private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() { - @Override - protected Object[] initialValue() { - return new Object[1]; - } - }; + /** + * This key is used to perform lookups in sMethods without causing allocations. + */ + private static final MethodKey sLookupKey = new MethodKey(); /** * @hide @@ -255,37 +253,47 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Handle with care! + * Stores information related to reflection method lookup. */ - static class MutablePair<F, S> { - F first; - S second; - - MutablePair(F first, S second) { - this.first = first; - this.second = second; - } + static class MethodKey { + public Class targetClass; + public Class paramClass; + public String methodName; @Override public boolean equals(Object o) { - if (!(o instanceof MutablePair)) { + if (!(o instanceof MethodKey)) { return false; } - MutablePair<?, ?> p = (MutablePair<?, ?>) o; - return Objects.equal(p.first, first) && Objects.equal(p.second, second); + MethodKey p = (MethodKey) o; + return Objects.equal(p.targetClass, targetClass) + && Objects.equal(p.paramClass, paramClass) + && Objects.equal(p.methodName, methodName); } @Override public int hashCode() { - return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass) + ^ Objects.hashCode(methodName); + } + + public void set(Class targetClass, Class paramClass, String methodName) { + this.targetClass = targetClass; + this.paramClass = paramClass; + this.methodName = methodName; } } + /** - * This pair is used to perform lookups in sMethods without causing allocations. + * Stores information related to reflection method lookup result. */ - private final MutablePair<String, Class<?>> mPair = - new MutablePair<String, Class<?>>(null, null); + static class MethodArgs { + public MethodHandle syncMethod; + public MethodHandle asyncMethod; + public String asyncMethodName; + } + /** * This annotation indicates that a subclass of View is allowed to be used @@ -307,6 +315,12 @@ public class RemoteViews implements Parcelable, Filter { public ActionException(String message) { super(message); } + /** + * @hide + */ + public ActionException(Throwable t) { + super(t); + } } /** @hide */ @@ -943,73 +957,66 @@ public class RemoteViews implements Parcelable, Filter { return rect; } - private Method getMethod(View view, String methodName, Class<?> paramType) { - Method method; + private MethodHandle getMethod(View view, String methodName, Class<?> paramType, + boolean async) { + MethodArgs result; Class<? extends View> klass = view.getClass(); - synchronized (sMethodsLock) { - ArrayMap<MutablePair<String, Class<?>>, Method> methods = sMethods.get(klass); - if (methods == null) { - methods = new ArrayMap<MutablePair<String, Class<?>>, Method>(); - sMethods.put(klass, methods); - } - - mPair.first = methodName; - mPair.second = paramType; + synchronized (sMethods) { + // The key is defined by the view class, param class and method name. + sLookupKey.set(klass, paramType, methodName); + result = sMethods.get(sLookupKey); - method = methods.get(mPair); - if (method == null) { + if (result == null) { + Method method; try { if (paramType == null) { method = klass.getMethod(methodName); } else { method = klass.getMethod(methodName, paramType); } - } catch (NoSuchMethodException ex) { - throw new ActionException("view: " + klass.getName() + " doesn't have method: " - + methodName + getParameters(paramType)); - } + if (!method.isAnnotationPresent(RemotableViewMethod.class)) { + throw new ActionException("view: " + klass.getName() + + " can't use method with RemoteViews: " + + methodName + getParameters(paramType)); + } - if (!method.isAnnotationPresent(RemotableViewMethod.class)) { - throw new ActionException("view: " + klass.getName() - + " can't use method with RemoteViews: " + result = new MethodArgs(); + result.syncMethod = MethodHandles.publicLookup().unreflect(method); + result.asyncMethodName = + method.getAnnotation(RemotableViewMethod.class).asyncImpl(); + } catch (NoSuchMethodException | IllegalAccessException ex) { + throw new ActionException("view: " + klass.getName() + " doesn't have method: " + methodName + getParameters(paramType)); } - methods.put(new MutablePair<String, Class<?>>(methodName, paramType), method); + MethodKey key = new MethodKey(); + key.set(klass, paramType, methodName); + sMethods.put(key, result); } - } - - return method; - } - /** - * @return the async implementation of the provided method. - */ - private Method getAsyncMethod(Method method) { - synchronized (sAsyncMethods) { - int valueIndex = sAsyncMethods.indexOfKey(method); - if (valueIndex >= 0) { - return sAsyncMethods.valueAt(valueIndex); + if (!async) { + return result.syncMethod; } - - RemotableViewMethod annotation = method.getAnnotation(RemotableViewMethod.class); - Method asyncMethod = null; - if (!annotation.asyncImpl().isEmpty()) { + // Check this so see if async method is implemented or not. + if (result.asyncMethodName.isEmpty()) { + return null; + } + // Async method is lazily loaded. If it is not yet loaded, load now. + if (result.asyncMethod == null) { + MethodType asyncType = result.syncMethod.type() + .dropParameterTypes(0, 1).changeReturnType(Runnable.class); try { - asyncMethod = method.getDeclaringClass() - .getMethod(annotation.asyncImpl(), method.getParameterTypes()); - if (!asyncMethod.getReturnType().equals(Runnable.class)) { - throw new ActionException("Async implementation for " + method.getName() + - " does not return a Runnable"); - } - } catch (NoSuchMethodException ex) { - throw new ActionException("Async implementation declared but not defined for " + - method.getName()); + result.asyncMethod = MethodHandles.publicLookup().findVirtual( + klass, result.asyncMethodName, asyncType); + } catch (NoSuchMethodException | IllegalAccessException ex) { + throw new ActionException("Async implementation declared as " + + result.asyncMethodName + " but not defined for " + methodName + + ": public Runnable " + result.asyncMethodName + " (" + + TextUtils.join(",", asyncType.parameterArray()) + ")"); } } - sAsyncMethods.put(method, asyncMethod); - return asyncMethod; + return result.asyncMethod; } } @@ -1018,12 +1025,6 @@ public class RemoteViews implements Parcelable, Filter { return "(" + paramType + ")"; } - private static Object[] wrapArg(Object value) { - Object[] args = sInvokeArgsTls.get(); - args[0] = value; - return args; - } - /** * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, @@ -1140,10 +1141,8 @@ public class RemoteViews implements Parcelable, Filter { if (view == null) return; try { - getMethod(view, this.methodName, null).invoke(view); - } catch (ActionException e) { - throw e; - } catch (Exception ex) { + getMethod(view, this.methodName, null, false /* async */).invoke(view); + } catch (Throwable ex) { throw new ActionException(ex); } } @@ -1516,12 +1515,9 @@ public class RemoteViews implements Parcelable, Filter { if (param == null) { throw new ActionException("bad type: " + this.type); } - try { - getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value)); - } catch (ActionException e) { - throw e; - } catch (Exception ex) { + getMethod(view, this.methodName, param, false /* async */).invoke(view, this.value); + } catch (Throwable ex) { throw new ActionException(ex); } } @@ -1537,11 +1533,10 @@ public class RemoteViews implements Parcelable, Filter { } try { - Method method = getMethod(view, this.methodName, param); - Method asyncMethod = getAsyncMethod(method); + MethodHandle method = getMethod(view, this.methodName, param, true /* async */); - if (asyncMethod != null) { - Runnable endAction = (Runnable) asyncMethod.invoke(view, wrapArg(this.value)); + if (method != null) { + Runnable endAction = (Runnable) method.invoke(view, this.value); if (endAction == null) { return ACTION_NOOP; } else { @@ -1555,9 +1550,7 @@ public class RemoteViews implements Parcelable, Filter { return new RunnableAction(endAction); } } - } catch (ActionException e) { - throw e; - } catch (Exception ex) { + } catch (Throwable ex) { throw new ActionException(ex); } @@ -2672,7 +2665,7 @@ public class RemoteViews implements Parcelable, Filter { * given {@link RemoteViews}. * * @param viewId The id of the parent {@link ViewGroup} to add the child into. - * @param nestedView {@link RemoveViews} of the child to add. + * @param nestedView {@link RemoteViews} of the child to add. * @param index The position at which to add the child. * * @hide diff --git a/core/java/android/widget/SmartSelectSprite.java b/core/java/android/widget/SmartSelectSprite.java index 45466c225e14..8d06f5fdfaf2 100644 --- a/core/java/android/widget/SmartSelectSprite.java +++ b/core/java/android/widget/SmartSelectSprite.java @@ -44,11 +44,8 @@ import android.view.animation.Interpolator; import java.lang.annotation.Retention; import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Set; -import java.util.Stack; /** * A utility class for creating and animating the Smart Select animation. @@ -59,7 +56,6 @@ final class SmartSelectSprite { private static final int EXPAND_DURATION = 300; private static final int CORNER_DURATION = 150; private static final float STROKE_WIDTH_DP = 1.5F; - private static final int POINTS_PER_LINE = 4; // GBLUE700 @ColorInt @@ -73,40 +69,13 @@ final class SmartSelectSprite { private Animator mActiveAnimator = null; @ColorInt private final int mStrokeColor; - private Set<Drawable> mExistingAnimationDrawables = new HashSet<>(); static final Comparator<RectF> RECTANGLE_COMPARATOR = Comparator .<RectF>comparingDouble(e -> e.bottom) .thenComparingDouble(e -> e.left); - /** - * Represents a set of points connected by lines. - */ - private static final class PolygonShape extends Shape { - - private final float[] mLineCoordinates; - - private PolygonShape(final List<PointF> points) { - mLineCoordinates = new float[points.size() * POINTS_PER_LINE]; - - int index = 0; - PointF currentPoint = points.get(0); - for (final PointF nextPoint : points) { - mLineCoordinates[index] = currentPoint.x; - mLineCoordinates[index + 1] = currentPoint.y; - mLineCoordinates[index + 2] = nextPoint.x; - mLineCoordinates[index + 3] = nextPoint.y; - - index += POINTS_PER_LINE; - currentPoint = nextPoint; - } - } - - @Override - public void draw(Canvas canvas, Paint paint) { - canvas.drawLines(mLineCoordinates, paint); - } - } + private Drawable mExistingDrawable = null; + private RectangleList mExistingRectangleList = null; /** * A rounded rectangle with a configurable corner radius and the ability to expand outside of @@ -262,16 +231,27 @@ final class SmartSelectSprite { */ private static final class RectangleList extends Shape { + @Retention(SOURCE) + @IntDef({DisplayType.RECTANGLES, DisplayType.POLYGON}) + private @interface DisplayType { + int RECTANGLES = 0; + int POLYGON = 1; + } + private static final String PROPERTY_RIGHT_BOUNDARY = "rightBoundary"; private static final String PROPERTY_LEFT_BOUNDARY = "leftBoundary"; private final List<RoundedRectangleShape> mRectangles; private final List<RoundedRectangleShape> mReversedRectangles; - private RectangleList(List<RoundedRectangleShape> rectangles) { + private final Path mOutlinePolygonPath; + private @DisplayType int mDisplayType = DisplayType.RECTANGLES; + + private RectangleList(final List<RoundedRectangleShape> rectangles) { mRectangles = new LinkedList<>(rectangles); mReversedRectangles = new LinkedList<>(rectangles); Collections.reverse(mReversedRectangles); + mOutlinePolygonPath = generateOutlinePolygonPath(rectangles); } private void setLeftBoundary(final float leftBoundary) { @@ -307,6 +287,10 @@ final class SmartSelectSprite { } } + void setDisplayType(@DisplayType int displayType) { + mDisplayType = displayType; + } + private int getTotalWidth() { int sum = 0; for (RoundedRectangleShape rectangle : mRectangles) { @@ -317,11 +301,34 @@ final class SmartSelectSprite { @Override public void draw(Canvas canvas, Paint paint) { + if (mDisplayType == DisplayType.POLYGON) { + drawPolygon(canvas, paint); + } else { + drawRectangles(canvas, paint); + } + } + + private void drawRectangles(final Canvas canvas, final Paint paint) { for (RoundedRectangleShape rectangle : mRectangles) { rectangle.draw(canvas, paint); } } + private void drawPolygon(final Canvas canvas, final Paint paint) { + canvas.drawPath(mOutlinePolygonPath, paint); + } + + private static Path generateOutlinePolygonPath( + final List<RoundedRectangleShape> rectangles) { + final Path path = new Path(); + for (final RoundedRectangleShape shape : rectangles) { + final Path rectanglePath = new Path(); + rectanglePath.addRect(shape.mBoundingRectangle, Path.Direction.CW); + path.op(rectanglePath, Path.Op.UNION); + } + return path; + } + } SmartSelectSprite(final View view) { @@ -337,84 +344,6 @@ final class SmartSelectSprite { mView = view; } - private static boolean intersectsOrTouches(RectF a, RectF b) { - return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom; - } - - private List<Drawable> mergeRectanglesToPolygonShape( - final List<RectF> rectangles, - final int color) { - final List<Drawable> drawables = new LinkedList<>(); - final Set<List<PointF>> mergedPaths = calculateMergedPolygonPoints(rectangles); - - for (List<PointF> path : mergedPaths) { - // Add the starting point to the end of the polygon so that it ends up closed. - path.add(path.get(0)); - - final PolygonShape shape = new PolygonShape(path); - final ShapeDrawable drawable = new ShapeDrawable(shape); - - drawable.getPaint().setColor(color); - drawable.getPaint().setStyle(Paint.Style.STROKE); - drawable.getPaint().setStrokeWidth(mStrokeWidth); - - drawables.add(drawable); - } - - return drawables; - } - - private static Set<List<PointF>> calculateMergedPolygonPoints( - List<RectF> rectangles) { - final Set<List<RectF>> partitions = new HashSet<>(); - final LinkedList<RectF> listOfRects = new LinkedList<>(rectangles); - - while (!listOfRects.isEmpty()) { - final RectF candidate = listOfRects.removeFirst(); - final List<RectF> partition = new LinkedList<>(); - partition.add(candidate); - - final LinkedList<RectF> otherCandidates = new LinkedList<>(); - otherCandidates.addAll(listOfRects); - - while (!otherCandidates.isEmpty()) { - final RectF otherCandidate = otherCandidates.removeFirst(); - for (RectF partitionElement : partition) { - if (intersectsOrTouches(partitionElement, otherCandidate)) { - partition.add(otherCandidate); - listOfRects.remove(otherCandidate); - break; - } - } - } - - partition.sort(Comparator.comparing(o -> o.top)); - partitions.add(partition); - } - - final Set<List<PointF>> result = new HashSet<>(); - for (List<RectF> partition : partitions) { - final List<PointF> points = new LinkedList<>(); - - final Stack<RectF> rects = new Stack<>(); - for (RectF rect : partition) { - points.add(new PointF(rect.right, rect.top)); - points.add(new PointF(rect.right, rect.bottom)); - rects.add(rect); - } - while (!rects.isEmpty()) { - final RectF rect = rects.pop(); - points.add(new PointF(rect.left, rect.bottom)); - points.add(new PointF(rect.left, rect.top)); - } - - result.add(points); - } - - return result; - - } - /** * Performs the Smart Select animation on the view bound to this SmartSelectSprite. * @@ -490,17 +419,17 @@ final class SmartSelectSprite { paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(mStrokeWidth); - addToOverlay(shapeDrawable); + mExistingRectangleList = rectangleList; + mExistingDrawable = shapeDrawable; + mView.getOverlay().add(shapeDrawable); - mActiveAnimator = createAnimator(mStrokeColor, destinationRectangles, rectangleList, - startingOffsetLeft, startingOffsetRight, cornerAnimators, updateListener, + mActiveAnimator = createAnimator(rectangleList, startingOffsetLeft, startingOffsetRight, + cornerAnimators, updateListener, onAnimationEnd); mActiveAnimator.start(); } private Animator createAnimator( - final @ColorInt int color, - final List<RectF> destinationRectangles, final RectangleList rectangleList, final float startingOffsetLeft, final float startingOffsetRight, @@ -537,15 +466,12 @@ final class SmartSelectSprite { final AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playSequentially(boundaryAnimator, cornerAnimator); - setUpAnimatorListener(animatorSet, destinationRectangles, color, onAnimationEnd); + setUpAnimatorListener(animatorSet, onAnimationEnd); return animatorSet; } - private void setUpAnimatorListener(final Animator animator, - final List<RectF> destinationRectangles, - final @ColorInt int color, - final Runnable onAnimationEnd) { + private void setUpAnimatorListener(final Animator animator, final Runnable onAnimationEnd) { animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { @@ -553,15 +479,8 @@ final class SmartSelectSprite { @Override public void onAnimationEnd(Animator animator) { - removeExistingDrawables(); - - final List<Drawable> polygonShapes = mergeRectanglesToPolygonShape( - destinationRectangles, - color); - - for (Drawable drawable : polygonShapes) { - addToOverlay(drawable); - } + mExistingRectangleList.setDisplayType(RectangleList.DisplayType.POLYGON); + mExistingDrawable.invalidateSelf(); onAnimationEnd.run(); } @@ -661,17 +580,12 @@ final class SmartSelectSprite { && y <= rectangle.bottom; } - private void addToOverlay(final Drawable drawable) { - mView.getOverlay().add(drawable); - mExistingAnimationDrawables.add(drawable); - } - private void removeExistingDrawables() { final ViewOverlay overlay = mView.getOverlay(); - for (Drawable drawable : mExistingAnimationDrawables) { - overlay.remove(drawable); - } - mExistingAnimationDrawables.clear(); + overlay.remove(mExistingDrawable); + + mExistingDrawable = null; + mExistingRectangleList = null; } /** diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 80f4b99c8113..ceb06f511108 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1941,7 +1941,8 @@ public class ResolverActivity extends Activity { final int checkedPos = mAdapterView.getCheckedItemPosition(); final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; if (!useLayoutWithDefault() - && (!hasValidSelection || mLastSelected != checkedPos)) { + && (!hasValidSelection || mLastSelected != checkedPos) + && mAlwaysButton != null) { setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); mOnceButton.setEnabled(hasValidSelection); if (hasValidSelection) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index bd94fc7b2112..8ea0242b3549 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1807,7 +1807,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mFloatingActionMode.finish(); } cleanupFloatingActionModeViews(); - mFloatingToolbar = new FloatingToolbar(mContext, mWindow); + mFloatingToolbar = new FloatingToolbar(mWindow); final FloatingActionMode mode = new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); mFloatingActionModeOriginatingView = originatingView; diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index 1d56e1ad3e03..f63b5a213528 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -120,8 +120,10 @@ public final class FloatingToolbar { /** * Initializes a floating toolbar. */ - public FloatingToolbar(Context context, Window window) { - mContext = applyDefaultTheme(Preconditions.checkNotNull(context)); + public FloatingToolbar(Window window) { + // TODO(b/65172902): Pass context in constructor when DecorView (and other callers) + // supports multi-display. + mContext = applyDefaultTheme(window.getContext()); mWindow = Preconditions.checkNotNull(window); mPopup = new FloatingToolbarPopup(mContext, window.getDecorView()); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index c62934100540..d63e22c189f8 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -103,6 +103,7 @@ cc_library_shared { "android_nio_utils.cpp", "android_util_AssetManager.cpp", "android_util_Binder.cpp", + "android_util_StatsLog.cpp", "android_util_EventLog.cpp", "android_util_MemoryIntArray.cpp", "android_util_Log.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 5afd06750601..02c9848ea149 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -111,6 +111,7 @@ namespace android { extern int register_android_app_admin_SecurityLog(JNIEnv* env); extern int register_android_content_AssetManager(JNIEnv* env); extern int register_android_util_EventLog(JNIEnv* env); +extern int register_android_util_StatsLog(JNIEnv* env); extern int register_android_util_Log(JNIEnv* env); extern int register_android_util_MemoryIntArray(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); @@ -1311,6 +1312,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit), REG_JNI(register_android_os_SystemClock), REG_JNI(register_android_util_EventLog), + REG_JNI(register_android_util_StatsLog), REG_JNI(register_android_util_Log), REG_JNI(register_android_util_MemoryIntArray), REG_JNI(register_android_util_PathParser), diff --git a/core/jni/android_util_StatsLog.cpp b/core/jni/android_util_StatsLog.cpp new file mode 100644 index 000000000000..c992365094f7 --- /dev/null +++ b/core/jni/android_util_StatsLog.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2007-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. + */ + +#include <fcntl.h> +#include <log/log_event_list.h> + +#include <log/log.h> + +#include <nativehelper/JNIHelp.h> +#include "core_jni_helpers.h" +#include "jni.h" + +#define UNUSED __attribute__((__unused__)) + +namespace android { + +static jclass gCollectionClass; +static jmethodID gCollectionAddID; + +static jclass gIntegerClass; +static jfieldID gIntegerValueID; + +static jclass gLongClass; +static jfieldID gLongValueID; + +static jclass gFloatClass; +static jfieldID gFloatValueID; + +static jclass gStringClass; + +/* + * In class android.util.StatsLog: + * static native int writeInt(int tag, int value) + */ +static jint android_util_StatsLog_write_Integer(JNIEnv* env UNUSED, + jobject clazz UNUSED, + jint tag, jint value) +{ + android_log_event_list ctx(tag); + ctx << (int32_t)value; + return ctx.write(LOG_ID_STATS); +} + +/* + * In class android.util.StatsLog: + * static native int writeLong(long tag, long value) + */ +static jint android_util_StatsLog_write_Long(JNIEnv* env UNUSED, + jobject clazz UNUSED, + jint tag, jlong value) +{ + android_log_event_list ctx(tag); + ctx << (int64_t)value; + return ctx.write(LOG_ID_STATS); +} + +/* + * In class android.util.StatsLog: + * static native int writeFloat(long tag, float value) + */ +static jint android_util_StatsLog_write_Float(JNIEnv* env UNUSED, + jobject clazz UNUSED, + jint tag, jfloat value) +{ + android_log_event_list ctx(tag); + ctx << (float)value; + return ctx.write(LOG_ID_STATS); +} + +/* + * In class android.util.StatsLog: + * static native int writeString(int tag, String value) + */ +static jint android_util_StatsLog_write_String(JNIEnv* env, + jobject clazz UNUSED, + jint tag, jstring value) { + android_log_event_list ctx(tag); + // Don't throw NPE -- I feel like it's sort of mean for a logging function + // to be all crashy if you pass in NULL -- but make the NULL value explicit. + if (value != NULL) { + const char *str = env->GetStringUTFChars(value, NULL); + ctx << str; + env->ReleaseStringUTFChars(value, str); + } else { + ctx << "NULL"; + } + return ctx.write(LOG_ID_STATS); +} + +/* + * In class android.util.StatsLog: + * static native int writeArray(long tag, Object... value) + */ +static jint android_util_StatsLog_write_Array(JNIEnv* env, jobject clazz, + jint tag, jobjectArray value) { + android_log_event_list ctx(tag); + + if (value == NULL) { + ctx << "[NULL]"; + return ctx.write(LOG_ID_STATS); + } + + jsize copied = 0, num = env->GetArrayLength(value); + for (; copied < num && copied < 255; ++copied) { + if (ctx.status()) break; + jobject item = env->GetObjectArrayElement(value, copied); + if (item == NULL) { + ctx << "NULL"; + } else if (env->IsInstanceOf(item, gStringClass)) { + const char *str = env->GetStringUTFChars((jstring) item, NULL); + ctx << str; + env->ReleaseStringUTFChars((jstring) item, str); + } else if (env->IsInstanceOf(item, gIntegerClass)) { + ctx << (int32_t)env->GetIntField(item, gIntegerValueID); + } else if (env->IsInstanceOf(item, gLongClass)) { + ctx << (int64_t)env->GetLongField(item, gLongValueID); + } else if (env->IsInstanceOf(item, gFloatClass)) { + ctx << (float)env->GetFloatField(item, gFloatValueID); + } else { + jniThrowException(env, + "java/lang/IllegalArgumentException", + "Invalid payload item type"); + return -1; + } + env->DeleteLocalRef(item); + } + return ctx.write(LOG_ID_STATS); +} + +/* + * JNI registration. + */ +static const JNINativeMethod gRegisterMethods[] = { + /* name, signature, funcPtr */ + { "writeInt", "(II)I", (void*) android_util_StatsLog_write_Integer }, + { "writeLong", "(IJ)I", (void*) android_util_StatsLog_write_Long }, + { "writeFloat", "(IF)I", (void*) android_util_StatsLog_write_Float }, + { "writeString", + "(ILjava/lang/String;)I", + (void*) android_util_StatsLog_write_String + }, + { "writeArray", + "(I[Ljava/lang/Object;)I", + (void*) android_util_StatsLog_write_Array + }, +}; + +static struct { const char *name; jclass *clazz; } gClasses[] = { + { "java/lang/Integer", &gIntegerClass }, + { "java/lang/Long", &gLongClass }, + { "java/lang/Float", &gFloatClass }, + { "java/lang/String", &gStringClass }, + { "java/util/Collection", &gCollectionClass }, +}; + +static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = { + { &gIntegerClass, "value", "I", &gIntegerValueID }, + { &gLongClass, "value", "J", &gLongValueID }, + { &gFloatClass, "value", "F", &gFloatValueID }, +}; + +static struct { jclass *c; const char *name, *mt; jmethodID *id; } gMethods[] = { + { &gCollectionClass, "add", "(Ljava/lang/Object;)Z", &gCollectionAddID }, +}; + +int register_android_util_StatsLog(JNIEnv* env) { + for (int i = 0; i < NELEM(gClasses); ++i) { + jclass clazz = FindClassOrDie(env, gClasses[i].name); + *gClasses[i].clazz = MakeGlobalRefOrDie(env, clazz); + } + + for (int i = 0; i < NELEM(gFields); ++i) { + *gFields[i].id = GetFieldIDOrDie(env, + *gFields[i].c, gFields[i].name, gFields[i].ft); + } + + for (int i = 0; i < NELEM(gMethods); ++i) { + *gMethods[i].id = GetMethodIDOrDie(env, + *gMethods[i].c, gMethods[i].name, gMethods[i].mt); + } + + return RegisterMethodsOrDie( + env, + "android/util/StatsLog", + gRegisterMethods, NELEM(gRegisterMethods)); +} + +}; // namespace android diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 7e9f561e68d5..f5b350b053d4 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -181,7 +181,7 @@ <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052|9963" /> + <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052|9963|76551" /> <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288" /> diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index b41e892adae9..8a57ea9a9c13 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -99,6 +99,7 @@ public class SettingsBackupTest { Settings.Global.ALARM_MANAGER_CONSTANTS, Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, Settings.Global.ALWAYS_FINISH_ACTIVITIES, + Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, Settings.Global.ANIMATOR_DURATION_SCALE, Settings.Global.ANOMALY_DETECTION_CONSTANTS, Settings.Global.APN_DB_UPDATE_CONTENT_URL, diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java index bdd828fd5127..c4bf9d3123bf 100644 --- a/graphics/java/android/graphics/Color.java +++ b/graphics/java/android/graphics/Color.java @@ -73,7 +73,7 @@ import java.util.function.DoubleUnaryOperator; * <h4>Encoding</h4> * <p>The four components of a color int are encoded in the following way:</p> * <pre class="prettyprint"> - * int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 16 | (B & 0xff); + * int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff); * </pre> * * <p>Because of this encoding, color ints can easily be described as an integer diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index 4e59baa48983..3c3b3177159b 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -216,10 +216,7 @@ void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* pa .setTransform(Matrix4::identity(), TransformFlags::None) .setModelViewIdentityEmptyBounds() .build(); - // Disable blending if this is the first draw to the main framebuffer, in case app has defined - // transparency where it doesn't make sense - as first draw in opaque window. - bool overrideDisableBlending = !mHasDrawn && mOpaque && !mRenderTarget.frameBufferId; - mRenderState.render(glop, mRenderTarget.orthoMatrix, overrideDisableBlending); + mRenderState.render(glop, mRenderTarget.orthoMatrix, false); mHasDrawn = true; } @@ -350,8 +347,14 @@ void BakedOpRenderer::renderGlopImpl(const Rect* dirtyBounds, const ClipBase* cl const Glop& glop) { prepareRender(dirtyBounds, clip); // Disable blending if this is the first draw to the main framebuffer, in case app has defined - // transparency where it doesn't make sense - as first draw in opaque window. - bool overrideDisableBlending = !mHasDrawn && mOpaque && !mRenderTarget.frameBufferId; + // transparency where it doesn't make sense - as first draw in opaque window. Note that we only + // apply this improvement when the blend mode is SRC_OVER - other modes (e.g. CLEAR) can be + // valid draws that affect other content (e.g. draw CLEAR, then draw DST_OVER) + bool overrideDisableBlending = !mHasDrawn + && mOpaque + && !mRenderTarget.frameBufferId + && glop.blend.src == GL_ONE + && glop.blend.dst == GL_ONE_MINUS_SRC_ALPHA; mRenderState.render(glop, mRenderTarget.orthoMatrix, overrideDisableBlending); if (!mRenderTarget.frameBufferId) mHasDrawn = true; } diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index f4ce864e83e1..e0373cae9923 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -511,25 +511,19 @@ void Tree::updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* } } if (!canReuseSurface || mCache.dirty) { - draw(surface.get(), dst); + if (surface) { + Bitmap& bitmap = getBitmapUpdateIfDirty(); + SkBitmap skiaBitmap; + bitmap.getSkBitmap(&skiaBitmap); + if (!surface->getCanvas()->writePixels(skiaBitmap, dst.fLeft, dst.fTop)) { + ALOGD("VectorDrawable caching failed to efficiently upload"); + surface->getCanvas()->drawBitmap(skiaBitmap, dst.fLeft, dst.fTop); + } + } mCache.dirty = false; } } -void Tree::draw(SkSurface* surface, const SkRect& dst) { - if (surface) { - SkCanvas* canvas = surface->getCanvas(); - float scaleX = dst.width() / mProperties.getViewportWidth(); - float scaleY = dst.height() / mProperties.getViewportHeight(); - SkAutoCanvasRestore acr(canvas, true); - canvas->translate(dst.fLeft, dst.fTop); - canvas->clipRect(SkRect::MakeWH(dst.width(), dst.height())); - canvas->clear(SK_ColorTRANSPARENT); - canvas->scale(scaleX, scaleY); - mRootNode->draw(canvas, false); - } -} - void Tree::Cache::setAtlas(sp<skiapipeline::VectorDrawableAtlas> newAtlas, skiapipeline::AtlasKey newAtlasKey) { LOG_ALWAYS_FATAL_IF(newAtlasKey == INVALID_ATLAS_KEY); @@ -570,22 +564,15 @@ void Tree::draw(SkCanvas* canvas) { // Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure. // We render the VD into a temporary standalone buffer and mark the frame as dirty. Next // frame will be cached into the atlas. + Bitmap& bitmap = getBitmapUpdateIfDirty(); + SkBitmap skiaBitmap; + bitmap.getSkBitmap(&skiaBitmap); + int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); - SkRect src = SkRect::MakeWH(scaledWidth, scaledHeight); -#ifndef ANDROID_ENABLE_LINEAR_BLENDING - sk_sp<SkColorSpace> colorSpace = nullptr; -#else - sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); -#endif - SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, kPremul_SkAlphaType, - colorSpace); - sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(canvas->getGrContext(), - SkBudgeted::kYes, info); - draw(surface.get(), src); + canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), + mutateProperties()->getBounds(), getPaint(), SkCanvas::kFast_SrcRectConstraint); mCache.clear(); - canvas->drawImageRect(surface->makeImageSnapshot().get(), mutateProperties()->getBounds(), - getPaint(), SkCanvas::kFast_SrcRectConstraint); markDirty(); } } diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index efbb695a14dd..10d3e05c067f 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -738,11 +738,6 @@ private: bool canReuseBitmap(Bitmap*, int width, int height); void updateBitmapCache(Bitmap& outCache, bool useStagingData); - /** - * Draws the root node into "surface" at a given "dst" position. - */ - void draw(SkSurface* surface, const SkRect& dst); - // Cap the bitmap size, such that it won't hurt the performance too much // and it won't crash due to a very large scale. // The drawable will look blurry above this size. diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index a8463ecc44d8..742f14d04db4 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -118,6 +118,8 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, return; } + ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(), bounds.height()); + layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false; layerCanvas->clear(SK_ColorTRANSPARENT); @@ -143,7 +145,6 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, } SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); - // TODO: Handle wide color gamut requests node->setLayerSurface( SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, info, 0, &props)); @@ -194,10 +195,10 @@ void SkiaPipeline::renderVectorDrawableCache() { sp<VectorDrawableAtlas> atlas = mRenderThread.cacheManager().acquireVectorDrawableAtlas(); auto grContext = mRenderThread.getGrContext(); atlas->prepareForDraw(grContext); + ATRACE_NAME("Update VectorDrawables"); for (auto vd : mVectorDrawables) { vd->updateCache(atlas, grContext); } - grContext->flush(); mVectorDrawables.clear(); } } diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp index 23969908ff4d..9c9e17d600bf 100644 --- a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp +++ b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp @@ -270,7 +270,10 @@ sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrCon sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); #endif SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); - return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info); + // This must have a top-left origin so that calls to surface->canvas->writePixels + // performs a basic texture upload instead of a more complex drawing operation + return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 0, + kTopLeft_GrSurfaceOrigin, nullptr); } void VectorDrawableAtlas::setStorageMode(StorageMode mode) { diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 3308fc929b03..dc7fa8c00f82 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -28,6 +28,7 @@ interface IMediaRouterService { MediaRouterClientState getState(IMediaRouterClient client); boolean isPlaybackActive(IMediaRouterClient client); + boolean isGlobalBluetoothA2doOn(); void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan); void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 29b88a28294c..2894e8956c1c 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -88,7 +88,6 @@ public class MediaRouter { RouteInfo mBluetoothA2dpRoute; RouteInfo mSelectedRoute; - RouteInfo mSystemAudioRoute; final boolean mCanConfigureWifiDisplays; boolean mActivelyScanningWifiDisplays; @@ -150,7 +149,6 @@ public class MediaRouter { } addRouteStatic(mDefaultAudioVideo); - mSystemAudioRoute = mDefaultAudioVideo; // This will select the active wifi display route if there is one. updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus()); @@ -185,7 +183,7 @@ public class MediaRouter { } void updateAudioRoutes(AudioRoutesInfo newRoutes) { - boolean updated = false; + boolean audioRoutesChanged = false; if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) { mCurAudioRoutesInfo.mainType = newRoutes.mainType; int name; @@ -201,11 +199,10 @@ public class MediaRouter { } mDefaultAudioVideo.mNameResId = name; dispatchRouteChanged(mDefaultAudioVideo); - updated = true; + audioRoutesChanged = true; } final int mainType = mCurAudioRoutesInfo.mainType; - if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; if (mCurAudioRoutesInfo.bluetoothName != null) { @@ -219,8 +216,6 @@ public class MediaRouter { info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH; mBluetoothA2dpRoute = info; addRouteStatic(mBluetoothA2dpRoute); - mSystemAudioRoute = mBluetoothA2dpRoute; - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false); } else { mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName; dispatchRouteChanged(mBluetoothA2dpRoute); @@ -229,30 +224,32 @@ public class MediaRouter { // BT disconnected removeRouteStatic(mBluetoothA2dpRoute); mBluetoothA2dpRoute = null; - mSystemAudioRoute = mDefaultAudioVideo; - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false); - } - updated = true; - } - - if (mBluetoothA2dpRoute != null) { - final boolean a2dpEnabled = isBluetoothA2dpOn(); - if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) { - // A2DP off - mSystemAudioRoute = mDefaultAudioVideo; - updated = true; - } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) && - a2dpEnabled) { - // A2DP on or BT connected - mSystemAudioRoute = mBluetoothA2dpRoute; - updated = true; } + audioRoutesChanged = true; } - if (updated) { + + if (audioRoutesChanged) { + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, getDefaultSystemAudioRoute(), false); Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn()); } } + RouteInfo getDefaultSystemAudioRoute() { + boolean globalBluetoothA2doOn = false; + try { + globalBluetoothA2doOn = mMediaRouterService.isGlobalBluetoothA2doOn(); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to call isSystemBluetoothA2doOn.", ex); + } + return (globalBluetoothA2doOn && mBluetoothA2dpRoute != null) + ? mBluetoothA2dpRoute : mDefaultAudioVideo; + } + + RouteInfo getCurrentSystemAudioRoute() { + return (isBluetoothA2dpOn() && mBluetoothA2dpRoute != null) + ? mBluetoothA2dpRoute : mDefaultAudioVideo; + } + boolean isBluetoothA2dpOn() { try { return mAudioService.isBluetoothA2dpOn(); @@ -603,15 +600,13 @@ public class MediaRouter { @Override public void onRestoreRoute() { + // Skip restoring route if the selected route is not a system audio route, or + // MediaRouter is initializing. if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute) - || mSelectedRoute == mSystemAudioRoute) { + || mSelectedRoute == null) { return; } - try { - sStatic.mAudioService.setBluetoothA2dpOn(mSelectedRoute == mBluetoothA2dpRoute); - } catch (RemoteException e) { - Log.e(TAG, "Error changing Bluetooth A2DP state", e); - } + mSelectedRoute.select(); } } } @@ -946,7 +941,7 @@ public class MediaRouter { boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo || oldRoute == sStatic.mBluetoothA2dpRoute); if (oldRoute == route - && (!wasDefaultOrBluetoothRoute || oldRoute == sStatic.mSystemAudioRoute)) { + && (!wasDefaultOrBluetoothRoute || route == sStatic.getCurrentSystemAudioRoute())) { return; } if (!route.matchesTypes(types)) { diff --git a/packages/SystemUI/res/drawable/car_qs_background_primary.xml b/packages/SystemUI/res/drawable/car_qs_background_primary.xml deleted file mode 100644 index 0f77987bb7ce..000000000000 --- a/packages/SystemUI/res/drawable/car_qs_background_primary.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?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. ---> -<inset xmlns:android="http://schemas.android.com/apk/res/android"> - <shape> - <solid android:color="?android:attr/colorPrimaryDark"/> - </shape> -</inset> diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml index deb494fdefbc..59c0957d98cb 100644 --- a/packages/SystemUI/res/layout/battery_percentage_view.xml +++ b/packages/SystemUI/res/layout/battery_percentage_view.xml @@ -26,4 +26,5 @@ android:textColor="?android:attr/textColorPrimary" android:gravity="center_vertical|start" android:paddingEnd="@dimen/battery_level_padding_start" + android:importantForAccessibility="no" /> diff --git a/packages/SystemUI/res/layout/car_qs_panel.xml b/packages/SystemUI/res/layout/car_qs_panel.xml index 0b46b0bdaa1c..4cb0fd5fecfb 100644 --- a/packages/SystemUI/res/layout/car_qs_panel.xml +++ b/packages/SystemUI/res/layout/car_qs_panel.xml @@ -18,12 +18,13 @@ android:id="@+id/quick_settings_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/car_qs_background_primary" + android:background="@color/car_qs_background_primary" android:orientation="vertical" - android:elevation="4dp"> + android:elevation="4dp" + android:theme="@android:style/Theme"> - <include layout="@layout/car_status_bar_header" /> - <include layout="@layout/car_qs_footer" /> + <include layout="@layout/car_status_bar_header"/> + <include layout="@layout/car_qs_footer"/> <com.android.systemui.statusbar.car.UserGridView android:id="@+id/user_grid" diff --git a/packages/SystemUI/res/values/colors_car.xml b/packages/SystemUI/res/values/colors_car.xml index 9593fe51917c..1b8c2fa68244 100644 --- a/packages/SystemUI/res/values/colors_car.xml +++ b/packages/SystemUI/res/values/colors_car.xml @@ -17,6 +17,7 @@ */ --> <resources> + <color name="car_qs_background_primary">#263238</color> <!-- Blue Gray 900 --> <color name="car_user_switcher_progress_bgcolor">#00000000</color> <!-- Transparent --> <color name="car_user_switcher_progress_fgcolor">#80CBC4</color> <!-- Teal 200 --> <color name="car_user_switcher_no_user_image_bgcolor">#FAFAFA</color> <!-- Grey 50 --> diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java new file mode 100644 index 000000000000..d1d180819eef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java @@ -0,0 +1,122 @@ +/* + * 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.doze; + +import android.content.Context; +import android.content.res.Resources; +import android.provider.Settings; +import android.text.format.DateUtils; +import android.util.KeyValueListParser; +import android.util.Log; + +import com.android.systemui.R; + +import java.util.Arrays; + +/** + * Class to store the policy for AOD, which comes from + * {@link android.provider.Settings.Global} + */ +public class AlwaysOnDisplayPolicy { + public static final String TAG = "AlwaysOnDisplayPolicy"; + + static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array"; + static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array"; + static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay"; + static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger"; + static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period"; + + /** + * Integer array to map ambient brightness type to real screen brightness. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_SCREEN_BRIGHTNESS_ARRAY + */ + public final int[] screenBrightnessArray; + + /** + * Integer array to map ambient brightness type to dimming scrim. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_DIMMING_SCRIM_ARRAY + */ + public final int[] dimmingScrimArray; + + /** + * Delay time(ms) from covering the prox to turning off the screen. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_PROX_SCREEN_OFF_DELAY_MS + */ + public final long proxScreenOffDelayMs; + + /** + * The threshold time(ms) to trigger the cooldown timer, which will + * turn off prox sensor for a period. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_PROX_COOLDOWN_TRIGGER_MS + */ + public final long proxCooldownTriggerMs; + + /** + * The period(ms) to turning off the prox sensor if + * {@link #KEY_PROX_COOLDOWN_TRIGGER_MS} is triggered. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_PROX_COOLDOWN_PERIOD_MS + */ + public final long proxCooldownPeriodMs; + + private final KeyValueListParser mParser; + + public AlwaysOnDisplayPolicy(Context context) { + final Resources resources = context.getResources(); + mParser = new KeyValueListParser(','); + + final String value = Settings.Global.getString(context.getContentResolver(), + Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS); + + try { + mParser.setString(value); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Bad AOD constants"); + } + + proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS, + 10 * DateUtils.MINUTE_IN_MILLIS); + proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS, + 2 * DateUtils.MINUTE_IN_MILLIS); + proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS, + 5 * DateUtils.MINUTE_IN_MILLIS); + screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY, + resources.getIntArray(R.array.config_doze_brightness_sensor_to_brightness)); + dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY, + resources.getIntArray(R.array.config_doze_brightness_sensor_to_scrim_opacity)); + } + + private int[] parseIntArray(final String key, final int[] defaultArray) { + final String value = mParser.getString(key, null); + if (value != null) { + return Arrays.stream(value.split(":")).map(String::trim).mapToInt( + Integer::parseInt).toArray(); + } else { + return defaultArray; + } + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index d374d68a456b..6f8bcff16a83 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -59,7 +59,7 @@ public class DozeFactory { DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock); machine.setParts(new DozeMachine.Part[]{ - new DozePauser(handler, machine, alarmManager), + new DozePauser(handler, machine, alarmManager, new AlwaysOnDisplayPolicy(context)), new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)), createDozeTriggers(context, sensorManager, host, alarmManager, config, params, handler, wakeLock, machine), @@ -76,7 +76,8 @@ public class DozeFactory { Handler handler) { Sensor sensor = DozeSensors.findSensorWithType(sensorManager, context.getString(R.string.doze_brightness_sensor_type)); - return new DozeScreenBrightness(context, service, sensorManager, sensor, host, handler); + return new DozeScreenBrightness(context, service, sensorManager, sensor, host, handler, + new AlwaysOnDisplayPolicy(context)); } private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java index a33b454c6430..76a190213ba3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java @@ -26,20 +26,22 @@ import com.android.systemui.util.AlarmTimeout; */ public class DozePauser implements DozeMachine.Part { public static final String TAG = DozePauser.class.getSimpleName(); - private static final long TIMEOUT = 10 * 1000; private final AlarmTimeout mPauseTimeout; private final DozeMachine mMachine; + private final long mTimeoutMs; - public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager) { + public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager, + AlwaysOnDisplayPolicy policy) { mMachine = machine; mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler); + mTimeoutMs = policy.proxScreenOffDelayMs; } @Override public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case DOZE_AOD_PAUSING: - mPauseTimeout.schedule(TIMEOUT, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); + mPauseTimeout.schedule(mTimeoutMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); break; default: mPauseTimeout.cancel(); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 30420529df56..11b4b0ef8294 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -42,7 +42,7 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen public DozeScreenBrightness(Context context, DozeMachine.Service service, SensorManager sensorManager, Sensor lightSensor, DozeHost host, - Handler handler) { + Handler handler, AlwaysOnDisplayPolicy policy) { mContext = context; mDozeService = service; mSensorManager = sensorManager; @@ -50,10 +50,8 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen mDozeHost = host; mHandler = handler; - mSensorToBrightness = context.getResources().getIntArray( - R.array.config_doze_brightness_sensor_to_brightness); - mSensorToScrimOpacity = context.getResources().getIntArray( - R.array.config_doze_brightness_sensor_to_scrim_opacity); + mSensorToBrightness = policy.screenBrightnessArray; + mSensorToScrimOpacity = policy.dimmingScrimArray; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 566353c74b57..91cde378c41b 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -72,7 +72,7 @@ public class DozeSensors { public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, - Consumer<Boolean> proxCallback) { + Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) { mContext = context; mAlarmManager = alarmManager; mSensorManager = sensorManager; @@ -112,7 +112,7 @@ public class DozeSensors { true /* touchscreen */), }; - mProxSensor = new ProxSensor(); + mProxSensor = new ProxSensor(policy); mCallback = callback; } @@ -206,17 +206,16 @@ public class DozeSensors { private class ProxSensor implements SensorEventListener { - static final long COOLDOWN_TRIGGER = 2 * 1000; - static final long COOLDOWN_PERIOD = 5 * 1000; - boolean mRequested; boolean mRegistered; Boolean mCurrentlyFar; long mLastNear; final AlarmTimeout mCooldownTimer; + final AlwaysOnDisplayPolicy mPolicy; - public ProxSensor() { + public ProxSensor(AlwaysOnDisplayPolicy policy) { + mPolicy = policy; mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered, "prox_cooldown", mHandler); } @@ -264,11 +263,12 @@ public class DozeSensors { // Sensor has been unregistered by the proxCallback. Do nothing. } else if (!mCurrentlyFar) { mLastNear = now; - } else if (mCurrentlyFar && now - mLastNear < COOLDOWN_TRIGGER) { + } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) { // If the last near was very recent, we might be using more power for prox // wakeups than we're saving from turning of the screen. Instead, turn it off // for a while. - mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); + mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs, + AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); updateRegistered(); } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 45831601a0f2..f7a258a2c959 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -84,7 +84,8 @@ public class DozeTriggers implements DozeMachine.Part { mWakeLock = wakeLock; mAllowPulseTriggers = allowPulseTriggers; mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters, - config, wakeLock, this::onSensor, this::onProximityFar); + config, wakeLock, this::onSensor, this::onProximityFar, + new AlwaysOnDisplayPolicy(context)); mUiModeManager = mContext.getSystemService(UiModeManager.class); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index c2a7ed3fdad4..aecf95fc677f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -132,6 +132,11 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Preloads the next task RecentsConfiguration config = Recents.getConfiguration(); if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { + Rect windowRect = getWindowRect(null /* windowRectOverride */); + if (windowRect.isEmpty()) { + return; + } + // Load the next task only if we aren't svelte SystemServicesProxy ssp = Recents.getSystemServices(); ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask(); @@ -146,8 +151,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // This callback is made when a new activity is launched and the old one is // paused so ignore the current activity and try and preload the thumbnail for // the previous one. - updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack, - getWindowRect(null /* windowRectOverride */)); + updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack, windowRect); // Launched from app is always the worst case (in terms of how many // thumbnails/tasks visible) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 46f9c04aa42e..afe5c917a856 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -404,8 +404,8 @@ public abstract class PanelView extends FrameLayout { false /* collapseWhenFinished */); notifyBarPanelExpansionChanged(); if (mVibrateOnOpening && !isHapticFeedbackDisabled(mContext)) { - AsyncTask.execute( - () -> mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK))); + AsyncTask.execute(() -> + mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_TICK, false))); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 03f42a6f760d..d7f11f710501 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -422,7 +422,7 @@ public class StatusBarWindowView extends FrameLayout { mFloatingActionMode.finish(); } cleanupFloatingActionModeViews(); - mFloatingToolbar = new FloatingToolbar(mContext, mFakeWindow); + mFloatingToolbar = new FloatingToolbar(mFakeWindow); final FloatingActionMode mode = new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); mFloatingActionModeOriginatingView = originatingView; diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 5e71dd4684c5..27c16d53ce78 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -54,7 +54,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ SystemUI-proto \ SystemUI-tags \ legacy-android-test \ - testables + testables \ + truth-prebuilt \ LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java new file mode 100644 index 000000000000..abc2d0e5c845 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/AlwaysOnDisplayPolicyTest.java @@ -0,0 +1,86 @@ +/* + * 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.doze; + +import static com.google.common.truth.Truth.assertThat; + +import android.provider.Settings; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.text.format.DateUtils; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class AlwaysOnDisplayPolicyTest extends SysuiTestCase { + private static final String ALWAYS_ON_DISPLAY_CONSTANTS_VALUE = "prox_screen_off_delay=1000" + + ",prox_cooldown_trigger=2000" + + ",prox_cooldown_period=3000" + + ",screen_brightness_array=1:2:3:4:5" + + ",dimming_scrim_array=5:4:3:2:1"; + + private String mPreviousConfig; + + @Before + public void setUp() { + mPreviousConfig = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS); + } + + @After + public void tearDown() { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, mPreviousConfig); + } + + @Test + public void testPolicy_valueNull_containsDefaultValue() throws Exception { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, null); + + AlwaysOnDisplayPolicy policy = new AlwaysOnDisplayPolicy(mContext); + + assertThat(policy.proxScreenOffDelayMs).isEqualTo(10 * DateUtils.MINUTE_IN_MILLIS); + assertThat(policy.proxCooldownTriggerMs).isEqualTo(2 * DateUtils.MINUTE_IN_MILLIS); + assertThat(policy.proxCooldownPeriodMs).isEqualTo(5 * DateUtils.MINUTE_IN_MILLIS); + assertThat(policy.screenBrightnessArray).isEqualTo(mContext.getResources().getIntArray( + R.array.config_doze_brightness_sensor_to_brightness)); + assertThat(policy.dimmingScrimArray).isEqualTo(mContext.getResources().getIntArray( + R.array.config_doze_brightness_sensor_to_scrim_opacity)); + } + + @Test + public void testPolicy_valueNotNull_containsValue() throws Exception { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS, ALWAYS_ON_DISPLAY_CONSTANTS_VALUE); + + AlwaysOnDisplayPolicy policy = new AlwaysOnDisplayPolicy(mContext); + + assertThat(policy.proxScreenOffDelayMs).isEqualTo(1000); + assertThat(policy.proxCooldownTriggerMs).isEqualTo(2000); + assertThat(policy.proxCooldownPeriodMs).isEqualTo(3000); + assertThat(policy.screenBrightnessArray).isEqualTo(new int[]{1, 2, 3, 4, 5}); + assertThat(policy.dimmingScrimArray).isEqualTo(new int[]{5, 4, 3, 2, 1}); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index c2758068a4ed..46e1d5562714 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -60,7 +60,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { mSensorManager = new FakeSensorManager(mContext); mSensor = mSensorManager.getFakeLightSensor(); mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, - mSensor.getSensor(), mHostFake, null /* handler */); + mSensor.getSensor(), mHostFake, null /* handler */, + new AlwaysOnDisplayPolicy(mContext)); } @Test @@ -135,7 +136,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { @Test public void testNullSensor() throws Exception { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, - null /* sensor */, mHostFake, null /* handler */); + null /* sensor */, mHostFake, null /* handler */, + new AlwaysOnDisplayPolicy(mContext)); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 7690f6d2853c..8e782c0dd32b 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -1516,7 +1516,7 @@ message MetricsEvent { // OS: N ACTION_ZEN_ALLOW_LIGHTS = 264; - // OPEN: Settings > Notifications > [App] > Topic Notifications + // OPEN: Settings > Notifications > [App] > Channel Notifications // CATEGORY: SETTINGS // OS: N NOTIFICATION_TOPIC_NOTIFICATION = 265; @@ -4360,6 +4360,11 @@ message MetricsEvent { // CATEGORY: SETTINGS SETTINGS_FEATURE_FLAGS_DASHBOARD = 1156; + // OPEN: Settings > Notifications > [App] > Topic Notifications + // CATEGORY: SETTINGS + // OS: P + NOTIFICATION_CHANNEL_GROUP = 1157; + // Add new aosp constants above this line. // END OF AOSP CONSTANTS } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 7324b82351f6..c60647fada09 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -417,7 +417,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo final boolean triggerable = (mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0; mMagnificationGestureHandler = new MagnificationGestureHandler( - mContext, mAms, detectControlGestures, triggerable); + mContext, mAms.getMagnificationController(), + detectControlGestures, triggerable); addFirstEventHandler(mMagnificationGestureHandler); } diff --git a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java index bc761914caf2..abfdb683c04c 100644 --- a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java +++ b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java @@ -12,32 +12,27 @@ final class GestureUtils { /* cannot be instantiated */ } - public static boolean isTap(MotionEvent down, MotionEvent up, int tapTimeSlop, - int tapDistanceSlop, int actionIndex) { - return eventsWithinTimeAndDistanceSlop(down, up, tapTimeSlop, tapDistanceSlop, actionIndex); - } - public static boolean isMultiTap(MotionEvent firstUp, MotionEvent secondUp, - int multiTapTimeSlop, int multiTapDistanceSlop, int actionIndex) { + int multiTapTimeSlop, int multiTapDistanceSlop) { + if (firstUp == null || secondUp == null) return false; return eventsWithinTimeAndDistanceSlop(firstUp, secondUp, multiTapTimeSlop, - multiTapDistanceSlop, actionIndex); + multiTapDistanceSlop); } private static boolean eventsWithinTimeAndDistanceSlop(MotionEvent first, MotionEvent second, - int timeout, int distance, int actionIndex) { + int timeout, int distance) { if (isTimedOut(first, second, timeout)) { return false; } - final double deltaMove = computeDistance(first, second, actionIndex); + final double deltaMove = distance(first, second); if (deltaMove >= distance) { return false; } return true; } - public static double computeDistance(MotionEvent first, MotionEvent second, int pointerIndex) { - return MathUtils.dist(first.getX(pointerIndex), first.getY(pointerIndex), - second.getX(pointerIndex), second.getY(pointerIndex)); + public static double distance(MotionEvent first, MotionEvent second) { + return MathUtils.dist(first.getX(), first.getY(), second.getX(), second.getY()); } public static boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) { @@ -54,7 +49,6 @@ final class GestureUtils { /** * Determines whether a two pointer gesture is a dragging one. * - * @param event The event with the pointer data. * @return True if the gesture is a dragging one. */ public static boolean isDraggingGesture(float firstPtrDownX, float firstPtrDownY, diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java index caa74b9512d1..98b8e6b723ac 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java @@ -16,11 +16,6 @@ package com.android.server.accessibility; -import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.SomeArgs; -import com.android.server.LocalServices; - import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.BroadcastReceiver; @@ -42,6 +37,12 @@ import android.view.View; import android.view.WindowManagerInternal; import android.view.animation.DecelerateInterpolator; +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.SomeArgs; +import com.android.server.LocalServices; + import java.util.Locale; /** @@ -138,7 +139,7 @@ class MagnificationController implements Handler.Callback { private final WindowManagerInternal mWindowManager; // Flag indicating that we are registered with window manager. - private boolean mRegistered; + @VisibleForTesting boolean mRegistered; private boolean mUnregisterPending; @@ -148,9 +149,14 @@ class MagnificationController implements Handler.Callback { mHandler = new Handler(context.getMainLooper(), this); } - public MagnificationController(Context context, AccessibilityManagerService ams, Object lock, - Handler handler, WindowManagerInternal windowManagerInternal, - ValueAnimator valueAnimator, SettingsBridge settingsBridge) { + public MagnificationController( + Context context, + AccessibilityManagerService ams, + Object lock, + Handler handler, + WindowManagerInternal windowManagerInternal, + ValueAnimator valueAnimator, + SettingsBridge settingsBridge) { mHandler = handler; mWindowManager = windowManagerInternal; mMainThreadId = context.getMainLooper().getThread().getId(); @@ -672,8 +678,7 @@ class MagnificationController implements Handler.Callback { * Resets magnification if magnification and auto-update are both enabled. * * @param animate whether the animate the transition - * @return {@code true} if magnification was reset to the disabled state, - * {@code false} if magnification is still active + * @return whether was {@link #isMagnifying magnifying} */ boolean resetIfNeeded(boolean animate) { synchronized (mLock) { @@ -790,6 +795,19 @@ class MagnificationController implements Handler.Callback { return true; } + @Override + public String toString() { + return "MagnificationController{" + + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec + + ", mMagnificationRegion=" + mMagnificationRegion + + ", mMagnificationBounds=" + mMagnificationBounds + + ", mUserId=" + mUserId + + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify + + ", mRegistered=" + mRegistered + + ", mUnregisterPending=" + mUnregisterPending + + '}'; + } + /** * Class responsible for animating spec on the main thread and sending spec * updates to the window manager. diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index b1ac5891dafa..d6452f87d155 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -16,6 +16,21 @@ package com.android.server.accessibility; +import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_POINTER_DOWN; +import static android.view.MotionEvent.ACTION_POINTER_UP; +import static android.view.MotionEvent.ACTION_UP; + +import static com.android.server.accessibility.GestureUtils.distance; + +import static java.lang.Math.abs; +import static java.util.Arrays.asList; +import static java.util.Arrays.copyOfRange; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -27,7 +42,6 @@ import android.util.Slog; import android.util.TypedValue; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; @@ -37,6 +51,8 @@ import android.view.ScaleGestureDetector.OnScaleGestureListener; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; +import com.android.internal.annotations.VisibleForTesting; + /** * This class handles magnification in response to touch events. * @@ -85,91 +101,109 @@ import android.view.accessibility.AccessibilityEvent; * * 7. The magnification scale will be persisted in settings and in the cloud. */ +@SuppressWarnings("WeakerAccess") class MagnificationGestureHandler implements EventStreamTransformation { private static final String LOG_TAG = "MagnificationEventHandler"; - private static final boolean DEBUG_STATE_TRANSITIONS = false; - private static final boolean DEBUG_DETECTING = false; - private static final boolean DEBUG_PANNING = false; + private static final boolean DEBUG_ALL = false; + private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL; + private static final boolean DEBUG_DETECTING = false || DEBUG_ALL; + private static final boolean DEBUG_PANNING = false || DEBUG_ALL; - private static final int STATE_DELEGATING = 1; - private static final int STATE_DETECTING = 2; - private static final int STATE_VIEWPORT_DRAGGING = 3; - private static final int STATE_MAGNIFIED_INTERACTION = 4; + /** @see #handleMotionEventStateDelegating */ + @VisibleForTesting static final int STATE_DELEGATING = 1; + /** @see DetectingStateHandler */ + @VisibleForTesting static final int STATE_DETECTING = 2; + /** @see ViewportDraggingStateHandler */ + @VisibleForTesting static final int STATE_VIEWPORT_DRAGGING = 3; + /** @see PanningScalingStateHandler */ + @VisibleForTesting static final int STATE_PANNING_SCALING = 4; private static final float MIN_SCALE = 2.0f; private static final float MAX_SCALE = 5.0f; - private final MagnificationController mMagnificationController; - private final DetectingStateHandler mDetectingStateHandler; - private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler; - private final StateViewportDraggingHandler mStateViewportDraggingHandler; + @VisibleForTesting final MagnificationController mMagnificationController; + + @VisibleForTesting final DetectingStateHandler mDetectingStateHandler; + @VisibleForTesting final PanningScalingStateHandler mPanningScalingStateHandler; + @VisibleForTesting final ViewportDraggingStateHandler mViewportDraggingStateHandler; private final ScreenStateReceiver mScreenStateReceiver; - private final boolean mDetectTripleTap; - private final boolean mTriggerable; + /** + * {@code true} if this detector should detect and respond to triple-tap + * gestures for engaging and disengaging magnification, + * {@code false} if it should ignore such gestures + */ + final boolean mDetectTripleTap; + + /** + * Whether {@link #mShortcutTriggered shortcut} is enabled + */ + final boolean mDetectShortcutTrigger; - private EventStreamTransformation mNext; + EventStreamTransformation mNext; - private int mCurrentState; - private int mPreviousState; + @VisibleForTesting int mCurrentState; + @VisibleForTesting int mPreviousState; - private boolean mTranslationEnabledBeforePan; + @VisibleForTesting boolean mShortcutTriggered; - private boolean mShortcutTriggered; + /** + * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link #STATE_DELEGATING} + */ + long mDelegatingStateDownTime; private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; - private long mDelegatingStateDownTime; - /** * @param context Context for resolving various magnification-related resources - * @param ams AccessibilityManagerService used to obtain a {@link MagnificationController} + * @param magnificationController the {@link MagnificationController} + * * @param detectTripleTap {@code true} if this detector should detect and respond to triple-tap - * gestures for engaging and disengaging magnification, - * {@code false} if it should ignore such gestures - * @param triggerable {@code true} if this detector should be "triggerable" by some external - * shortcut invoking {@link #notifyShortcutTriggered}, {@code - * false} if it should ignore such triggers. + * gestures for engaging and disengaging magnification, + * {@code false} if it should ignore such gestures + * @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some + * external shortcut invoking {@link #notifyShortcutTriggered}, + * {@code false} if it should ignore such triggers. */ - public MagnificationGestureHandler(Context context, AccessibilityManagerService ams, - boolean detectTripleTap, boolean triggerable) { - mMagnificationController = ams.getMagnificationController(); + public MagnificationGestureHandler(Context context, + MagnificationController magnificationController, + boolean detectTripleTap, + boolean detectShortcutTrigger) { + mMagnificationController = magnificationController; + mDetectingStateHandler = new DetectingStateHandler(context); - mStateViewportDraggingHandler = new StateViewportDraggingHandler(); - mMagnifiedContentInteractionStateHandler = - new MagnifiedContentInteractionStateHandler(context); + mViewportDraggingStateHandler = new ViewportDraggingStateHandler(); + mPanningScalingStateHandler = + new PanningScalingStateHandler(context); + mDetectTripleTap = detectTripleTap; - mTriggerable = triggerable; + mDetectShortcutTrigger = detectShortcutTrigger; - if (triggerable) { + if (mDetectShortcutTrigger) { mScreenStateReceiver = new ScreenStateReceiver(context, this); mScreenStateReceiver.register(); } else { mScreenStateReceiver = null; } - transitionToState(STATE_DETECTING); + transitionTo(STATE_DETECTING); } @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { - if (mNext != null) { - mNext.onMotionEvent(event, rawEvent, policyFlags); - } - return; - } - if (!mDetectTripleTap && !mTriggerable) { - if (mNext != null) { - dispatchTransformedEvent(event, rawEvent, policyFlags); - } + if ((!mDetectTripleTap && !mDetectShortcutTrigger) + || !event.isFromSource(SOURCE_TOUCHSCREEN)) { + dispatchTransformedEvent(event, rawEvent, policyFlags); return; } - mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags); - switch (mCurrentState) { + // Local copy to avoid dispatching the same event to more than one state handler + // in case mPanningScalingStateHandler changes mCurrentState + int currentState = mCurrentState; + mPanningScalingStateHandler.onMotionEvent(event, rawEvent, policyFlags); + switch (currentState) { case STATE_DELEGATING: { handleMotionEventStateDelegating(event, rawEvent, policyFlags); } @@ -179,17 +213,17 @@ class MagnificationGestureHandler implements EventStreamTransformation { } break; case STATE_VIEWPORT_DRAGGING: { - mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags); + mViewportDraggingStateHandler.onMotionEvent(event, rawEvent, policyFlags); } break; - case STATE_MAGNIFIED_INTERACTION: { - // mMagnifiedContentInteractionStateHandler handles events only + case STATE_PANNING_SCALING: { + // mPanningScalingStateHandler handles events only // if this is the current state since it uses ScaleGestureDetector // and a GestureDetector which need well formed event stream. } break; default: { - throw new IllegalStateException("Unknown state: " + mCurrentState); + throw new IllegalStateException("Unknown state: " + currentState); } } } @@ -215,8 +249,8 @@ class MagnificationGestureHandler implements EventStreamTransformation { @Override public void clearEvents(int inputSource) { - if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) { - clear(); + if (inputSource == SOURCE_TOUCHSCREEN) { + clearAndTransitionToStateDetecting(); } if (mNext != null) { @@ -229,20 +263,25 @@ class MagnificationGestureHandler implements EventStreamTransformation { if (mScreenStateReceiver != null) { mScreenStateReceiver.unregister(); } - clear(); + clearAndTransitionToStateDetecting(); } void notifyShortcutTriggered() { - if (mTriggerable) { - if (mMagnificationController.resetIfNeeded(true)) { - clear(); + if (mDetectShortcutTrigger) { + boolean wasMagnifying = mMagnificationController.resetIfNeeded(/* animate */ true); + if (wasMagnifying) { + clearAndTransitionToStateDetecting(); } else { - setMagnificationShortcutTriggered(!mShortcutTriggered); + toggleShortcutTriggered(); } } } - private void setMagnificationShortcutTriggered(boolean state) { + private void toggleShortcutTriggered() { + setShortcutTriggered(!mShortcutTriggered); + } + + private void setShortcutTriggered(boolean state) { if (mShortcutTriggered == state) { return; } @@ -251,27 +290,25 @@ class MagnificationGestureHandler implements EventStreamTransformation { mMagnificationController.setForceShowMagnifiableBounds(state); } - private void clear() { + void clearAndTransitionToStateDetecting() { + setShortcutTriggered(false); mCurrentState = STATE_DETECTING; - setMagnificationShortcutTriggered(false); mDetectingStateHandler.clear(); - mStateViewportDraggingHandler.clear(); - mMagnifiedContentInteractionStateHandler.clear(); + mViewportDraggingStateHandler.clear(); + mPanningScalingStateHandler.clear(); } private void handleMotionEventStateDelegating(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - mDelegatingStateDownTime = event.getDownTime(); - } - break; - case MotionEvent.ACTION_UP: { - if (mDetectingStateHandler.mDelayedEventQueue == null) { - transitionToState(STATE_DETECTING); - } - } - break; + if (event.getActionMasked() == ACTION_UP) { + transitionTo(STATE_DETECTING); + } + delegateEvent(event, rawEvent, policyFlags); + } + + void delegateEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mDelegatingStateDownTime = event.getDownTime(); } if (mNext != null) { // We cache some events to see if the user wants to trigger magnification. @@ -287,13 +324,15 @@ class MagnificationGestureHandler implements EventStreamTransformation { private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - // If the event is within the magnified portion of the screen we have + if (mNext == null) return; // Nowhere to dispatch to + + // If the touchscreen event is within the magnified portion of the screen we have // to change its location to be where the user thinks he is poking the // UI which may have been magnified and panned. - final float eventX = event.getX(); - final float eventY = event.getY(); if (mMagnificationController.isMagnifying() - && mMagnificationController.magnificationRegionContains(eventX, eventY)) { + && event.isFromSource(SOURCE_TOUCHSCREEN) + && mMagnificationController.magnificationRegionContains( + event.getX(), event.getY())) { final float scale = mMagnificationController.getScale(); final float scaledOffsetX = mMagnificationController.getOffsetX(); final float scaledOffsetY = mMagnificationController.getOffsetY(); @@ -347,34 +386,27 @@ class MagnificationGestureHandler implements EventStreamTransformation { return mTempPointerProperties; } - private void transitionToState(int state) { + private void transitionTo(int state) { if (DEBUG_STATE_TRANSITIONS) { - switch (state) { - case STATE_DELEGATING: { - Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING"); - } - break; - case STATE_DETECTING: { - Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING"); - } - break; - case STATE_VIEWPORT_DRAGGING: { - Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING"); - } - break; - case STATE_MAGNIFIED_INTERACTION: { - Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION"); - } - break; - default: { - throw new IllegalArgumentException("Unknown state: " + state); - } - } + Slog.i(LOG_TAG, (stateToString(mCurrentState) + " -> " + stateToString(state) + + " at " + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5))) + .replace(getClass().getName(), "")); } mPreviousState = mCurrentState; mCurrentState = state; } + private static String stateToString(int state) { + switch (state) { + case STATE_DELEGATING: return "STATE_DELEGATING"; + case STATE_DETECTING: return "STATE_DETECTING"; + case STATE_VIEWPORT_DRAGGING: return "STATE_VIEWPORT_DRAGGING"; + case STATE_PANNING_SCALING: return "STATE_PANNING_SCALING"; + case 0: return "0"; + default: throw new IllegalArgumentException("Unknown state: " + state); + } + } + private interface MotionEventHandler { void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags); @@ -384,21 +416,20 @@ class MagnificationGestureHandler implements EventStreamTransformation { /** * This class determines if the user is performing a scale or pan gesture. + * + * @see #STATE_PANNING_SCALING */ - private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener + final class PanningScalingStateHandler extends SimpleOnGestureListener implements OnScaleGestureListener, MotionEventHandler { private final ScaleGestureDetector mScaleGestureDetector; - private final GestureDetector mGestureDetector; + final float mScalingThreshold; - private final float mScalingThreshold; + float mInitialScaleFactor = -1; + boolean mScaling; - private float mInitialScaleFactor = -1; - - private boolean mScaling; - - public MagnifiedContentInteractionStateHandler(Context context) { + public PanningScalingStateHandler(Context context) { final TypedValue scaleValue = new TypedValue(); context.getResources().getValue( com.android.internal.R.dimen.config_screen_magnification_scaling_threshold, @@ -411,26 +442,39 @@ class MagnificationGestureHandler implements EventStreamTransformation { @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Dispatches #onScaleBegin, #onScale, #onScaleEnd mScaleGestureDetector.onTouchEvent(event); + // Dispatches #onScroll mGestureDetector.onTouchEvent(event); - if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { + + if (mCurrentState != STATE_PANNING_SCALING) { return; } - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - clear(); - mMagnificationController.persistScale(); - if (mPreviousState == STATE_VIEWPORT_DRAGGING) { - transitionToState(STATE_VIEWPORT_DRAGGING); - } else { - transitionToState(STATE_DETECTING); - } + + int action = event.getActionMasked(); + if (action == ACTION_POINTER_UP + && event.getPointerCount() == 2 // includes the pointer currently being released + && mPreviousState == STATE_VIEWPORT_DRAGGING) { + + persistScaleAndTransitionTo(STATE_VIEWPORT_DRAGGING); + + } else if (action == ACTION_UP) { + + persistScaleAndTransitionTo(STATE_DETECTING); + } } + public void persistScaleAndTransitionTo(int state) { + mMagnificationController.persistScale(); + clear(); + transitionTo(state); + } + @Override - public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX, - float distanceY) { - if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { + public boolean onScroll(MotionEvent first, MotionEvent second, + float distanceX, float distanceY) { + if (mCurrentState != STATE_PANNING_SCALING) { return true; } if (DEBUG_PANNING) { @@ -447,14 +491,15 @@ class MagnificationGestureHandler implements EventStreamTransformation { if (!mScaling) { if (mInitialScaleFactor < 0) { mInitialScaleFactor = detector.getScaleFactor(); + return false; + } + final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor; + if (abs(deltaScale) > mScalingThreshold) { + mScaling = true; + return true; } else { - final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor; - if (Math.abs(deltaScale) > mScalingThreshold) { - mScaling = true; - return true; - } + return false; } - return false; } final float initialScale = mMagnificationController.getScale(); @@ -485,7 +530,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { - return (mCurrentState == STATE_MAGNIFIED_INTERACTION); + return (mCurrentState == STATE_PANNING_SCALING); } @Override @@ -498,60 +543,65 @@ class MagnificationGestureHandler implements EventStreamTransformation { mInitialScaleFactor = -1; mScaling = false; } + + @Override + public String toString() { + return "MagnifiedContentInteractionStateHandler{" + + "mInitialScaleFactor=" + mInitialScaleFactor + + ", mScaling=" + mScaling + + '}'; + } } /** * This class handles motion events when the event dispatcher has * determined that the user is performing a single-finger drag of the * magnification viewport. + * + * @see #STATE_VIEWPORT_DRAGGING */ - private final class StateViewportDraggingHandler implements MotionEventHandler { + final class ViewportDraggingStateHandler implements MotionEventHandler { + /** Whether to disable zoom after dragging ends */ + boolean mZoomedInBeforeDrag; private boolean mLastMoveOutsideMagnifiedRegion; @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { final int action = event.getActionMasked(); switch (action) { - case MotionEvent.ACTION_DOWN: { - throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN"); - } - case MotionEvent.ACTION_POINTER_DOWN: { + case ACTION_POINTER_DOWN: { clear(); - transitionToState(STATE_MAGNIFIED_INTERACTION); + transitionTo(STATE_PANNING_SCALING); } break; - case MotionEvent.ACTION_MOVE: { + case ACTION_MOVE: { if (event.getPointerCount() != 1) { throw new IllegalStateException("Should have one pointer down."); } final float eventX = event.getX(); final float eventY = event.getY(); if (mMagnificationController.magnificationRegionContains(eventX, eventY)) { - if (mLastMoveOutsideMagnifiedRegion) { - mLastMoveOutsideMagnifiedRegion = false; - mMagnificationController.setCenter(eventX, eventY, true, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); - } else { - mMagnificationController.setCenter(eventX, eventY, false, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); - } + mMagnificationController.setCenter(eventX, eventY, + /* animate */ mLastMoveOutsideMagnifiedRegion, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + mLastMoveOutsideMagnifiedRegion = false; } else { mLastMoveOutsideMagnifiedRegion = true; } } break; - case MotionEvent.ACTION_UP: { - if (!mTranslationEnabledBeforePan) { - mMagnificationController.reset(true); - } + case ACTION_UP: { + if (!mZoomedInBeforeDrag) zoomOff(); clear(); - transitionToState(STATE_DETECTING); + transitionTo(STATE_DETECTING); } break; - case MotionEvent.ACTION_POINTER_UP: { + + case ACTION_DOWN: + case ACTION_POINTER_UP: { throw new IllegalArgumentException( - "Unexpected event type: ACTION_POINTER_UP"); + "Unexpected event type: " + MotionEvent.actionToString(action)); } } } @@ -560,211 +610,224 @@ class MagnificationGestureHandler implements EventStreamTransformation { public void clear() { mLastMoveOutsideMagnifiedRegion = false; } + + @Override + public String toString() { + return "ViewportDraggingStateHandler{" + + "mZoomedInBeforeDrag=" + mZoomedInBeforeDrag + + ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion + + '}'; + } } /** * This class handles motion events when the event dispatch has not yet * determined what the user is doing. It watches for various tap events. + * + * @see #STATE_DETECTING */ - private final class DetectingStateHandler implements MotionEventHandler { - - private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1; + final class DetectingStateHandler implements MotionEventHandler, Handler.Callback { + private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1; private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; - private static final int ACTION_TAP_COUNT = 3; - - private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout(); - - private final int mMultiTapTimeSlop; - - private final int mTapDistanceSlop; - - private final int mMultiTapDistanceSlop; + final int mLongTapMinDelay = ViewConfiguration.getJumpTapTimeout(); + final int mSwipeMinDistance; + final int mMultiTapMaxDelay; + final int mMultiTapMaxDistance; private MotionEventInfo mDelayedEventQueue; + MotionEvent mLastDown; + private MotionEvent mPreLastDown; + private MotionEvent mLastUp; + private MotionEvent mPreLastUp; - private MotionEvent mLastDownEvent; - - private MotionEvent mLastTapUpEvent; - - private int mTapCount; + Handler mHandler = new Handler(this); public DetectingStateHandler(Context context) { - mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout() + mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout() + context.getResources().getInteger( com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment); - mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); - } - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message message) { - final int type = message.what; - switch (type) { - case MESSAGE_ON_ACTION_TAP_AND_HOLD: { - MotionEvent event = (MotionEvent) message.obj; - final int policyFlags = message.arg1; - onActionTapAndHold(event, policyFlags); - } - break; - case MESSAGE_TRANSITION_TO_DELEGATING_STATE: { - transitionToState(STATE_DELEGATING); - sendDelayedMotionEvents(); - clear(); - } - break; - default: { - throw new IllegalArgumentException("Unknown message type: " + type); - } + mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop(); + mMultiTapMaxDistance = ViewConfiguration.get(context).getScaledDoubleTapSlop(); + } + + @Override + public boolean handleMessage(Message message) { + final int type = message.what; + switch (type) { + case MESSAGE_ON_TRIPLE_TAP_AND_HOLD: { + onTripleTapAndHold(/* down */ (MotionEvent) message.obj); + } + break; + case MESSAGE_TRANSITION_TO_DELEGATING_STATE: { + transitionToDelegatingState(/* andClear */ true); + } + break; + default: { + throw new IllegalArgumentException("Unknown message type: " + type); } } - }; + return true; + } @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { cacheDelayedMotionEvent(event, rawEvent, policyFlags); - final int action = event.getActionMasked(); - switch (action) { + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: { + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { - transitionToDelegatingState(!mShortcutTriggered); - return; - } - if (mShortcutTriggered) { - Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, - policyFlags, 0, event); - mHandler.sendMessageDelayed(message, - ViewConfiguration.getLongPressTimeout()); - return; - } - if (mDetectTripleTap) { - if ((mTapCount == ACTION_TAP_COUNT - 1) && (mLastDownEvent != null) - && GestureUtils.isMultiTap(mLastDownEvent, event, mMultiTapTimeSlop, - mMultiTapDistanceSlop, 0)) { - Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, - policyFlags, 0, event); - mHandler.sendMessageDelayed(message, - ViewConfiguration.getLongPressTimeout()); - } else if (mTapCount < ACTION_TAP_COUNT) { - Message message = mHandler.obtainMessage( - MESSAGE_TRANSITION_TO_DELEGATING_STATE); - mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); - } - clearLastDownEvent(); - mLastDownEvent = MotionEvent.obtain(event); - } else if (mMagnificationController.isMagnifying()) { - // If magnified, consume an ACTION_DOWN until mMultiTapTimeSlop or - // mTapDistanceSlop is reached to ensure MAGNIFIED_INTERACTION is reachable. - Message message = mHandler.obtainMessage( - MESSAGE_TRANSITION_TO_DELEGATING_STATE); - mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); - return; + + transitionToDelegatingState(/* andClear */ !mShortcutTriggered); + + } else if (isMultiTapTriggered(2 /* taps */)) { + + // 3tap and hold + delayedTransitionToDraggingState(event); + + } else if (mDetectTripleTap + // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay + // to ensure reachability of + // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) + || mMagnificationController.isMagnifying()) { + + delayedTransitionToDelegatingState(); + } else { - transitionToDelegatingState(true); - return; + + // Delegate pending events without delay + transitionToDelegatingState(/* andClear */ true); } } break; - case MotionEvent.ACTION_POINTER_DOWN: { + case ACTION_POINTER_DOWN: { if (mMagnificationController.isMagnifying()) { - mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); - transitionToState(STATE_MAGNIFIED_INTERACTION); + transitionTo(STATE_PANNING_SCALING); clear(); } else { - transitionToDelegatingState(true); + transitionToDelegatingState(/* andClear */ true); } } break; - case MotionEvent.ACTION_MOVE: { - if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) { - final double distance = GestureUtils.computeDistance(mLastDownEvent, - event, 0); - if (Math.abs(distance) > mTapDistanceSlop) { - transitionToDelegatingState(true); - } + case ACTION_MOVE: { + if (isFingerDown() + && distance(mLastDown, /* move */ event) > mSwipeMinDistance + // For convenience, viewport dragging on 3tap&hold takes precedence + // over insta-delegating on 3tap&swipe + // (which is a rare combo to be used aside from magnification) + && !isMultiTapTriggered(2 /* taps */)) { + + // Swipe detected - delegate skipping timeout + transitionToDelegatingState(/* andClear */ true); } } break; - case MotionEvent.ACTION_UP: { - mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); + case ACTION_UP: { + + mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); + if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { - transitionToDelegatingState(!mShortcutTriggered); - return; - } - if (mShortcutTriggered) { - clear(); - onActionTap(event, policyFlags); - return; - } - if (mLastDownEvent == null) { - return; - } - if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, - mTapDistanceSlop, 0)) { - transitionToDelegatingState(true); - return; - } - if (mLastTapUpEvent != null && !GestureUtils.isMultiTap( - mLastTapUpEvent, event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { - transitionToDelegatingState(true); - return; - } - mTapCount++; - if (DEBUG_DETECTING) { - Slog.i(LOG_TAG, "Tap count:" + mTapCount); - } - if (mTapCount == ACTION_TAP_COUNT) { - clear(); - onActionTap(event, policyFlags); - return; + + transitionToDelegatingState(/* andClear */ !mShortcutTriggered); + + } else if (isMultiTapTriggered(3 /* taps */)) { + + onTripleTap(/* up */ event); + + } else if ( + // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP + isFingerDown() + //TODO long tap should never happen here + && (timeBetween(mLastDown, /* mLastUp */ event) >= mLongTapMinDelay) + || distance(mLastDown, /* mLastUp */ event) + >= mSwipeMinDistance) { + + transitionToDelegatingState(/* andClear */ true); + } - clearLastTapUpEvent(); - mLastTapUpEvent = MotionEvent.obtain(event); - } - break; - case MotionEvent.ACTION_POINTER_UP: { - /* do nothing */ } break; } } - @Override - public void clear() { - setMagnificationShortcutTriggered(false); - mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); - mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); - clearTapDetectionState(); - clearDelayedMotionEvents(); + public boolean isMultiTapTriggered(int numTaps) { + + // Shortcut acts as the 2 initial taps + if (mShortcutTriggered) return tapCount() + 2 >= numTaps; + + return mDetectTripleTap + && tapCount() >= numTaps + && isMultiTap(mPreLastDown, mLastDown) + && isMultiTap(mPreLastUp, mLastUp); } - private void clearTapDetectionState() { - mTapCount = 0; - clearLastTapUpEvent(); - clearLastDownEvent(); + private boolean isMultiTap(MotionEvent first, MotionEvent second) { + return GestureUtils.isMultiTap(first, second, mMultiTapMaxDelay, mMultiTapMaxDistance); } - private void clearLastTapUpEvent() { - if (mLastTapUpEvent != null) { - mLastTapUpEvent.recycle(); - mLastTapUpEvent = null; - } + public boolean isFingerDown() { + return mLastDown != null; } - private void clearLastDownEvent() { - if (mLastDownEvent != null) { - mLastDownEvent.recycle(); - mLastDownEvent = null; - } + private long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) { + if (a == null && b == null) return 0; + return abs(timeOf(a) - timeOf(b)); + } + + /** + * Nullsafe {@link MotionEvent#getEventTime} that interprets null event as something that + * has happened long enough ago to be gone from the event queue. + * Thus the time for a null event is a small number, that is below any other non-null + * event's time. + * + * @return {@link MotionEvent#getEventTime}, or {@link Long#MIN_VALUE} if the event is null + */ + private long timeOf(@Nullable MotionEvent event) { + return event != null ? event.getEventTime() : Long.MIN_VALUE; + } + + public int tapCount() { + return MotionEventInfo.countOf(mDelayedEventQueue, ACTION_UP); } + /** -> {@link #STATE_DELEGATING} */ + public void delayedTransitionToDelegatingState() { + mHandler.sendEmptyMessageDelayed( + MESSAGE_TRANSITION_TO_DELEGATING_STATE, + mMultiTapMaxDelay); + } + + /** -> {@link #STATE_VIEWPORT_DRAGGING} */ + public void delayedTransitionToDraggingState(MotionEvent event) { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MESSAGE_ON_TRIPLE_TAP_AND_HOLD, event), + ViewConfiguration.getLongPressTimeout()); + } + + @Override + public void clear() { + setShortcutTriggered(false); + mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + clearDelayedMotionEvents(); + } + + private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (event.getActionMasked() == ACTION_DOWN) { + mPreLastDown = mLastDown; + mLastDown = event; + } else if (event.getActionMasked() == ACTION_UP) { + mPreLastUp = mLastUp; + mLastUp = event; + } + MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent, policyFlags); if (mDelayedEventQueue == null) { @@ -782,8 +845,13 @@ class MagnificationGestureHandler implements EventStreamTransformation { while (mDelayedEventQueue != null) { MotionEventInfo info = mDelayedEventQueue; mDelayedEventQueue = info.mNext; - MagnificationGestureHandler.this.onMotionEvent(info.mEvent, info.mRawEvent, - info.mPolicyFlags); + + // Because MagnifiedInteractionStateHandler requires well-formed event stream + mPanningScalingStateHandler.onMotionEvent( + info.event, info.rawEvent, info.policyFlags); + + delegateEvent(info.event, info.rawEvent, info.policyFlags); + info.recycle(); } } @@ -794,91 +862,136 @@ class MagnificationGestureHandler implements EventStreamTransformation { mDelayedEventQueue = info.mNext; info.recycle(); } + mPreLastDown = null; + mPreLastUp = null; + mLastDown = null; + mLastUp = null; } - private void transitionToDelegatingState(boolean andClear) { - transitionToState(STATE_DELEGATING); + void transitionToDelegatingState(boolean andClear) { + transitionTo(STATE_DELEGATING); sendDelayedMotionEvents(); - if (andClear) { - clear(); - } + if (andClear) clear(); } - private void onActionTap(MotionEvent up, int policyFlags) { + private void onTripleTap(MotionEvent up) { + if (DEBUG_DETECTING) { - Slog.i(LOG_TAG, "onActionTap()"); + Slog.i(LOG_TAG, "onTripleTap(); delayed: " + + MotionEventInfo.toString(mDelayedEventQueue)); } + clear(); - if (!mMagnificationController.isMagnifying()) { - final float targetScale = mMagnificationController.getPersistedScale(); - final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE); - mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + // Toggle zoom + if (mMagnificationController.isMagnifying()) { + zoomOff(); } else { - mMagnificationController.reset(true); + zoomOn(up.getX(), up.getY()); } } - private void onActionTapAndHold(MotionEvent down, int policyFlags) { - if (DEBUG_DETECTING) { - Slog.i(LOG_TAG, "onActionTapAndHold()"); - } + void onTripleTapAndHold(MotionEvent down) { + if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()"); clear(); - mTranslationEnabledBeforePan = mMagnificationController.isMagnifying(); - final float targetScale = mMagnificationController.getPersistedScale(); - final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE); - mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + mViewportDraggingStateHandler.mZoomedInBeforeDrag = + mMagnificationController.isMagnifying(); + + zoomOn(down.getX(), down.getY()); - transitionToState(STATE_VIEWPORT_DRAGGING); + transitionTo(STATE_VIEWPORT_DRAGGING); + } + + @Override + public String toString() { + return "DetectingStateHandler{" + + "tapCount()=" + tapCount() + + ", mDelayedEventQueue=" + MotionEventInfo.toString(mDelayedEventQueue) + + '}'; } } + private void zoomOn(float centerX, float centerY) { + final float scale = MathUtils.constrain( + mMagnificationController.getPersistedScale(), + MIN_SCALE, MAX_SCALE); + mMagnificationController.setScaleAndCenter( + scale, centerX, centerY, + /* animate */ true, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + } + + private void zoomOff() { + mMagnificationController.reset(/* animate */ true); + } + + private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) { + if (event != null) { + event.recycle(); + } + return null; + } + + @Override + public String toString() { + return "MagnificationGestureHandler{" + + "mDetectingStateHandler=" + mDetectingStateHandler + + ", mMagnifiedInteractionStateHandler=" + mPanningScalingStateHandler + + ", mViewportDraggingStateHandler=" + mViewportDraggingStateHandler + + ", mDetectTripleTap=" + mDetectTripleTap + + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger + + ", mCurrentState=" + stateToString(mCurrentState) + + ", mPreviousState=" + stateToString(mPreviousState) + + ", mShortcutTriggered=" + mShortcutTriggered + + ", mDelegatingStateDownTime=" + mDelegatingStateDownTime + + ", mMagnificationController=" + mMagnificationController + + '}'; + } + private static final class MotionEventInfo { private static final int MAX_POOL_SIZE = 10; - private static final Object sLock = new Object(); - private static MotionEventInfo sPool; - private static int sPoolSize; private MotionEventInfo mNext; - private boolean mInPool; - public MotionEvent mEvent; - - public MotionEvent mRawEvent; - - public int mPolicyFlags; + public MotionEvent event; + public MotionEvent rawEvent; + public int policyFlags; public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent, int policyFlags) { synchronized (sLock) { - MotionEventInfo info; - if (sPoolSize > 0) { - sPoolSize--; - info = sPool; - sPool = info.mNext; - info.mNext = null; - info.mInPool = false; - } else { - info = new MotionEventInfo(); - } + MotionEventInfo info = obtainInternal(); info.initialize(event, rawEvent, policyFlags); return info; } } + @NonNull + private static MotionEventInfo obtainInternal() { + MotionEventInfo info; + if (sPoolSize > 0) { + sPoolSize--; + info = sPool; + sPool = info.mNext; + info.mNext = null; + info.mInPool = false; + } else { + info = new MotionEventInfo(); + } + return info; + } + private void initialize(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - mEvent = MotionEvent.obtain(event); - mRawEvent = MotionEvent.obtain(rawEvent); - mPolicyFlags = policyFlags; + this.event = MotionEvent.obtain(event); + this.rawEvent = MotionEvent.obtain(rawEvent); + this.policyFlags = policyFlags; } public void recycle() { @@ -897,11 +1010,22 @@ class MagnificationGestureHandler implements EventStreamTransformation { } private void clear() { - mEvent.recycle(); - mEvent = null; - mRawEvent.recycle(); - mRawEvent = null; - mPolicyFlags = 0; + event = recycleAndNullify(event); + rawEvent = recycleAndNullify(rawEvent); + policyFlags = 0; + } + + static int countOf(MotionEventInfo info, int eventType) { + if (info == null) return 0; + return (info.event.getAction() == eventType ? 1 : 0) + + countOf(info.mNext, eventType); + } + + public static String toString(MotionEventInfo info) { + return info == null + ? "" + : MotionEvent.actionToString(info.event.getAction()).replace("ACTION_", "") + + " " + MotionEventInfo.toString(info.mNext); } } @@ -927,7 +1051,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { @Override public void onReceive(Context context, Intent intent) { - mGestureHandler.setMagnificationShortcutTriggered(false); + mGestureHandler.setShortcutTriggered(false); } } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 20ccee286fbc..2b8b25ef0fb4 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -522,10 +522,11 @@ final class AutofillManagerServiceImpl { /** * Updates the last fill selection when an authentication was selected. */ - void setAuthenticationSelected(int sessionId) { + void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) { synchronized (mLock) { if (isValidEventLocked("setAuthenticationSelected()", sessionId)) { - mEventHistory.addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null)); + mEventHistory + .addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState)); } } } @@ -533,11 +534,13 @@ final class AutofillManagerServiceImpl { /** * Updates the last fill selection when an dataset authentication was selected. */ - void setDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) { + void setDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId, + @Nullable Bundle clientState) { synchronized (mLock) { if (isValidEventLocked("setDatasetAuthenticationSelected()", sessionId)) { mEventHistory.addEvent( - new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset)); + new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, + clientState)); } } } @@ -545,10 +548,10 @@ final class AutofillManagerServiceImpl { /** * Updates the last fill selection when an save Ui is shown. */ - void setSaveShown(int sessionId) { + void setSaveShown(int sessionId, @Nullable Bundle clientState) { synchronized (mLock) { if (isValidEventLocked("setSaveShown()", sessionId)) { - mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null)); + mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState)); } } } @@ -556,10 +559,12 @@ final class AutofillManagerServiceImpl { /** * Updates the last fill response when a dataset was selected. */ - void setDatasetSelected(@Nullable String selectedDataset, int sessionId) { + void setDatasetSelected(@Nullable String selectedDataset, int sessionId, + @Nullable Bundle clientState) { synchronized (mLock) { if (isValidEventLocked("setDatasetSelected()", sessionId)) { - mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset)); + mEventHistory.addEvent( + new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState)); } } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 95db6039b696..8d9f0aa2f49b 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -597,7 +597,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getFillContextByRequestIdLocked(requestId).getStructure(), extras); } - mService.setAuthenticationSelected(id); + mService.setAuthenticationSelected(id, mClientState); final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex); mHandlerCaller.getHandler().post(() -> startAuthentication(authenticationId, @@ -970,7 +970,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!"); - mService.setSaveShown(id); + mService.setSaveShown(id, mClientState); final IAutoFillManagerClient client = getClient(); mPendingSaveUi = new PendingUi(mActivityToken); getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, @@ -1532,14 +1532,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Autofill it directly... if (dataset.getAuthentication() == null) { - mService.setDatasetSelected(dataset.getId(), id); + mService.setDatasetSelected(dataset.getId(), id, mClientState); autoFillApp(dataset); return; } // ...or handle authentication. - mService.setDatasetAuthenticationSelected(dataset.getId(), id); + mService.setDatasetAuthenticationSelected(dataset.getId(), id, mClientState); setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false); final Intent fillInIntent = createAuthFillInIntent( getFillContextByRequestIdLocked(requestId).getStructure(), mClientState); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 29d562b17deb..c1a7f6882d18 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -10475,7 +10475,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF public ComponentName[] listAllTransportComponents() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransportComponents"); - return mTransportManager.getAllTransportCompenents(); + return mTransportManager.getAllTransportComponents(); } @Override diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index b01cfc572432..83b6693e7a70 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -2762,7 +2762,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public ComponentName[] listAllTransportComponents() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransportComponents"); - return mTransportManager.getAllTransportCompenents(); + return mTransportManager.getAllTransportComponents(); } @Override diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index fb2982eb0baa..098bc0778ac1 100644 --- a/services/backup/java/com/android/server/backup/TransportManager.java +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -41,10 +41,12 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -57,7 +59,9 @@ public class TransportManager { private static final String TAG = "BackupTransportManager"; - private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; + @VisibleForTesting + /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST = + "android.backup.TRANSPORT_HOST"; private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins @@ -95,7 +99,11 @@ public class TransportManager { TransportBoundListener listener, Looper looper) { mContext = context; mPackageManager = context.getPackageManager(); - mTransportWhitelist = (whitelist != null) ? whitelist : new ArraySet<>(); + if (whitelist != null) { + mTransportWhitelist = whitelist; + } else { + mTransportWhitelist = new ArraySet<>(); + } mCurrentTransportName = defaultTransport; mTransportBoundListener = listener; mHandler = new RebindOnTimeoutHandler(looper); @@ -186,7 +194,7 @@ public class TransportManager { } } - ComponentName[] getAllTransportCompenents() { + ComponentName[] getAllTransportComponents() { synchronized (mTransportLock) { return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]); } @@ -208,7 +216,8 @@ public class TransportManager { } } - void ensureTransportReady(ComponentName transportComponent, SelectBackupTransportCallback listener) { + void ensureTransportReady(ComponentName transportComponent, + SelectBackupTransportCallback listener) { synchronized (mTransportLock) { TransportConnection conn = mValidTransports.get(transportComponent); if (conn == null) { @@ -252,7 +261,7 @@ public class TransportManager { intent, 0, UserHandle.USER_SYSTEM); if (hosts != null) { for (ResolveInfo host : hosts) { - final ComponentName infoComponentName = host.serviceInfo.getComponentName(); + final ComponentName infoComponentName = getComponentName(host.serviceInfo); boolean shouldBind = false; if (components != null && packageName != null) { for (String component : components) { @@ -310,7 +319,7 @@ public class TransportManager { Intent intent = new Intent(mTransportServiceIntent) .setComponent(componentName); return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, - UserHandle.SYSTEM); + createSystemUserHandle()); } private class TransportConnection implements ServiceConnection { @@ -333,7 +342,7 @@ public class TransportManager { boolean success = false; EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - component.flattenToShortString(), 1); + component.flattenToShortString(), 1); try { mTransportName = mBinder.name(); @@ -437,7 +446,8 @@ public class TransportManager { } private long getRebindTimeout() { - final boolean isDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(), + final boolean isDeviceProvisioned = Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; return isDeviceProvisioned ? REBINDING_TIMEOUT_PROVISIONED_MS @@ -445,7 +455,7 @@ public class TransportManager { } } - interface TransportBoundListener { + public interface TransportBoundListener { /** Should return true if this is a valid transport. */ boolean onTransportBound(IBackupTransport binder); } @@ -465,7 +475,7 @@ public class TransportManager { synchronized (mTransportLock) { if (mBoundTransports.containsValue(transportComponent)) { Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to " - + componentShortString + " so not attempting to rebind"); + + componentShortString + " so not attempting to rebind"); return; } Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: " @@ -492,4 +502,18 @@ public class TransportManager { Slog.v(TAG, message); } } + + // These only exists to make it testable with Robolectric, which is not updated to API level 24 + // yet. + // TODO: Get rid of this once Robolectric is updated. + private static ComponentName getComponentName(ServiceInfo serviceInfo) { + return new ComponentName(serviceInfo.packageName, serviceInfo.name); + } + + // These only exists to make it testable with Robolectric, which is not updated to API level 24 + // yet. + // TODO: Get rid of this once Robolectric is updated. + private static UserHandle createSystemUserHandle() { + return new UserHandle(UserHandle.USER_SYSTEM); + } } diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 473384081656..046eb761d1c0 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -728,6 +728,9 @@ public class VibratorService extends IVibratorService.Stub return timeout; } } + if (!prebaked.shouldFallback()) { + return 0; + } final int id = prebaked.getId(); if (id < 0 || id >= mFallbackEffects.length || mFallbackEffects[id] == null) { Slog.w(TAG, "Failed to play prebaked effect, no fallback"); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f3bc62f90c5c..8f68bd6c2781 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3093,7 +3093,7 @@ public class ActivityManagerService extends IActivityManager.Stub */ void setResumedActivityUncheckLocked(ActivityRecord r, String reason) { final TaskRecord task = r.getTask(); - if (task.isApplicationTask()) { + if (task.isActivityTypeStandard()) { if (mCurAppTimeTracker != r.appTimeTracker) { // We are switching app tracking. Complete the current one. if (mCurAppTimeTracker != null) { @@ -9934,7 +9934,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!allowed) { // If the caller doesn't have the GET_TASKS permission, then only // allow them to see a small subset of tasks -- their own and home. - if (!tr.isHomeTask() && tr.effectiveUid != callingUid) { + if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) { if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr); continue; } @@ -12945,7 +12945,7 @@ public class ActivityManagerService extends IActivityManager.Stub int userId; synchronized (this) { final ActivityStack focusedStack = getFocusedStack(); - if (focusedStack == null || focusedStack.isAssistantStack()) { + if (focusedStack == null || focusedStack.isActivityTypeAssistant()) { return false; } @@ -13050,7 +13050,7 @@ public class ActivityManagerService extends IActivityManager.Stub pae = new PendingAssistExtras(activity, extras, intent, hint, receiver, receiverExtras, userHandle); - pae.isHome = activity.isHomeActivity(); + pae.isHome = activity.isActivityTypeHome(); // Increment the sessionId if necessary if (newSessionId) { diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 4433b84af68d..651d3a6a044e 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -33,6 +33,12 @@ import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.activityTypeToString; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; @@ -226,12 +232,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private final boolean componentSpecified; // did caller specify an explicit component? final boolean rootVoiceInteraction; // was this the root activity of a voice interaction? - static final int APPLICATION_ACTIVITY_TYPE = 0; - static final int HOME_ACTIVITY_TYPE = 1; - static final int RECENTS_ACTIVITY_TYPE = 2; - static final int ASSISTANT_ACTIVITY_TYPE = 3; - int mActivityType; - private CharSequence nonLocalizedLabel; // the label information from the package mgr. private int labelRes; // the label information from the package mgr. private int icon; // resource identifier of activity's icon. @@ -388,7 +388,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded); pw.print(" componentSpecified="); pw.print(componentSpecified); - pw.print(" mActivityType="); pw.println(mActivityType); + pw.print(" mActivityType="); pw.println( + activityTypeToString(getActivityType())); if (rootVoiceInteraction) { pw.print(prefix); pw.print("rootVoiceInteraction="); pw.println(rootVoiceInteraction); } @@ -495,7 +496,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); pw.print(" forceNewConfig="); pw.println(forceNewConfig); pw.print(prefix); pw.print("mActivityType="); - pw.println(activityTypeToString(mActivityType)); + pw.println(activityTypeToString(getActivityType())); if (requestedVrComponent != null) { pw.print(prefix); pw.print("requestedVrComponent="); @@ -652,14 +653,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } } - void updatePictureInPictureMode(Rect targetStackBounds) { + void updatePictureInPictureMode(Rect targetStackBounds, boolean forceUpdate) { if (task == null || task.getStack() == null || app == null || app.thread == null) { return; } final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) && (targetStackBounds != null); - if (inPictureInPictureMode != mLastReportedPictureInPictureMode) { + if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) { // Picture-in-picture mode changes also trigger a multi-window mode change as well, so // update that here in order mLastReportedPictureInPictureMode = inPictureInPictureMode; @@ -674,8 +675,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private void schedulePictureInPictureModeChanged(Configuration overrideConfig) { try { app.thread.schedulePictureInPictureModeChanged(appToken, - mLastReportedPictureInPictureMode, - overrideConfig); + mLastReportedPictureInPictureMode, overrideConfig); } catch (Exception e) { // If process died, no one cares. } @@ -938,7 +938,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(), appInfo.targetSdkVersion, mRotationAnimationHint, ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L, - getOverrideConfiguration(), mBounds); + new Configuration(getOverrideConfiguration()), mBounds); task.addActivityToTop(this); @@ -1028,10 +1028,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private void setActivityType(boolean componentSpecified, int launchedFromUid, Intent intent, ActivityOptions options, ActivityRecord sourceRecord) { + int activityType = ACTIVITY_TYPE_UNDEFINED; if ((!componentSpecified || canLaunchHomeActivity(launchedFromUid, sourceRecord)) && isHomeIntent(intent) && !isResolverActivity()) { // This sure looks like a home activity! - mActivityType = HOME_ACTIVITY_TYPE; + activityType = ACTIVITY_TYPE_HOME; if (info.resizeMode == RESIZE_MODE_FORCE_RESIZEABLE || info.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) { @@ -1039,13 +1040,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo info.resizeMode = RESIZE_MODE_UNRESIZEABLE; } } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) { - mActivityType = RECENTS_ACTIVITY_TYPE; + activityType = ACTIVITY_TYPE_RECENTS; } else if (options != null && options.getLaunchStackId() == ASSISTANT_STACK_ID && canLaunchAssistActivity(launchedFromPackage)) { - mActivityType = ASSISTANT_ACTIVITY_TYPE; - } else { - mActivityType = APPLICATION_ACTIVITY_TYPE; + activityType = ACTIVITY_TYPE_ASSISTANT; } + setActivityType(activityType); } void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) { @@ -1096,18 +1096,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return stack != null && stack.isInStackLocked(this) != null; } - boolean isHomeActivity() { - return mActivityType == HOME_ACTIVITY_TYPE; - } - - boolean isRecentsActivity() { - return mActivityType == RECENTS_ACTIVITY_TYPE; - } - - boolean isAssistantActivity() { - return mActivityType == ASSISTANT_ACTIVITY_TYPE; - } - boolean isPersistable() { return (info.persistableMode == PERSIST_ROOT_ONLY || info.persistableMode == PERSIST_ACROSS_REBOOTS) && @@ -1134,7 +1122,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo * @return whether this activity supports PiP multi-window and can be put in the pinned stack. */ boolean supportsPictureInPicture() { - return service.mSupportsPictureInPicture && !isHomeActivity() + return service.mSupportsPictureInPicture && isActivityTypeStandard() && info.supportsPictureInPicture(); } @@ -1160,7 +1148,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo * @return whether this activity supports non-PiP multi-window. */ private boolean supportsResizeableMultiWindow() { - return service.mSupportsMultiWindow && !isHomeActivity() + return service.mSupportsMultiWindow && !isActivityTypeHome() && (ActivityInfo.isResizeableMode(info.resizeMode) || service.mForceResizableActivities); } @@ -1521,7 +1509,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } void notifyUnknownVisibilityLaunched() { - mWindowContainerController.notifyUnknownVisibilityLaunched(); + + // No display activities never add a window, so there is no point in waiting them for + // relayout. + if (!noDisplay) { + mWindowContainerController.notifyUnknownVisibilityLaunched(); + } } /** @@ -1537,7 +1530,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo boolean isVisible = !behindFullscreenActivity || mLaunchTaskBehind; - if (service.mSupportsLeanbackOnly && isVisible && isRecentsActivity()) { + if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) { // On devices that support leanback only (Android TV), Recents activity can only be // visible if the home stack is the focused stack or we are in split-screen mode. isVisible = mStackSupervisor.getStack(DOCKED_STACK_ID) != null @@ -1615,7 +1608,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo newIntents = null; stopped = false; - if (isHomeActivity()) { + if (isActivityTypeHome()) { ProcessRecord app = task.mActivities.get(0).app; if (app != null && app != service.mHomeProcess) { service.mHomeProcess = app; @@ -2145,7 +2138,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo config.getOverrideConfiguration()); } - void setLastReportedConfiguration(Configuration global, Configuration override) { + private void setLastReportedConfiguration(Configuration global, Configuration override) { mLastReportedConfiguration.setConfiguration(global, override); } @@ -2718,16 +2711,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return r; } - private static String activityTypeToString(int type) { - switch (type) { - case APPLICATION_ACTIVITY_TYPE: return "APPLICATION_ACTIVITY_TYPE"; - case HOME_ACTIVITY_TYPE: return "HOME_ACTIVITY_TYPE"; - case RECENTS_ACTIVITY_TYPE: return "RECENTS_ACTIVITY_TYPE"; - case ASSISTANT_ACTIVITY_TYPE: return "ASSISTANT_ACTIVITY_TYPE"; - default: return Integer.toString(type); - } - } - private static boolean isInVrUiMode(Configuration config) { return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET; } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 2da437769ed0..4560d3a6e4f7 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -23,8 +23,12 @@ import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; +import static android.app.ActivityManager.StackId.getActivityTypeForStackId; import static android.app.ActivityManager.StackId.getWindowingModeForStackId; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; @@ -66,9 +70,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAV import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.ASSISTANT_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityStack.ActivityState.STOPPED; import static com.android.server.am.ActivityStack.ActivityState.STOPPING; import static com.android.server.am.ActivityStackSupervisor.FindTaskResult; @@ -486,6 +487,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (windowingMode != WINDOWING_MODE_UNDEFINED) { setWindowingMode(windowingMode); } + final int activityType = getActivityTypeForStackId(mStackId); + if (activityType != ACTIVITY_TYPE_UNDEFINED) { + setActivityType(activityType); + } } /** Adds the stack to specified display and calls WindowManager to do the same. */ @@ -831,14 +836,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return hadit; } - final boolean isHomeStack() { - return mStackId == HOME_STACK_ID; - } - - final boolean isRecentsStack() { - return mStackId == RECENTS_STACK_ID; - } - final boolean isHomeOrRecentsStack() { return StackId.isHomeOrRecentsStack(mStackId); } @@ -851,10 +848,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return mStackId == PINNED_STACK_ID; } - final boolean isAssistantStack() { - return mStackId == ASSISTANT_STACK_ID; - } - final boolean isOnHomeDisplay() { return isAttached() && mDisplayId == DEFAULT_DISPLAY; } @@ -975,7 +968,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": mismatch root " + r); continue; } - if (r.mActivityType != target.mActivityType) { + if (!r.hasCompatibleActivityType(target)) { if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": mismatch activity type"); continue; } @@ -1656,7 +1649,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack() - && !StackId.isHomeOrRecentsStack(stackBehindId) && !isAssistantStack()) { + && !StackId.isHomeOrRecentsStack(stackBehindId) + && !isActivityTypeAssistant()) { // Stack isn't translucent if it's top activity should have the home stack // behind it and the stack currently behind it isn't the home or recents stack // or the assistant stack. @@ -1703,7 +1697,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (mStackId == DOCKED_STACK_ID) { // If the assistant stack is focused and translucent, then the docked stack is always // visible - if (topStack.isAssistantStack()) { + if (topStack.isActivityTypeAssistant()) { return (topStack.isStackTranslucent(starting, DOCKED_STACK_ID)) ? STACK_VISIBLE : STACK_INVISIBLE; } @@ -1905,39 +1899,22 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // status of an activity in a previous task affects other. behindFullscreenActivity = stackVisibility == STACK_INVISIBLE; } else if (mStackId == HOME_STACK_ID) { - if (task.isHomeTask()) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + task - + " stackInvisible=" + stackInvisible - + " behindFullscreenActivity=" + behindFullscreenActivity); - // No other task in the home stack should be visible behind the home activity. - // Home activities is usually a translucent activity with the wallpaper behind - // them. However, when they don't have the wallpaper behind them, we want to - // show activities in the next application stack behind them vs. another - // task in the home stack like recents. - behindFullscreenActivity = true; - } else if (task.isRecentsTask() - && task.getTaskToReturnTo() == APPLICATION_ACTIVITY_TYPE) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Recents task returning to app: at " + task - + " stackInvisible=" + stackInvisible - + " behindFullscreenActivity=" + behindFullscreenActivity); - // We don't want any other tasks in the home stack visible if the recents - // activity is going to be returning to an application activity type. - // We do this to preserve the visible order the user used to get into the - // recents activity. The recents activity is normally translucent and if it - // doesn't have the wallpaper behind it the next activity in the home stack - // shouldn't be visible when the home stack is brought to the front to display - // the recents activity from an app. - behindFullscreenActivity = true; - } + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + task + + " stackInvisible=" + stackInvisible + + " behindFullscreenActivity=" + behindFullscreenActivity); + // No other task in the home stack should be visible behind the home activity. + // Home activities is usually a translucent activity with the wallpaper behind + // them. However, when they don't have the wallpaper behind them, we want to + // show activities in the next application stack behind them vs. another + // task in the home stack like recents. + behindFullscreenActivity = true; } else if (mStackId == FULLSCREEN_WORKSPACE_STACK_ID) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task + " returning to non-application type=" + task.getTaskToReturnTo()); // Once we reach a fullscreen stack task that has a running activity and should // return to another stack task, then no other activities behind that one should // be visible. - if (task.topRunningActivityLocked() != null && - task.getTaskToReturnTo() != APPLICATION_ACTIVITY_TYPE) { + if (task.topRunningActivityLocked() != null && !task.returnsToStandardTask()) { behindFullscreenActivity = true; } } @@ -2334,10 +2311,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // This task is going away but it was supposed to return to the home stack. // Now the task above it has to return to the home task instead. final int taskNdx = mTaskHistory.indexOf(prevTask) + 1; - mTaskHistory.get(taskNdx).setTaskToReturnTo(HOME_ACTIVITY_TYPE); + mTaskHistory.get(taskNdx).setTaskToReturnTo(ACTIVITY_TYPE_HOME); } else if (!isOnHomeDisplay()) { return false; - } else if (!isHomeStack()){ + } else if (!isActivityTypeHome()){ if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Launching home next"); return isOnHomeDisplay() && @@ -2820,7 +2797,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If this is not on the default display, then just set the return type to application if (!isOnHomeDisplay()) { - task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); + task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); return; } @@ -2832,8 +2809,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } // If the task was launched from the assistant stack, set the return type to assistant - if (lastStack.isAssistantStack()) { - task.setTaskToReturnTo(ASSISTANT_ACTIVITY_TYPE); + if (lastStack.isActivityTypeAssistant()) { + task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); return; } @@ -2845,9 +2822,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If it's a last task over home - we default to keep its return to type not to // make underlying task focused when this one will be finished. int returnToType = isLastTaskOverHome - ? task.getTaskToReturnTo() : APPLICATION_ACTIVITY_TYPE; + ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD; if (fromHomeOrRecents && StackId.allowTopTaskToReturnHome(mStackId)) { - returnToType = topTask == null ? HOME_ACTIVITY_TYPE : topTask.taskType; + returnToType = topTask == null + ? ACTIVITY_TYPE_HOME : topTask.getActivityType(); } task.setTaskToReturnTo(returnToType); } @@ -3099,7 +3077,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } else { targetTask = createTaskRecord( mStackSupervisor.getNextTaskIdForUserLocked(target.userId), - target.info, null, null, null, false, target.mActivityType); + target.info, null, null, null, false); targetTask.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target + " out to new task " + targetTask); @@ -3410,8 +3388,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai throw new IllegalStateException("activity no longer associated with task:" + r); } - final boolean isAssistantOrOverAssistant = task.getStack().isAssistantStack() || - task.isOverAssistantStack(); + final boolean isAssistantOrOverAssistant = + task.getStack().isActivityTypeAssistant() || task.isOverAssistantStack(); if (r.frontOfTask && isATopFinishingTask(task) && (task.isOverHomeStack() || isAssistantOrOverAssistant)) { // For non-fullscreen or assistant stack, we want to move the focus to the next @@ -3446,8 +3424,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * @param allowFocusSelf Is the focus allowed to remain on the same stack. */ private boolean adjustFocusToNextFocusableStackLocked(String reason, boolean allowFocusSelf) { - if (isAssistantStack() && bottomTask() != null && - bottomTask().getTaskToReturnTo() == HOME_ACTIVITY_TYPE) { + if (isActivityTypeAssistant() && bottomTask() != null + && bottomTask().returnsToHomeTask()) { // If the current stack is the assistant stack, then use the return-to type to determine // whether to return to the home screen. This is needed to workaround an issue where // launching a fullscreen task (and subequently returning from that task) will cause @@ -3472,8 +3450,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return mStackSupervisor.moveHomeStackTaskToTop(reason); } - if (stack.isAssistantStack() && top != null - && top.getTask().getTaskToReturnTo() == HOME_ACTIVITY_TYPE) { + if (stack.isActivityTypeAssistant() && top != null + && top.getTask().returnsToHomeTask()) { // It is possible for the home stack to not be directly underneath the assistant stack. // For example, the assistant may start an activity in the fullscreen stack. Upon // returning to the assistant stack, we must ensure that the home stack is underneath @@ -3611,7 +3589,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { - if (!r.isHomeActivity() || mService.mHomeProcess != r.app) { + if (!r.isActivityTypeHome() || mService.mHomeProcess != r.app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false); @@ -3928,7 +3906,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (srec.frontOfTask && task != null && task.getBaseIntent() != null && task.getBaseIntent().isDocument()) { // Okay, this activity is at the root of its task. What to do, what to do... - if (task.getTaskToReturnTo() != ActivityRecord.APPLICATION_ACTIVITY_TYPE) { + if (!task.returnsToStandardTask()) { // Finishing won't return to an application, so we need to recreate. return true; } @@ -3938,11 +3916,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai Slog.w(TAG, "shouldUpRecreateTask: task not in history for " + srec); return false; } - if (taskIdx == 0) { - // At the bottom of the stack, nothing to go back to. - return true; - } - TaskRecord prevTask = mTaskHistory.get(taskIdx); + final TaskRecord prevTask = mTaskHistory.get(taskIdx); if (!task.affinity.equals(prevTask.affinity)) { // These are different apps, so need to recreate. return true; @@ -4527,17 +4501,18 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } void moveHomeStackTaskToTop() { + if (!isActivityTypeHome()) { + throw new IllegalStateException("Calling moveHomeStackTaskToTop() on non-home stack: " + + this); + } final int top = mTaskHistory.size() - 1; - for (int taskNdx = top; taskNdx >= 0; --taskNdx) { - final TaskRecord task = mTaskHistory.get(taskNdx); - if (task.taskType == HOME_ACTIVITY_TYPE) { - if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG_STACK, - "moveHomeStackTaskToTop: moving " + task); - mTaskHistory.remove(taskNdx); - mTaskHistory.add(top, task); - updateTaskMovement(task, true); - return; - } + if (top >= 0) { + final TaskRecord task = mTaskHistory.get(top); + if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG_STACK, + "moveHomeStackTaskToTop: moving " + task); + mTaskHistory.remove(top); + mTaskHistory.add(top, task); + updateTaskMovement(task, true); } } @@ -4660,7 +4635,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If true, we should resume the home activity next if the task we are moving to the // back is over the home stack. We force to false if the task we are moving to back // is the home task and we don't want it resumed after moving to the back. - final boolean canGoHome = !tr.isHomeTask() && tr.isOverHomeStack(); + final boolean canGoHome = !tr.isActivityTypeHome() && tr.isOverHomeStack(); if (canGoHome) { final TaskRecord nextTask = getNextTask(tr); if (nextTask != null) { @@ -4695,7 +4670,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } if (taskNdx == 1) { // Set the last task before tr to go to home. - task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); } } @@ -4705,7 +4680,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Not ready yet! return false; } - tr.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); + tr.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); return mStackSupervisor.resumeHomeStackTask(null, "moveTaskToBack"); } @@ -4919,7 +4894,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } return true; } - if (r.isHomeActivity()) { + if (r.isActivityTypeHome()) { if (homeActivity != null && homeActivity.equals(r.realActivity)) { Slog.i(TAG, "Skip force-stop again " + r); continue; @@ -4962,7 +4937,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai int numActivities = 0; int numRunning = 0; final ArrayList<ActivityRecord> activities = task.mActivities; - if (!allowed && !task.isHomeTask() && task.effectiveUid != callingUid) { + if (!allowed && !task.isActivityTypeHome() && task.effectiveUid != callingUid) { continue; } for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { @@ -5158,7 +5133,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (task.isOverHomeStack() && taskNdx < topTaskNdx) { final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1); if (!nextTask.isOverHomeStack() && !nextTask.isOverAssistantStack()) { - nextTask.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + nextTask.setTaskToReturnTo(ACTIVITY_TYPE_HOME); } } mTaskHistory.remove(task); @@ -5214,9 +5189,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - boolean toTop, int type) { - TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession, - voiceInteractor, type); + boolean toTop) { + final TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession, + voiceInteractor); // add the task to stack first, mTaskPositioner might need the stack association addTask(task, toTop, "createTaskRecord"); final boolean isLockscreenShown = diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 64cbe2bacbf1..e4a2273387be 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -33,6 +33,9 @@ import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; @@ -67,8 +70,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.ANIMATE; import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG; -import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityStack.ActivityState.DESTROYED; import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING; @@ -689,7 +690,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } if (prev != null) { - prev.getTask().setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); + prev.getTask().setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); } mHomeStack.moveHomeStackTaskToTop(); @@ -1268,6 +1269,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D r.app = app; + if (mKeyguardController.isKeyguardLocked()) { + r.notifyUnknownVisibilityLaunched(); + } + // Have the window manager re-evaluate the orientation of the screen based on the new // activity order. Note that as a result of this, it can call back into the activity // manager with a new orientation. We don't care about that, because the activity is @@ -1294,9 +1299,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D r.setVisibility(true); } - if (mKeyguardController.isKeyguardLocked()) { - r.notifyUnknownVisibilityLaunched(); - } final int applicationInfoUid = (r.info.applicationInfo != null) ? r.info.applicationInfo.uid : -1; if ((r.userId != app.userId) || (r.appInfo.uid != applicationInfoUid)) { @@ -1342,7 +1344,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D + " newIntents=" + newIntents + " andResume=" + andResume); EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, r.userId, System.identityHashCode(r), task.taskId, r.shortComponentName); - if (r.isHomeActivity()) { + if (r.isActivityTypeHome()) { // Home process is the root process of the task. mService.mHomeProcess = task.mActivities.get(0).app; } @@ -2077,7 +2079,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) { // Caller wants the home activity moved with it. To accomplish this, // we'll just indicate that this task returns to the home task. - task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); } ActivityStack currentStack = task.getStack(); if (currentStack == null) { @@ -2263,11 +2265,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ArrayList<TaskRecord> tasks = mHomeStack.getAllTasks(); for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = tasks.get(taskNdx); - if (task.isHomeTask()) { + if (task.isActivityTypeHome()) { final ArrayList<ActivityRecord> activities = task.mActivities; for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = activities.get(activityNdx); - if (r.isHomeActivity() + if (r.isActivityTypeHome() && ((userId == UserHandle.USER_ALL) || (r.userId == userId))) { return r; } @@ -2400,8 +2402,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Update the return-to to reflect where the pinned stack task was moved // from so that we retain the stack that was previously visible if the // pinned stack is recreated. See moveActivityToPinnedStackLocked(). - task.setTaskToReturnTo(isFullscreenStackVisible && onTop ? - APPLICATION_ACTIVITY_TYPE : HOME_ACTIVITY_TYPE); + task.setTaskToReturnTo(isFullscreenStackVisible ? + ACTIVITY_TYPE_STANDARD : ACTIVITY_TYPE_HOME); } // Defer resume until all the tasks have been moved to the fullscreen stack task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, @@ -2921,7 +2923,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // move the home stack forward if we are currently entering picture-in-picture // while pausing because that changes the focused stack and may prevent the new // starting activity from resuming. - if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE + if (moveHomeStackToFront && task.returnsToHomeTask() && (r.state == RESUMED || !r.supportsEnterPipOnTaskSwitch)) { // Move the home stack forward if the task we just moved to the pinned stack // was launched from home so home should be visible behind it. @@ -2940,8 +2942,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // ensures that all the necessary work to migrate states in the old and new stacks // is also done. final TaskRecord newTask = task.getStack().createTaskRecord( - getNextTaskIdForUserLocked(r.userId), r.info, r.intent, null, null, true, - r.mActivityType); + getNextTaskIdForUserLocked(r.userId), r.info, r.intent, null, null, true); r.reparent(newTask, MAX_VALUE, "moveActivityToStack"); // Defer resume until below, and do not schedule PiP changes until we animate below @@ -3013,7 +3014,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - if (!checkActivityBelongsInStack(r, stack)) { + if (!r.hasCompatibleActivityType(stack)) { if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack); continue; @@ -3041,21 +3042,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return affinityMatch; } - /** - * Checks that for the given activity {@param r}, its activity type matches the {@param stack} - * type. - */ - private boolean checkActivityBelongsInStack(ActivityRecord r, ActivityStack stack) { - if (r.isHomeActivity()) { - return stack.isHomeStack(); - } else if (r.isRecentsActivity()) { - return stack.isRecentsStack(); - } else if (r.isAssistantActivity()) { - return stack.isAssistantStack(); - } - return true; - } - ActivityRecord findActivityLocked(Intent intent, ActivityInfo info, boolean compareIntentFilters) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { @@ -3438,7 +3424,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (stack == null) { stack = mHomeStack; } - final boolean homeInFront = stack.isHomeStack(); + final boolean homeInFront = stack.isActivityTypeHome(); if (stack.isOnHomeDisplay()) { stack.moveToFront("switchUserOnHomeDisplay"); } else { @@ -4038,7 +4024,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY) || StackId.isDynamicStack(preferredStackId); if (((!isStackDockedInEffect(actualStackId) && preferredStackId != DOCKED_STACK_ID) - && !isSecondaryDisplayPreferred) || task.isHomeTask()) { + && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) { return; } @@ -4135,31 +4121,29 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return; } - scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds, false /* immediate */); + scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds); } - void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds, - boolean immediate) { - - if (immediate) { - mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG); - for (int i = task.mActivities.size() - 1; i >= 0; i--) { - final ActivityRecord r = task.mActivities.get(i); - if (r.app != null && r.app.thread != null) { - r.updatePictureInPictureMode(targetStackBounds); - } - } - } else { - for (int i = task.mActivities.size() - 1; i >= 0; i--) { - final ActivityRecord r = task.mActivities.get(i); - if (r.app != null && r.app.thread != null) { - mPipModeChangedActivities.add(r); - } + void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) { + for (int i = task.mActivities.size() - 1; i >= 0; i--) { + final ActivityRecord r = task.mActivities.get(i); + if (r.app != null && r.app.thread != null) { + mPipModeChangedActivities.add(r); } - mPipModeChangedTargetStackBounds = targetStackBounds; + } + mPipModeChangedTargetStackBounds = targetStackBounds; + + if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) { + mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG); + } + } - if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) { - mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG); + void updatePictureInPictureMode(TaskRecord task, Rect targetStackBounds, boolean forceUpdate) { + mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG); + for (int i = task.mActivities.size() - 1; i >= 0; i--) { + final ActivityRecord r = task.mActivities.get(i); + if (r.app != null && r.app.thread != null) { + r.updatePictureInPictureMode(targetStackBounds, forceUpdate); } } } @@ -4221,7 +4205,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D synchronized (mService) { for (int i = mPipModeChangedActivities.size() - 1; i >= 0; i--) { final ActivityRecord r = mPipModeChangedActivities.remove(i); - r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds); + r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds, + false /* forceUpdate */); } } } break; diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 235477e24a27..16abcfb620d9 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -36,6 +36,9 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; import static android.app.ActivityManager.StackId.isDynamicStack; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; @@ -72,9 +75,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAV import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.ANIMATE; -import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.ASSISTANT_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityStack.ActivityState.RESUMED; import static com.android.server.am.ActivityStack.STACK_INVISIBLE; import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED; @@ -1507,7 +1507,7 @@ class ActivityStarter { // There can be one and only one instance of single instance activity in the // history, and it is always in its own unique task, so we do a special search. intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info, - mStartActivity.isHomeActivity()); + mStartActivity.isActivityTypeHome()); } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { // For the launch adjacent case we only want to put the activity in an existing // task if the activity already exists in the history. @@ -1669,21 +1669,21 @@ class ActivityStarter { if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { // Caller wants to appear on home activity. - task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); return; - } else if (focusedStack == null || focusedStack.isHomeStack()) { + } else if (focusedStack == null || focusedStack.isActivityTypeHome()) { // Task will be launched over the home stack, so return home. - task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); return; } else if (focusedStack != null && focusedStack != task.getStack() && - focusedStack.isAssistantStack()) { + focusedStack.isActivityTypeAssistant()) { // Task was launched over the assistant stack, so return there - task.setTaskToReturnTo(ASSISTANT_ACTIVITY_TYPE); + task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); return; } // Else we are coming from an application stack so return to an application. - task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); + task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); } private void setTaskFromIntentActivity(ActivityRecord intentActivity) { @@ -1791,7 +1791,7 @@ class ActivityStarter { mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info, mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession, - mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity.mActivityType); + mVoiceInteractor, !mLaunchTaskBehind /* toTop */); addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask"); if (mLaunchBounds != null) { final int stackId = mTargetStack.mStackId; @@ -1995,7 +1995,7 @@ class ActivityStarter { final ActivityRecord prev = mTargetStack.topActivity(); final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord( mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info, - mIntent, null, null, true, mStartActivity.mActivityType); + mIntent, null, null, true); addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask"); mTargetStack.positionChildWindowContainerAtTop(task); if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity @@ -2124,7 +2124,7 @@ class ActivityStarter { canUseFocusedStack = true; break; case ASSISTANT_STACK_ID: - canUseFocusedStack = r.isAssistantActivity(); + canUseFocusedStack = r.isActivityTypeAssistant(); break; case DOCKED_STACK_ID: // Any activity which supports split screen can go in the docked stack. @@ -2155,13 +2155,13 @@ class ActivityStarter { // If the activity is of a specific type, return the associated stack, creating it if // necessary - if (r.isHomeActivity()) { + if (r.isActivityTypeHome()) { return mSupervisor.mHomeStack; } - if (r.isRecentsActivity()) { + if (r.isActivityTypeRecents()) { return mSupervisor.getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, ON_TOP); } - if (r.isAssistantActivity()) { + if (r.isActivityTypeAssistant()) { return mSupervisor.getStack(ASSISTANT_STACK_ID, CREATE_IF_NEEDED, ON_TOP); } @@ -2254,9 +2254,9 @@ class ActivityStarter { case PINNED_STACK_ID: return r.supportsPictureInPicture(); case RECENTS_STACK_ID: - return r.isRecentsActivity(); + return r.isActivityTypeRecents(); case ASSISTANT_STACK_ID: - return r.isAssistantActivity(); + return r.isActivityTypeAssistant(); default: if (StackId.isDynamicStack(stackId)) { return r.canBeLaunchedOnDisplay(displayId); diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index fc03db1203bd..d1424c8d43f2 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -55,7 +55,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Set; import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST; @@ -676,7 +675,7 @@ class AppErrors { && (mService.mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = activities.get(activityNdx); - if (r.isHomeActivity()) { + if (r.isActivityTypeHome()) { Log.i(TAG, "Clearing package preferred activities from " + r.packageName); try { ActivityThread.getPackageManager() diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java index a1b95f9de05a..86ee3f4ba215 100644 --- a/services/core/java/com/android/server/am/PinnedActivityStack.java +++ b/services/core/java/com/android/server/am/PinnedActivityStack.java @@ -92,15 +92,16 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> return mWindowContainerController.deferScheduleMultiWindowModeChanged(); } - public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) { + public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds, + boolean forceUpdate) { // It is guaranteed that the activities requiring the update will be in the pinned stack at // this point (either reparented before the animation into PiP, or before reparenting after // the animation out of PiP) synchronized(this) { ArrayList<TaskRecord> tasks = getAllTasks(); for (int i = 0; i < tasks.size(); i++ ) { - mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(tasks.get(i), - targetStackBounds, true /* immediate */); + mStackSupervisor.updatePictureInPictureMode(tasks.get(i), targetStackBounds, + forceUpdate); } } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 15789309cf7a..5c0d587a1202 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -26,6 +26,11 @@ import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; @@ -44,6 +49,7 @@ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; + import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; @@ -54,15 +60,12 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.ASSISTANT_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; -import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN; import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING; import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP; import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; + import static java.lang.Integer.MAX_VALUE; import android.annotation.IntDef; @@ -75,6 +78,7 @@ import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IActivityManager; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -135,6 +139,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi private static final String ATTR_USERID = "user_id"; private static final String ATTR_USER_SETUP_COMPLETE = "user_setup_complete"; private static final String ATTR_EFFECTIVE_UID = "effective_uid"; + @Deprecated private static final String ATTR_TASKTYPE = "task_type"; private static final String ATTR_FIRSTACTIVETIME = "first_active_time"; private static final String ATTR_LASTACTIVETIME = "last_active_time"; @@ -248,9 +253,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi /** Current stack. Setter must always be used to update the value. */ private ActivityStack mStack; - /** Takes on same set of values as ActivityRecord.mActivityType */ - int taskType; - /** Takes on same value as first root activity */ boolean isPersistable = false; int maxRecents; @@ -260,10 +262,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * (positive) or back (negative). Absolute value indicates time. */ long mLastTimeMoved = System.currentTimeMillis(); - /** Indication of what to run next when task exits. Use ActivityRecord types. - * ActivityRecord.APPLICATION_ACTIVITY_TYPE indicates to resume the task below this one in the - * task stack. */ - private int mTaskToReturnTo = APPLICATION_ACTIVITY_TYPE; + /** Indication of what to run next when task exits. */ + // TODO: Shouldn't be needed if we have things in visual order. I.e. we stop using stacks or + // have a stack per standard application type... + /*@WindowConfiguration.ActivityType*/ + private int mTaskToReturnTo = ACTIVITY_TYPE_STANDARD; /** If original intent did not allow relinquishing task identity, save that information */ private boolean mNeverRelinquishIdentity = true; @@ -316,7 +319,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi private TaskWindowContainerController mWindowContainerController; TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, - IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, int type) { + IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; @@ -329,7 +332,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mActivities = new ArrayList<>(); mCallingUid = info.applicationInfo.uid; mCallingPackage = info.packageName; - taskType = type; setIntent(_intent, info); setMinDimensions(info); touchActiveTime(); @@ -358,8 +360,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi maxRecents = Math.min(Math.max(info.maxRecents, 1), ActivityManager.getMaxAppRecentsLimitStatic()); - taskType = APPLICATION_ACTIVITY_TYPE; - mTaskToReturnTo = HOME_ACTIVITY_TYPE; + mTaskToReturnTo = ACTIVITY_TYPE_HOME; lastTaskDescription = _taskDescription; touchActiveTime(); mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity); @@ -368,7 +369,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi private TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, - boolean _autoRemoveRecents, boolean _askedCompatMode, int _taskType, int _userId, + boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime, long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription, @@ -393,8 +394,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi isAvailable = true; autoRemoveRecents = _autoRemoveRecents; askedCompatMode = _askedCompatMode; - taskType = _taskType; - mTaskToReturnTo = HOME_ACTIVITY_TYPE; + mTaskToReturnTo = ACTIVITY_TYPE_HOME; userId = _userId; mUserSetupComplete = userSetupComplete; effectiveUid = _effectiveUid; @@ -433,8 +433,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi final Configuration overrideConfig = getOverrideConfiguration(); setWindowContainerController(new TaskWindowContainerController(taskId, this, getStack().getWindowContainerController(), userId, bounds, overrideConfig, - mResizeMode, mSupportsPictureInPicture, isHomeTask(), onTop, showForAllUsers, - lastTaskDescription)); + mResizeMode, mSupportsPictureInPicture, onTop, + showForAllUsers, lastTaskDescription)); } /** @@ -887,16 +887,16 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return this.intent.filterEquals(intent); } - void setTaskToReturnTo(int taskToReturnTo) { - mTaskToReturnTo = (taskToReturnTo == RECENTS_ACTIVITY_TYPE) - ? HOME_ACTIVITY_TYPE : taskToReturnTo; + void setTaskToReturnTo(/*@WindowConfiguration.ActivityType*/ int taskToReturnTo) { + mTaskToReturnTo = taskToReturnTo == ACTIVITY_TYPE_RECENTS + ? ACTIVITY_TYPE_HOME : taskToReturnTo; } void setTaskToReturnTo(ActivityRecord source) { - if (source.isRecentsActivity()) { - setTaskToReturnTo(RECENTS_ACTIVITY_TYPE); - } else if (source.isAssistantActivity()) { - setTaskToReturnTo(ASSISTANT_ACTIVITY_TYPE); + if (source.isActivityTypeRecents()) { + setTaskToReturnTo(ACTIVITY_TYPE_RECENTS); + } else if (source.isActivityTypeAssistant()) { + setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); } } @@ -904,6 +904,14 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return mTaskToReturnTo; } + boolean returnsToHomeTask() { + return mTaskToReturnTo == ACTIVITY_TYPE_HOME; + } + + boolean returnsToStandardTask() { + return mTaskToReturnTo == ACTIVITY_TYPE_STANDARD; + } + void setPrevAffiliate(TaskRecord prevAffiliate) { mPrevAffiliate = prevAffiliate; mPrevAffiliateTaskId = prevAffiliate == null ? INVALID_TASK_ID : prevAffiliate.taskId; @@ -1133,6 +1141,16 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi addActivityAtIndex(mActivities.size(), r); } + @Override + /*@WindowConfiguration.ActivityType*/ + public int getActivityType() { + final int applicationType = super.getActivityType(); + if (applicationType != ACTIVITY_TYPE_UNDEFINED || mActivities.isEmpty()) { + return applicationType; + } + return mActivities.get(0).getActivityType(); + } + /** * Adds an activity {@param r} at the given {@param index}. The activity {@param r} must either * be in the current task or unparented to any task. @@ -1153,7 +1171,17 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } // Only set this based on the first activity if (mActivities.isEmpty()) { - taskType = r.mActivityType; + // TODO: propagating this change to the WM side...Should probably be done by having + // ConfigurationContainer change listener that the WindowContainerController registers + // for. + if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) { + // Normally non-standard activity type for the activity record will be set when the + // object is created, however we delay setting the standard application type until + // this point so that the task can set the type for additional activities added in + // the else condition below. + r.setActivityType(ACTIVITY_TYPE_STANDARD); + } + setActivityType(r.getActivityType()); isPersistable = r.isPersistable(); mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; @@ -1162,7 +1190,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi ActivityManager.getMaxAppRecentsLimitStatic()); } else { // Otherwise make all added activities match this one. - r.mActivityType = taskType; + r.setActivityType(getActivityType()); } final int size = mActivities.size(); @@ -1427,28 +1455,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return false; } - boolean isHomeTask() { - return taskType == HOME_ACTIVITY_TYPE; - } - - boolean isRecentsTask() { - return taskType == RECENTS_ACTIVITY_TYPE; - } - - boolean isAssistantTask() { - return taskType == ASSISTANT_ACTIVITY_TYPE; - } - - boolean isApplicationTask() { - return taskType == APPLICATION_ACTIVITY_TYPE; - } - boolean isOverHomeStack() { - return mTaskToReturnTo == HOME_ACTIVITY_TYPE; + return mTaskToReturnTo == ACTIVITY_TYPE_HOME; } boolean isOverAssistantStack() { - return mTaskToReturnTo == ASSISTANT_ACTIVITY_TYPE; + return mTaskToReturnTo == ACTIVITY_TYPE_ASSISTANT; } private boolean isResizeable(boolean checkSupportsPip) { @@ -1642,7 +1654,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi out.attribute(null, ATTR_USERID, String.valueOf(userId)); out.attribute(null, ATTR_USER_SETUP_COMPLETE, String.valueOf(mUserSetupComplete)); out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid)); - out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType)); out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime)); out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime)); out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved)); @@ -1712,7 +1723,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi boolean rootHasReset = false; boolean autoRemoveRecents = false; boolean askedCompatMode = false; - int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE; + int taskType = 0; int userId = 0; boolean userSetupComplete = true; int effectiveUid = -1; @@ -1867,7 +1878,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // they are marked as RESIZE_MODE_RESIZEABLE to RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION // since we didn't have that differentiation before version 1 and the system didn't // resize home activities before then. - if (taskType == HOME_ACTIVITY_TYPE && resizeMode == RESIZE_MODE_RESIZEABLE) { + if (taskType == 1 /* old home type */ && resizeMode == RESIZE_MODE_RESIZEABLE) { resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; } } else { @@ -1883,7 +1894,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent, affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset, - autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription, + autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription, activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, callingUid, callingPackage, resizeMode, supportsPictureInPicture, privileged, @@ -2105,13 +2116,13 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * The task will be moved (and stack focus changed) later if necessary. */ int getLaunchStackId() { - if (isRecentsTask()) { + if (isActivityTypeRecents()) { return RECENTS_STACK_ID; } - if (isHomeTask()) { + if (isActivityTypeHome()) { return HOME_STACK_ID; } - if (isAssistantTask()) { + if (isActivityTypeAssistant()) { return ASSISTANT_STACK_ID; } if (mBounds != null) { @@ -2190,12 +2201,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); } - if (autoRemoveRecents || isPersistable || taskType != 0 || mTaskToReturnTo != 0 - || numFullscreen != 0) { + if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() + || mTaskToReturnTo != ACTIVITY_TYPE_STANDARD || numFullscreen != 0) { pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents); pw.print(" isPersistable="); pw.print(isPersistable); pw.print(" numFullscreen="); pw.print(numFullscreen); - pw.print(" taskType="); pw.print(taskType); + pw.print(" activityType="); pw.print(getActivityType()); pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo); } if (rootWasReset || mNeverRelinquishIdentity || mReuseTask diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index faf4729ead35..91b15912fcb5 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -801,12 +801,23 @@ public class AudioService extends IAudioService.Stub public void systemReady() { sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE, 0, 0, null, 0); - try { - ActivityManager.getService().registerUidObserver(mUidObserver, - ActivityManager.UID_OBSERVER_CACHED | ActivityManager.UID_OBSERVER_GONE, - ActivityManager.PROCESS_STATE_UNKNOWN, null); - } catch (RemoteException e) { - // ignored; both services live in system_server + if (false) { + // This is turned off for now, because it is racy and thus causes apps to break. + // Currently banning a uid means that if an app tries to start playing an audio + // stream, that will be preventing, and unbanning it will not allow that stream + // to resume. However these changes in uid state are racy with what the app is doing, + // so that after taking a process out of the cached state we can't guarantee that + // we will unban the uid before the app actually tries to start playing audio. + // (To do that, the activity manager would need to wait until it knows for sure + // that the ban has been removed, before telling the app to do whatever it is + // supposed to do that caused it to go out of the cached state.) + try { + ActivityManager.getService().registerUidObserver(mUidObserver, + ActivityManager.UID_OBSERVER_CACHED | ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, null); + } catch (RemoteException e) { + // ignored; both services live in system_server + } } } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 79bed73a0ca0..c6998d6a108c 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -520,11 +520,12 @@ public final class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Receieved: " + action); } + final String pkgName = getPackageName(intent); + final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); + if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { // Purge the app's jobs if the whole package was just disabled. When this is // the case the component name will be a bare package name. - final String pkgName = getPackageName(intent); - final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); if (pkgName != null && pkgUid != -1) { final String[] changedComponents = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); @@ -544,7 +545,8 @@ public final class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "Removing jobs for package " + pkgName + " in user " + userId); } - cancelJobsForUid(pkgUid, "app package state changed"); + cancelJobsForPackageAndUid(pkgName, pkgUid, + "app disabled"); } } catch (RemoteException|IllegalArgumentException e) { /* @@ -573,7 +575,7 @@ public final class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); } - cancelJobsForUid(uidRemoved, "app uninstalled"); + cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled"); } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); @@ -584,8 +586,6 @@ public final class JobSchedulerService extends com.android.server.SystemService } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { // Has this package scheduled any jobs, such that we will take action // if it were to be force-stopped? - final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final String pkgName = intent.getData().getSchemeSpecificPart(); if (pkgUid != -1) { List<JobStatus> jobsForUid; synchronized (mLock) { @@ -604,13 +604,11 @@ public final class JobSchedulerService extends com.android.server.SystemService } } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) { // possible force-stop - final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final String pkgName = intent.getData().getSchemeSpecificPart(); if (pkgUid != -1) { if (DEBUG) { Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid); } - cancelJobsForPackageAndUid(pkgName, pkgUid); + cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped"); } } } @@ -790,13 +788,17 @@ public final class JobSchedulerService extends com.android.server.SystemService } } - void cancelJobsForPackageAndUid(String pkgName, int uid) { + void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) { + if ("android".equals(pkgName)) { + Slog.wtfStack(TAG, "Can't cancel all jobs for system package"); + return; + } synchronized (mLock) { final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid); for (int i = jobsForUid.size() - 1; i >= 0; i--) { final JobStatus job = jobsForUid.get(i); if (job.getSourcePackageName().equals(pkgName)) { - cancelJobImplLocked(job, null, "app force stopped"); + cancelJobImplLocked(job, null, reason); } } } @@ -811,8 +813,7 @@ public final class JobSchedulerService extends com.android.server.SystemService */ public void cancelJobsForUid(int uid, String reason) { if (uid == Process.SYSTEM_UID) { - // This really shouldn't happen. - Slog.wtfStack(TAG, "cancelJobsForUid() called for system uid"); + Slog.wtfStack(TAG, "Can't cancel all jobs for system uid"); return; } synchronized (mLock) { diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 031bdd0ee39c..d3fd3a992a31 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -261,6 +261,13 @@ public final class JobServiceContext implements ServiceConnection { return mRunningJob; } + /** + * Used only for debugging. Will return <code>"<null>"</code> if there is no job running. + */ + private String getRunningJobNameLocked() { + return mRunningJob != null ? mRunningJob.toShortString() : "<null>"; + } + /** Called externally when a job that was scheduled for execution should be cancelled. */ void cancelExecutingJobLocked(int reason, String debugReason) { doCancelLocked(reason, debugReason); @@ -522,7 +529,7 @@ public final class JobServiceContext implements ServiceConnection { /** Start the job on the service. */ private void handleServiceBoundLocked() { if (DEBUG) { - Slog.d(TAG, "handleServiceBound for " + mRunningJob.toShortString()); + Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked()); } if (mVerb != VERB_BINDING) { Slog.e(TAG, "Sending onStartJob for a job that isn't pending. " @@ -639,36 +646,34 @@ public final class JobServiceContext implements ServiceConnection { private void handleOpTimeoutLocked() { switch (mVerb) { case VERB_BINDING: - Slog.w(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() + - ", dropping."); + Slog.w(TAG, "Time-out while trying to bind " + getRunningJobNameLocked() + + ", dropping."); closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding"); break; case VERB_STARTING: // Client unresponsive - wedged or failed to respond in time. We don't really // know what happened so let's log it and notify the JobScheduler // FINISHED/NO-RETRY. - Slog.w(TAG, "No response from client for onStartJob " + - mRunningJob != null ? mRunningJob.toShortString() : "<null>"); + Slog.w(TAG, "No response from client for onStartJob " + + getRunningJobNameLocked()); closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting"); break; case VERB_STOPPING: // At least we got somewhere, so fail but ask the JobScheduler to reschedule. - Slog.w(TAG, "No response from client for onStopJob " + - mRunningJob != null ? mRunningJob.toShortString() : "<null>"); + Slog.w(TAG, "No response from client for onStopJob " + + getRunningJobNameLocked()); closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping"); break; case VERB_EXECUTING: // Not an error - client ran out of time. Slog.i(TAG, "Client timed out while executing (no jobFinished received), " + - "sending onStop: " + - mRunningJob != null ? mRunningJob.toShortString() : "<null>"); + "sending onStop: " + getRunningJobNameLocked()); mParams.setStopReason(JobParameters.REASON_TIMEOUT); sendStopMessageLocked("timeout while executing"); break; default: - Slog.e(TAG, "Handling timeout for an invalid job state: " + - mRunningJob != null ? mRunningJob.toShortString() : "<null>" - + ", dropping."); + Slog.e(TAG, "Handling timeout for an invalid job state: " + + getRunningJobNameLocked() + ", dropping."); closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout"); } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 922df1e3dba8..3795b7f3091c 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -18,9 +18,7 @@ package com.android.server.media; import com.android.internal.util.DumpUtils; import com.android.server.Watchdog; -import com.android.server.media.AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener; -import android.Manifest; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -96,9 +94,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<IBinder, ClientRecord>(); private int mCurrentUserId = -1; - private boolean mHasBluetoothRoute = false; + private boolean mGlobalBluetoothA2dpOn = false; private final IAudioService mAudioService; private final AudioPlaybackMonitor mAudioPlaybackMonitor; + private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); public MediaRouterService(Context context) { mContext = context; @@ -137,13 +136,39 @@ public final class MediaRouterService extends IMediaRouterService.Stub audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() { @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { - mHasBluetoothRoute = newRoutes.bluetoothName != null; + synchronized (mLock) { + if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) { + if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET + | AudioRoutesInfo.MAIN_HEADPHONES + | AudioRoutesInfo.MAIN_USB)) == 0) { + // headset was plugged out. + mGlobalBluetoothA2dpOn = newRoutes.bluetoothName != null; + } else { + // headset was plugged in. + mGlobalBluetoothA2dpOn = false; + } + mCurAudioRoutesInfo.mainType = newRoutes.mainType; + } + if (!TextUtils.equals( + newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { + if (newRoutes.bluetoothName == null) { + // BT was disconnected. + mGlobalBluetoothA2dpOn = false; + } else { + // BT was connected or changed. + mGlobalBluetoothA2dpOn = true; + } + mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; + } + } } }); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in the audio service."); } - mHasBluetoothRoute = (audioRoutes != null && audioRoutes.bluetoothName != null); + synchronized (mLock) { + mGlobalBluetoothA2dpOn = (audioRoutes != null && audioRoutes.bluetoothName != null); + } } public void systemRunning() { @@ -246,6 +271,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override + public boolean isGlobalBluetoothA2doOn() { + synchronized (mLock) { + return mGlobalBluetoothA2dpOn; + } + } + + // Binder call + @Override public void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan) { if (client == null) { @@ -346,7 +379,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub void restoreBluetoothA2dp() { try { - mAudioService.setBluetoothA2dpOn(mHasBluetoothRoute); + boolean a2dpOn = false; + synchronized (mLock) { + a2dpOn = mGlobalBluetoothA2dpOn; + } + Slog.v(TAG, "restoreBluetoothA2dp( " + a2dpOn + ")"); + mAudioService.setBluetoothA2dpOn(a2dpOn); } catch (RemoteException e) { Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn."); } @@ -354,12 +392,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub void restoreRoute(int uid) { ClientRecord clientRecord = null; - UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid)); - if (userRecord != null && userRecord.mClientRecords != null) { - for (ClientRecord cr : userRecord.mClientRecords) { - if (validatePackageName(uid, cr.mPackageName)) { - clientRecord = cr; - break; + synchronized (mLock) { + UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid)); + if (userRecord != null && userRecord.mClientRecords != null) { + for (ClientRecord cr : userRecord.mClientRecords) { + if (validatePackageName(uid, cr.mPackageName)) { + clientRecord = cr; + break; + } } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 9a76294cdd9e..0c9f65aeff98 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1476,6 +1476,19 @@ public class NotificationManagerService extends SystemService { savePolicyFile(); } + private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, + boolean fromApp, boolean fromListener) { + Preconditions.checkNotNull(group); + Preconditions.checkNotNull(pkg); + mRankingHelper.createNotificationChannelGroup(pkg, uid, group, + fromApp); + if (!fromListener) { + mListeners.notifyNotificationChannelGroupChanged(pkg, + UserHandle.of(UserHandle.getCallingUserId()), group, + NOTIFICATION_CHANNEL_OR_GROUP_ADDED); + } + } + private ArrayList<ComponentName> getSuppressors() { ArrayList<ComponentName> names = new ArrayList<ComponentName>(); for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) { @@ -1763,6 +1776,14 @@ public class NotificationManagerService extends SystemService { } @Override + public void updateNotificationChannelGroupForPackage(String pkg, int uid, + NotificationChannelGroup group) throws RemoteException { + enforceSystemOrSystemUI("Caller not system or systemui"); + createNotificationChannelGroup(pkg, uid, group, false, false); + savePolicyFile(); + } + + @Override public void createNotificationChannelGroups(String pkg, ParceledListSlice channelGroupList) throws RemoteException { checkCallerIsSystemOrSameApp(pkg); @@ -1770,12 +1791,7 @@ public class NotificationManagerService extends SystemService { final int groupSize = groups.size(); for (int i = 0; i < groupSize; i++) { final NotificationChannelGroup group = groups.get(i); - Preconditions.checkNotNull(group, "group in list is null"); - mRankingHelper.createNotificationChannelGroup(pkg, Binder.getCallingUid(), group, - true /* fromTargetApp */); - mListeners.notifyNotificationChannelGroupChanged(pkg, - UserHandle.of(UserHandle.getCallingUserId()), group, - NOTIFICATION_CHANNEL_OR_GROUP_ADDED); + createNotificationChannelGroup(pkg, Binder.getCallingUid(), group, true, false); } savePolicyFile(); } @@ -1921,6 +1937,14 @@ public class NotificationManagerService extends SystemService { } @Override + public NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage( + String pkg, int uid, String groupId, boolean includeDeleted) { + enforceSystemOrSystemUI("getPopulatedNotificationChannelGroupForPackage"); + return mRankingHelper.getNotificationChannelGroupWithChannels( + pkg, uid, groupId, includeDeleted); + } + + @Override public NotificationChannelGroup getNotificationChannelGroupForPackage( String groupId, String pkg, int uid) { enforceSystemOrSystemUI("getNotificationChannelGroupForPackage"); @@ -2923,6 +2947,17 @@ public class NotificationManagerService extends SystemService { } @Override + public void updateNotificationChannelGroupFromPrivilegedListener( + INotificationListener token, String pkg, UserHandle user, + NotificationChannelGroup group) throws RemoteException { + Preconditions.checkNotNull(user); + verifyPrivilegedListener(token, user); + createNotificationChannelGroup( + pkg, getUidForPackageAndUser(pkg, user), group, false, true); + savePolicyFile(); + } + + @Override public void updateNotificationChannelFromPrivilegedListener(INotificationListener token, String pkg, UserHandle user, NotificationChannel channel) throws RemoteException { Preconditions.checkNotNull(channel); @@ -3618,9 +3653,10 @@ public class NotificationManagerService extends SystemService { usageStats.registerSuspendedByAdmin(r); return isPackageSuspended; } - final boolean isBlocked = - mRankingHelper.getImportance(pkg, callingUid) == NotificationManager.IMPORTANCE_NONE + mRankingHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup()) + || mRankingHelper.getImportance(pkg, callingUid) + == NotificationManager.IMPORTANCE_NONE || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE; if (isBlocked) { Slog.e(TAG, "Suppressing notification from package by user request."); diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 36da04dfc3c6..b5ef1c60f607 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -29,6 +29,7 @@ public interface RankingConfig { void setShowBadge(String packageName, int uid, boolean showBadge); boolean canShowBadge(String packageName, int uid); boolean badgingEnabled(UserHandle userHandle); + boolean isGroupBlocked(String packageName, int uid, String groupId); Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 3386fe832e0f..1736a74e1270 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -215,6 +215,7 @@ public class RankingHelper implements RankingConfig { if (!TextUtils.isEmpty(id)) { NotificationChannelGroup group = new NotificationChannelGroup(id, groupName); + group.populateFromXml(parser); r.groups.put(id, group); } } @@ -493,6 +494,19 @@ public class RankingHelper implements RankingConfig { updateConfig(); } + @Override + public boolean isGroupBlocked(String packageName, int uid, String groupId) { + if (groupId == null) { + return false; + } + Record r = getOrCreateRecord(packageName, uid); + NotificationChannelGroup group = r.groups.get(groupId); + if (group == null) { + return false; + } + return group.isBlocked(); + } + int getPackagePriority(String pkg, int uid) { return getOrCreateRecord(pkg, uid).priority; } @@ -514,9 +528,16 @@ public class RankingHelper implements RankingConfig { } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); if (!group.equals(oldGroup)) { - // will log for new entries as well as name changes + // will log for new entries as well as name/description changes MetricsLogger.action(getChannelGroupLog(group.getId(), pkg)); } + if (oldGroup != null) { + group.setChannels(oldGroup.getChannels()); + + if (fromTargetApp) { + group.setBlocked(oldGroup.isBlocked()); + } + } r.groups.put(group.getId(), group); } @@ -552,6 +573,9 @@ public class RankingHelper implements RankingConfig { existing.setName(channel.getName().toString()); existing.setDescription(channel.getDescription()); existing.setBlockableSystem(channel.isBlockableSystem()); + if (existing.getGroup() == null) { + existing.setGroup(channel.getGroup()); + } // Apps are allowed to downgrade channel importance if the user has not changed any // fields on this channel yet. @@ -684,6 +708,27 @@ public class RankingHelper implements RankingConfig { } } + public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg, + int uid, String groupId, boolean includeDeleted) { + Preconditions.checkNotNull(pkg); + Record r = getRecord(pkg, uid); + if (r == null || groupId == null || !r.groups.containsKey(groupId)) { + return null; + } + NotificationChannelGroup group = r.groups.get(groupId).clone(); + group.setChannels(new ArrayList<>()); + int N = r.channels.size(); + for (int i = 0; i < N; i++) { + final NotificationChannel nc = r.channels.valueAt(i); + if (includeDeleted || !nc.isDeleted()) { + if (groupId.equals(nc.getGroup())) { + group.addChannel(nc); + } + } + } + return group; + } + public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg, int uid) { Preconditions.checkNotNull(pkg); @@ -710,6 +755,7 @@ public class RankingHelper implements RankingConfig { NotificationChannelGroup ncg = groups.get(nc.getGroup()); if (ncg == null) { ncg = r.groups.get(nc.getGroup()).clone(); + ncg.setChannels(new ArrayList<>()); groups.put(nc.getGroup(), ncg); } diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 04d91f882d04..807c343d0d10 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -92,26 +92,10 @@ class IdmapManager { return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath())).isFile(); } - boolean isDangerous(@NonNull final PackageInfo overlayPackage, final int userId) { - // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible - return isDangerous(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath())); - } - private String getIdmapPath(@NonNull final String baseCodePath) { final StringBuilder sb = new StringBuilder("/data/resource-cache/"); sb.append(baseCodePath.substring(1).replace('/', '@')); sb.append("@idmap"); return sb.toString(); } - - private boolean isDangerous(@NonNull final String idmapPath) { - try (DataInputStream dis = new DataInputStream(new FileInputStream(idmapPath))) { - final int magic = dis.readInt(); - final int version = dis.readInt(); - final int dangerous = dis.readInt(); - return dangerous != 0; - } catch (IOException e) { - return true; - } - } } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index db6e9749535b..c3957f432f4c 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -169,8 +169,9 @@ final class OverlayManagerServiceImpl { } final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId); - updateAllOverlaysForTarget(packageName, userId, targetPackage); - mListener.onOverlaysChanged(packageName, userId); + if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) { + mListener.onOverlaysChanged(packageName, userId); + } } void onTargetPackageChanged(@NonNull final String packageName, final int userId) { @@ -210,7 +211,9 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId); } - updateAllOverlaysForTarget(packageName, userId, null); + if (updateAllOverlaysForTarget(packageName, userId, null)) { + mListener.onOverlaysChanged(packageName, userId); + } } /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index d21100c0cf72..68913c3a887a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -7820,13 +7820,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { case HapticFeedbackConstants.VIRTUAL_KEY: return VibrationEffect.get(VibrationEffect.EFFECT_CLICK); case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE: - return VibrationEffect.get(VibrationEffect.EFFECT_TICK); + return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false); case HapticFeedbackConstants.KEYBOARD_PRESS: // == HapticFeedbackConstants.KEYBOARD_TAP return VibrationEffect.get(VibrationEffect.EFFECT_CLICK); case HapticFeedbackConstants.KEYBOARD_RELEASE: - return VibrationEffect.get(VibrationEffect.EFFECT_TICK); + return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false); case HapticFeedbackConstants.TEXT_HANDLE_MOVE: - return VibrationEffect.get(VibrationEffect.EFFECT_TICK); + return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false); default: return null; } diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index c2edc04db2e1..c76b90593ab3 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -450,7 +450,7 @@ public class AppWindowAnimator { return isAnimating; } - void dump(PrintWriter pw, String prefix, boolean dumpAll) { + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken); pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator); pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen); diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index f628d5e6de27..66e0a152e09b 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -365,7 +365,6 @@ public class AppWindowContainerController // Now that the app is going invisible, we can remove it. It will be restarted // if made visible again. wtoken.removeDeadWindows(); - mService.mUnknownAppVisibilityController.appRemovedOrHidden(wtoken); } else { if (!mService.mAppTransition.isTransitionSet() && mService.mAppTransition.isReady()) { diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index d625003305b6..f70035ca9c92 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -362,10 +362,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean runningAppAnimation = false; + if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) { + mAppAnimator.setNullAnimation(); + } if (transit != AppTransition.TRANSIT_UNSET) { - if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) { - mAppAnimator.setNullAnimation(); - } if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) { delayed = runningAppAnimation = true; } @@ -1618,6 +1618,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (mRemovingFromDisplay) { pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay); } + if (mAppAnimator.isAnimating()) { + mAppAnimator.dump(pw, prefix + " "); + } } @Override diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java index cff2fadd7649..7953ee430934 100644 --- a/services/core/java/com/android/server/wm/BoundsAnimationController.java +++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java @@ -21,7 +21,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.animation.AnimationHandler; -import android.animation.AnimationHandler.AnimationFrameCallbackProvider; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.IntDef; @@ -32,13 +31,11 @@ import android.os.IBinder; import android.os.Debug; import android.util.ArrayMap; import android.util.Slog; -import android.view.Choreographer; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.WindowManagerInternal; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -142,9 +139,6 @@ public class BoundsAnimationController { // True if this this animation was canceled and will be replaced the another animation from // the same {@link #BoundsAnimationTarget} target. private boolean mSkipFinalResize; - // True if this animation replaced a previous animation of the same - // {@link #BoundsAnimationTarget} target. - private final boolean mSkipAnimationStart; // True if this animation was canceled by the user, not as a part of a replacing animation private boolean mSkipAnimationEnd; @@ -159,6 +153,7 @@ public class BoundsAnimationController { // Whether to schedule PiP mode changes on animation start/end private @SchedulePipModeChangedState int mSchedulePipModeChangedState; + private @SchedulePipModeChangedState int mPrevSchedulePipModeChangedState; // Depending on whether we are animating from // a smaller to a larger size @@ -171,14 +166,14 @@ public class BoundsAnimationController { BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState, - boolean moveFromFullscreen, boolean moveToFullscreen, - boolean replacingExistingAnimation) { + @SchedulePipModeChangedState int prevShedulePipModeChangedState, + boolean moveFromFullscreen, boolean moveToFullscreen) { super(); mTarget = target; mFrom.set(from); mTo.set(to); - mSkipAnimationStart = replacingExistingAnimation; mSchedulePipModeChangedState = schedulePipModeChangedState; + mPrevSchedulePipModeChangedState = prevShedulePipModeChangedState; mMoveFromFullscreen = moveFromFullscreen; mMoveToFullscreen = moveToFullscreen; addUpdateListener(this); @@ -200,7 +195,7 @@ public class BoundsAnimationController { @Override public void onAnimationStart(Animator animation) { if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget - + " mSkipAnimationStart=" + mSkipAnimationStart + + " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState); mFinishAnimationAfterTransition = false; mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth, @@ -210,18 +205,26 @@ public class BoundsAnimationController { // running updateBooster(); - // Ensure that we have prepared the target for animation before - // we trigger any size changes, so it can swap surfaces - // in to appropriate modes, or do as it wishes otherwise. - if (!mSkipAnimationStart) { + // Ensure that we have prepared the target for animation before we trigger any size + // changes, so it can swap surfaces in to appropriate modes, or do as it wishes + // otherwise. + if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) { mTarget.onAnimationStart(mSchedulePipModeChangedState == - SCHEDULE_PIP_MODE_CHANGED_ON_START); + SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */); // When starting an animation from fullscreen, pause here and wait for the // windows-drawn signal before we start the rest of the transition down into PiP. if (mMoveFromFullscreen) { pause(); } + } else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END && + mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { + // We are replacing a running animation into PiP, but since it hasn't completed, the + // client will not currently receive any picture-in-picture mode change callbacks. + // However, we still need to report to them that they are leaving PiP, so this will + // force an update via a mode changed callback. + mTarget.onAnimationStart(true /* schedulePipModeChangedCallback */, + true /* forceUpdate */); } // Immediately update the task bounds if they have to become larger, but preserve @@ -388,6 +391,8 @@ public class BoundsAnimationController { boolean moveFromFullscreen, boolean moveToFullscreen) { final BoundsAnimator existing = mRunningAnimations.get(target); final boolean replacing = existing != null; + @SchedulePipModeChangedState int prevSchedulePipModeChangedState = + NO_PIP_MODE_CHANGED_CALLBACKS; if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to + " schedulePipModeChangedState=" + schedulePipModeChangedState @@ -403,6 +408,9 @@ public class BoundsAnimationController { return existing; } + // Save the previous state + prevSchedulePipModeChangedState = existing.mSchedulePipModeChangedState; + // Update the PiP callback states if we are replacing the animation if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { @@ -428,7 +436,8 @@ public class BoundsAnimationController { existing.cancel(); } final BoundsAnimator animator = new BoundsAnimator(target, from, to, - schedulePipModeChangedState, moveFromFullscreen, moveToFullscreen, replacing); + schedulePipModeChangedState, prevSchedulePipModeChangedState, + moveFromFullscreen, moveToFullscreen); mRunningAnimations.put(target, animator); animator.setFloatValues(0f, 1f); animator.setDuration((animationDuration != -1 ? animationDuration diff --git a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java index 8b1bf7bf77dc..647a2d6deac6 100644 --- a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java +++ b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java @@ -31,7 +31,7 @@ interface BoundsAnimationTarget { * @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed * callbacks */ - void onAnimationStart(boolean schedulePipModeChangedCallback); + void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate); /** * Sets the size of the target (without any intermediate steps, like scheduling animation) diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index ace40c1f1e73..21b67f1f9821 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -16,6 +16,13 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.activityTypeToString; + import android.app.WindowConfiguration; import android.content.res.Configuration; @@ -41,6 +48,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { */ private Configuration mMergedOverrideConfiguration = new Configuration(); + // TODO: Can't have ag/2592611 soon enough! + private final Configuration mTmpConfig = new Configuration(); + /** * Returns full configuration applied to this configuration container. * This method should be used for getting settings applied in each particular level of the @@ -120,10 +130,56 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** Sets the windowing mode for the configuration container. */ - public void setWindowingMode(/* @WindowConfiguration.WindowingMode...triggers Jack compiler bug...*/ - int windowingMode) { - mOverrideConfiguration.windowConfiguration.setWindowingMode(windowingMode); - onOverrideConfigurationChanged(mOverrideConfiguration); + public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) { + mTmpConfig.setTo(getOverrideConfiguration()); + mTmpConfig.windowConfiguration.setWindowingMode(windowingMode); + onOverrideConfigurationChanged(mTmpConfig); + } + + /** Returns the activity type associated with the the configuration container. */ + /*@WindowConfiguration.ActivityType*/ + public int getActivityType() { + return mFullConfiguration.windowConfiguration.getActivityType(); + } + + /** Sets the activity type to associate with the configuration container. */ + public void setActivityType(/*@WindowConfiguration.ActivityType*/ int activityType) { + int currentActivityType = getActivityType(); + if (currentActivityType == activityType) { + return; + } + if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) { + throw new IllegalStateException("Can't change activity type once set: " + this + + " activityType=" + activityTypeToString(activityType)); + } + mTmpConfig.setTo(getOverrideConfiguration()); + mTmpConfig.windowConfiguration.setActivityType(activityType); + onOverrideConfigurationChanged(mTmpConfig); + } + + public boolean isActivityTypeHome() { + return getActivityType() == ACTIVITY_TYPE_HOME; + } + + public boolean isActivityTypeRecents() { + return getActivityType() == ACTIVITY_TYPE_RECENTS; + } + + public boolean isActivityTypeAssistant() { + return getActivityType() == ACTIVITY_TYPE_ASSISTANT; + } + + public boolean isActivityTypeStandard() { + return getActivityType() == ACTIVITY_TYPE_STANDARD; + } + + public boolean hasCompatibleActivityType(ConfigurationContainer other) { + /*@WindowConfiguration.ActivityType*/ int thisType = getActivityType(); + /*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType(); + + return thisType == otherType + || thisType == ACTIVITY_TYPE_UNDEFINED + || otherType == ACTIVITY_TYPE_UNDEFINED; } /** diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index ce21991e1b3e..030b986ef0e9 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -608,8 +608,7 @@ public class DockedStackDividerController implements DimLayerUser { final TaskStack fullscreenStack = mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID); final boolean homeVisible = homeTask.getTopVisibleAppToken() != null; - final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisible()) - || (homeStack.hasMultipleTaskWithHomeTaskNotTop()); + final boolean homeBehind = fullscreenStack != null && fullscreenStack.isVisible(); setMinimizedDockedStack(homeVisible && !homeBehind, animate); } diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java index 135f4000dd81..b5c9b99b35d6 100644 --- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java +++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java @@ -204,10 +204,12 @@ public class PinnedStackWindowController extends StackWindowController { */ /** Calls directly into activity manager so window manager lock shouldn't held. */ - public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) { + public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds, + boolean forceUpdate) { if (mListener != null) { PinnedStackWindowListener listener = (PinnedStackWindowListener) mListener; - listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds); + listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds, + forceUpdate); } } } diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowListener.java b/services/core/java/com/android/server/wm/PinnedStackWindowListener.java index 12b9c1f0c552..33e8a60329bf 100644 --- a/services/core/java/com/android/server/wm/PinnedStackWindowListener.java +++ b/services/core/java/com/android/server/wm/PinnedStackWindowListener.java @@ -28,5 +28,6 @@ public interface PinnedStackWindowListener extends StackWindowListener { * Called when the stack container pinned stack animation will change the picture-in-picture * mode. This is a direct call into ActivityManager. */ - default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {} + default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds, + boolean forceUpdate) {} } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 60384f27d594..f57238e34694 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -97,27 +97,23 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU private boolean mDragResizing; private int mDragResizeMode; - private boolean mHomeTask; - private TaskDescription mTaskDescription; // If set to true, the task will report that it is not in the floating - // state regardless of it's stack affilation. As the floating state drives + // state regardless of it's stack affiliation. As the floating state drives // production of content insets this can be used to preserve them across // stack moves and we in fact do so when moving from full screen to pinned. private boolean mPreserveNonFloatingState = false; Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds, Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, - boolean homeTask, TaskDescription taskDescription, - TaskWindowContainerController controller) { + TaskDescription taskDescription, TaskWindowContainerController controller) { mTaskId = taskId; mStack = stack; mUserId = userId; mService = service; mResizeMode = resizeMode; mSupportsPictureInPicture = supportsPictureInPicture; - mHomeTask = homeTask; setController(controller); setBounds(bounds, overrideConfig); mTaskDescription = taskDescription; @@ -370,10 +366,6 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU return isResizeable(); } - boolean isHomeTask() { - return mHomeTask; - } - boolean resizeLocked(Rect bounds, Configuration overrideConfig, boolean forced) { int boundsChanged = setBounds(bounds, overrideConfig); if (forced) { diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 1a6e2c87e695..6ec7565ab156 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -18,18 +18,9 @@ package com.android.server.wm; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; -import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; -import static android.app.WindowConfiguration.WINDOWING_MODE_DOCKED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -52,7 +43,6 @@ import static com.android.server.wm.proto.StackProto.ID; import static com.android.server.wm.proto.StackProto.TASKS; import android.app.ActivityManager.StackId; -import android.app.WindowConfiguration; import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; @@ -170,20 +160,10 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } Task findHomeTask() { - if (mStackId != HOME_STACK_ID) { + if (!isActivityTypeHome() || mChildren.isEmpty()) { return null; } - - for (int i = mChildren.size() - 1; i >= 0; i--) { - if (mChildren.get(i).isHomeTask()) { - return mChildren.get(i); - } - } - return null; - } - - boolean hasMultipleTaskWithHomeTaskNotTop() { - return mChildren.size() > 1 && !mChildren.get(mChildren.size() - 1).isHomeTask(); + return mChildren.get(mChildren.size() - 1); } /** @@ -1470,7 +1450,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye * small portion which the home stack currently is resized to. */ - if (task.isHomeTask() && isMinimizedDockAndHomeStackResizable()) { + if (task.isActivityTypeHome() && isMinimizedDockAndHomeStackResizable()) { mDisplayContent.getLogicalDisplayRect(mTmpRect); } else { task.getDimBounds(mTmpRect); @@ -1525,7 +1505,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } @Override // AnimatesBounds - public void onAnimationStart(boolean schedulePipModeChangedCallback) { + public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) { // Hold the lock since this is called from the BoundsAnimator running on the UiThread synchronized (mService.mWindowMap) { mBoundsAnimatingRequested = false; @@ -1550,9 +1530,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye final PinnedStackWindowController controller = (PinnedStackWindowController) getController(); if (schedulePipModeChangedCallback && controller != null) { - // We need to schedule the PiP mode change after the animation down, so use the - // final bounds - controller.updatePictureInPictureModeForPinnedStackAnimation(null); + // We need to schedule the PiP mode change before the animation up. It is possible + // in this case for the animation down to not have been completed, so always + // force-schedule and update to the client to ensure that it is notified that it + // is no longer in picture-in-picture mode + controller.updatePictureInPictureModeForPinnedStackAnimation(null, forceUpdate); } } } @@ -1580,7 +1562,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye // We need to schedule the PiP mode change after the animation down, so use the // final bounds controller.updatePictureInPictureModeForPinnedStackAnimation( - mBoundsAnimationTarget); + mBoundsAnimationTarget, false /* forceUpdate */); } if (finalStackSize != null) { diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java index 54a6cc01c831..d8929c9d80c2 100644 --- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java +++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java @@ -51,18 +51,17 @@ public class TaskWindowContainerController public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, StackWindowController stackController, int userId, Rect bounds, Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, - boolean homeTask, boolean toTop, boolean showForAllUsers, - TaskDescription taskDescription) { + boolean toTop, boolean showForAllUsers, TaskDescription taskDescription) { this(taskId, listener, stackController, userId, bounds, overrideConfig, resizeMode, - supportsPictureInPicture, homeTask, toTop, showForAllUsers, taskDescription, + supportsPictureInPicture, toTop, showForAllUsers, taskDescription, WindowManagerService.getInstance()); } public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, StackWindowController stackController, int userId, Rect bounds, Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, - boolean homeTask, boolean toTop, boolean showForAllUsers, - TaskDescription taskDescription, WindowManagerService service) { + boolean toTop, boolean showForAllUsers, TaskDescription taskDescription, + WindowManagerService service) { super(listener, service); mTaskId = taskId; mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); @@ -78,7 +77,7 @@ public class TaskWindowContainerController } EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId); final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode, - supportsPictureInPicture, homeTask, taskDescription); + supportsPictureInPicture, taskDescription); final int position = toTop ? POSITION_TOP : POSITION_BOTTOM; // We only want to move the parents to the parents if we are creating this task at the // top of its stack. @@ -89,9 +88,9 @@ public class TaskWindowContainerController @VisibleForTesting Task createTask(int taskId, TaskStack stack, int userId, Rect bounds, Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, - boolean homeTask, TaskDescription taskDescription) { + TaskDescription taskDescription) { return new Task(taskId, stack, userId, mService, bounds, overrideConfig, resizeMode, - supportsPictureInPicture, homeTask, taskDescription, this); + supportsPictureInPicture, taskDescription, this); } @Override diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp index 0370490668ec..d2f374dd9e08 100644 --- a/services/core/jni/com_android_server_VibratorService.cpp +++ b/services/core/jni/com_android_server_VibratorService.cpp @@ -153,9 +153,9 @@ static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength if (status == Status::OK) { return lengthMs; } else if (status != Status::UNSUPPORTED_OPERATION) { - // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor - // doesn't have a pre-defined waveform to perform for it, so we should just fall back - // to the framework waveforms. + // Don't warn on UNSUPPORTED_OPERATION, that's a normal event and just means the motor + // doesn't have a pre-defined waveform to perform for it, so we should just give the + // opportunity to fall back to the framework waveforms. ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32 ", error=%" PRIu32 ").", static_cast<int64_t>(effect), static_cast<int32_t>(strength), static_cast<uint32_t>(status)); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c7db7947d293..d6b55678040d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -43,6 +43,7 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; +import static android.app.admin.DevicePolicyManager.START_USER_IN_BACKGROUND; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; @@ -614,11 +615,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } else if (Intent.ACTION_USER_STARTED.equals(action)) { synchronized (DevicePolicyManagerService.this) { + maybeSendAdminEnabledBroadcastLocked(userHandle); // Reset the policy data mUserData.remove(userHandle); - sendAdminEnabledBroadcastLocked(userHandle); } handlePackagesChanged(null /* check all admins */, userHandle); + } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { + synchronized (DevicePolicyManagerService.this) { + maybeSendAdminEnabledBroadcastLocked(userHandle); + } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { handlePackagesChanged(null /* check all admins */, userHandle); } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action) @@ -1854,6 +1859,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_STARTED); + filter.addAction(Intent.ACTION_USER_UNLOCKED); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); filter = new IntentFilter(); @@ -2372,11 +2378,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { sendAdminCommandLocked(admin, action, null, result); } + void sendAdminCommandLocked(ActiveAdmin admin, String action, Bundle adminExtras, + BroadcastReceiver result) { + sendAdminCommandLocked(admin, action, adminExtras, result, false); + } + /** * Send an update to one specific admin, get notified when that admin returns a result. + * + * @return whether the broadcast was successfully sent */ - void sendAdminCommandLocked(ActiveAdmin admin, String action, Bundle adminExtras, - BroadcastReceiver result) { + boolean sendAdminCommandLocked(ActiveAdmin admin, String action, Bundle adminExtras, + BroadcastReceiver result, boolean inForeground) { Intent intent = new Intent(action); intent.setComponent(admin.info.getComponent()); if (UserManager.isDeviceInDemoMode(mContext)) { @@ -2385,15 +2398,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (action.equals(DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING)) { intent.putExtra("expiration", admin.passwordExpirationDate); } + if (inForeground) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + } if (adminExtras != null) { intent.putExtras(adminExtras); } + if (mInjector.getPackageManager().queryBroadcastReceiversAsUser( + intent, + PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + admin.getUserHandle()).isEmpty()) { + return false; + } if (result != null) { mContext.sendOrderedBroadcastAsUser(intent, admin.getUserHandle(), null, result, mHandler, Activity.RESULT_OK, null, null); } else { mContext.sendBroadcastAsUser(intent, admin.getUserHandle()); } + return true; } /** @@ -8099,20 +8122,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - - private void sendAdminEnabledBroadcastLocked(int userHandle) { + private void maybeSendAdminEnabledBroadcastLocked(int userHandle) { DevicePolicyData policyData = getUserData(userHandle); if (policyData.mAdminBroadcastPending) { // Send the initialization data to profile owner and delete the data ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle); + boolean clearInitBundle = true; if (admin != null) { PersistableBundle initBundle = policyData.mInitBundle; - sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED, - initBundle == null ? null : new Bundle(initBundle), null); + clearInitBundle = sendAdminCommandLocked(admin, + DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED, + initBundle == null ? null : new Bundle(initBundle), + null /* result receiver */, + true /* send in foreground */); + } + if (clearInitBundle) { + // If there's no admin or we've successfully called the admin, clear the init bundle + // otherwise, keep it around + policyData.mInitBundle = null; + policyData.mAdminBroadcastPending = false; + saveSettingsLocked(userHandle); } - policyData.mInitBundle = null; - policyData.mAdminBroadcastPending = false; - saveSettingsLocked(userHandle); } } @@ -8158,7 +8188,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (user == null) { return null; } - // Set admin. final long id = mInjector.binderClearCallingIdentity(); try { final String adminPkg = admin.getPackageName(); @@ -8171,30 +8200,38 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 0 /*installFlags*/, PackageManager.INSTALL_REASON_POLICY); } } catch (RemoteException e) { - Slog.e(LOG_TAG, "Failed to make remote calls for createAndManageUser, " - + "removing created user", e); - mUserManager.removeUser(user.getIdentifier()); - return null; + // Does not happen, same process } + // Set admin. setActiveAdmin(profileOwner, true, userHandle); - // User is not started yet, the broadcast by setActiveAdmin will not be received. - // So we store adminExtras for broadcasting when the user starts for first time. - synchronized(this) { + final String ownerName = getProfileOwnerName(Process.myUserHandle().getIdentifier()); + setProfileOwner(profileOwner, ownerName, userHandle); + + synchronized (this) { DevicePolicyData policyData = getUserData(userHandle); policyData.mInitBundle = adminExtras; policyData.mAdminBroadcastPending = true; saveSettingsLocked(userHandle); } - final String ownerName = getProfileOwnerName(Process.myUserHandle().getIdentifier()); - setProfileOwner(profileOwner, ownerName, userHandle); if ((flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0) { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle); } + if ((flags & START_USER_IN_BACKGROUND) != 0) { + try { + mInjector.getIActivityManager().startUserInBackground(user.getIdentifier()); + } catch (RemoteException re) { + // Does not happen, same process + } + } + return user; + } catch (Throwable re) { + mUserManager.removeUser(user.getIdentifier()); + return null; } finally { mInjector.binderRestoreCallingIdentity(id); } @@ -9077,6 +9114,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } mLockPatternUtils.setLockScreenDisabled(disabled, userId); + mInjector.getIWindowManager().dismissKeyguard(null); + } catch (RemoteException e) { + // Same process, does not happen. } finally { mInjector.binderRestoreCallingIdentity(ident); } diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk new file mode 100644 index 000000000000..717176247d36 --- /dev/null +++ b/services/robotests/Android.mk @@ -0,0 +1,78 @@ +# 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. + + +############################################################ +# FrameworksServicesLib app just for Robolectric test target. # +############################################################ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_PACKAGE_NAME := FrameworksServicesLib +LOCAL_MODULE_TAGS := optional + +LOCAL_PRIVILEGED_MODULE := true + +LOCAL_STATIC_JAVA_LIBRARIES := \ + frameworks-base-testutils \ + services.backup \ + services.core \ + android-support-test \ + mockito-target-minus-junit4 \ + platform-test-annotations \ + truth-prebuilt + +include $(BUILD_PACKAGE) + +############################################# +# FrameworksServices Robolectric test target. # +############################################# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +# Include the testing libraries (JUnit4 + Robolectric libs). +LOCAL_STATIC_JAVA_LIBRARIES := \ + platform-system-robolectric \ + truth-prebuilt + +LOCAL_JAVA_LIBRARIES := \ + junit \ + platform-robolectric-prebuilt + +LOCAL_INSTRUMENTATION_FOR := FrameworksServicesLib +LOCAL_MODULE := FrameworksServicesRoboTests + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_JAVA_LIBRARY) + +############################################################# +# FrameworksServices runner target to run the previous target. # +############################################################# +include $(CLEAR_VARS) + +LOCAL_MODULE := RunFrameworksServicesRoboTests + +LOCAL_SDK_VERSION := current + +LOCAL_STATIC_JAVA_LIBRARIES := \ + FrameworksServicesRoboTests + +LOCAL_TEST_PACKAGE := FrameworksServicesLib + +LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))backup/java + +include prebuilts/misc/common/robolectric/run_robotests.mk diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java new file mode 100644 index 000000000000..f345da2b548c --- /dev/null +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -0,0 +1,513 @@ +/* + * 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.backup; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; + +import com.android.server.backup.testing.BackupTransportStub; +import com.android.server.backup.testing.DefaultPackageManagerWithQueryIntentServicesAsUser; +import com.android.server.backup.testing.ShadowBackupTransportStub; +import com.android.server.backup.testing.ShadowContextImplForBackup; +import com.android.server.backup.testing.TransportBoundListenerStub; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.res.builder.RobolectricPackageManager; +import org.robolectric.shadows.ShadowLog; +import org.robolectric.shadows.ShadowLooper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config( + manifest = Config.NONE, + sdk = 23, + shadows = { + ShadowContextImplForBackup.class, + ShadowBackupTransportStub.class + } +) +@Presubmit +public class TransportManagerTest { + private static final String PACKAGE_NAME = "some.package.name"; + private static final String ANOTHER_PACKAGE_NAME = "another.package.name"; + + private TransportInfo mTransport1; + private TransportInfo mTransport2; + + private RobolectricPackageManager mPackageManager; + + private final TransportBoundListenerStub mTransportBoundListenerStub = + new TransportBoundListenerStub(true); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + ShadowLog.stream = System.out; + mPackageManager = new DefaultPackageManagerWithQueryIntentServicesAsUser( + RuntimeEnvironment.getAppResourceLoader()); + RuntimeEnvironment.setRobolectricPackageManager(mPackageManager); + + mTransport1 = new TransportInfo(PACKAGE_NAME, "transport1.name"); + mTransport2 = new TransportInfo(PACKAGE_NAME, "transport2.name"); + + ShadowContextImplForBackup.sComponentBinderMap.put(mTransport1.componentName, + mTransport1.binder); + ShadowContextImplForBackup.sComponentBinderMap.put(mTransport2.componentName, + mTransport2.binder); + ShadowBackupTransportStub.sBinderTransportMap.put(mTransport1.binder, mTransport1.stub); + ShadowBackupTransportStub.sBinderTransportMap.put(mTransport2.binder, mTransport2.stub); + } + + @After + public void tearDown() throws Exception { + ShadowContextImplForBackup.resetBackupShadowState(); + } + + @Test + public void onPackageAdded_bindsToAllTransports() throws Exception { + setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), + ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + new HashSet<>(Arrays.asList( + mTransport1.componentName, mTransport2.componentName)), + null /* defaultTransport */, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + transportManager.onPackageAdded(PACKAGE_NAME); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.componentName, mTransport2.componentName)); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.name, mTransport2.name)); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.stub)).isTrue(); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.stub)).isTrue(); + } + + @Test + public void onPackageAdded_oneTransportUnavailable_bindsToOnlyOneTransport() throws Exception { + setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), + ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + + ShadowContextImplForBackup.sUnbindableComponents.add(mTransport1.componentName); + + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + new HashSet<>(Arrays.asList( + mTransport1.componentName, mTransport2.componentName)), + null /* defaultTransport */, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + transportManager.onPackageAdded(PACKAGE_NAME); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + Collections.singleton(mTransport2.componentName)); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + Collections.singleton(mTransport2.name)); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.stub)).isFalse(); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.stub)).isTrue(); + } + + @Test + public void onPackageAdded_whitelistIsNull_doesNotBindToTransports() throws Exception { + setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), + ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + null /* whitelist */, + null /* defaultTransport */, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + transportManager.onPackageAdded(PACKAGE_NAME); + + assertThat(transportManager.getAllTransportComponents()).isEmpty(); + assertThat(transportManager.getBoundTransportNames()).isEmpty(); + assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); + } + + @Test + public void onPackageAdded_onlyOneTransportWhitelisted_onlyConnectsToWhitelistedTransport() + throws Exception { + setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), + ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + new HashSet<>(Collections.singleton(mTransport2.componentName)), + null /* defaultTransport */, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + transportManager.onPackageAdded(PACKAGE_NAME); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + Collections.singleton(mTransport2.componentName)); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + Collections.singleton(mTransport2.name)); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.stub)).isFalse(); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.stub)).isTrue(); + } + + @Test + public void onPackageAdded_appIsNotPrivileged_doesNotBindToTransports() throws Exception { + setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), 0); + + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + new HashSet<>(Arrays.asList( + mTransport1.componentName, mTransport2.componentName)), + null /* defaultTransport */, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + transportManager.onPackageAdded(PACKAGE_NAME); + + assertThat(transportManager.getAllTransportComponents()).isEmpty(); + assertThat(transportManager.getBoundTransportNames()).isEmpty(); + assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); + } + + @Test + public void onPackageRemoved_transportsUnbound() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + transportManager.onPackageRemoved(PACKAGE_NAME); + + assertThat(transportManager.getAllTransportComponents()).isEmpty(); + assertThat(transportManager.getBoundTransportNames()).isEmpty(); + } + + @Test + public void onPackageRemoved_incorrectPackageName_nothingHappens() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + transportManager.onPackageRemoved(ANOTHER_PACKAGE_NAME); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.componentName, mTransport2.componentName)); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.name, mTransport2.name)); + } + + @Test + public void onPackageChanged_oneComponentChanged_onlyOneTransportRebound() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.componentName, mTransport2.componentName)); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.name, mTransport2.name)); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.stub)).isFalse(); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.stub)).isTrue(); + } + + @Test + public void onPackageChanged_nothingChanged_noTransportsRebound() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + transportManager.onPackageChanged(PACKAGE_NAME, new String[0]); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.componentName, mTransport2.componentName)); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.name, mTransport2.name)); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.stub)).isFalse(); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.stub)).isFalse(); + } + + @Test + public void onPackageChanged_unexpectedComponentChanged_noTransportsRebound() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + transportManager.onPackageChanged(PACKAGE_NAME, new String[]{"unexpected.component"}); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.componentName, mTransport2.componentName)); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.name, mTransport2.name)); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.stub)).isFalse(); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.stub)).isFalse(); + } + + @Test + public void onPackageChanged_transportsRebound() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.componentName, mTransport2.componentName)); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + Arrays.asList(mTransport1.name, mTransport2.name)); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.stub)).isFalse(); + assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.stub)).isTrue(); + } + + @Test + public void getTransportBinder_returnsCorrectBinder() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + assertThat(transportManager.getTransportBinder(mTransport1.name)).isEqualTo( + mTransport1.stub); + assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( + mTransport2.stub); + } + + @Test + public void getTransportBinder_incorrectTransportName_returnsNull() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + assertThat(transportManager.getTransportBinder("incorrect.transport")).isNull(); + } + + @Test + public void getTransportBinder_oneTransportUnavailable_returnsCorrectBinder() throws Exception { + TransportManager transportManager = + createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), + Collections.singletonList(mTransport1), mTransport1.name); + + assertThat(transportManager.getTransportBinder(mTransport1.name)).isNull(); + assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( + mTransport2.stub); + } + + @Test + public void getCurrentTransport_selectTransportNotCalled_returnsDefaultTransport() + throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + } + + @Test + public void getCurrentTransport_selectTransportCalled_returnsCorrectTransport() + throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + + transportManager.selectTransport(mTransport2.name); + + assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport2.name); + } + + @Test + public void getCurrentTransportBinder_returnsCorrectBinder() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + assertThat(transportManager.getCurrentTransportBinder()).isEqualTo(mTransport1.stub); + } + + @Test + public void getCurrentTransportBinder_transportNotBound_returnsNull() throws Exception { + TransportManager transportManager = + createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), + Collections.singletonList(mTransport1), mTransport2.name); + + transportManager.selectTransport(mTransport1.name); + + assertThat(transportManager.getCurrentTransportBinder()).isNull(); + } + + @Test + public void getTransportName_returnsCorrectTransportName() throws Exception { + TransportManager transportManager = createTransportManagerAndSetUpTransports( + Arrays.asList(mTransport1, mTransport2), mTransport1.name); + + assertThat(transportManager.getTransportName(mTransport1.stub)).isEqualTo(mTransport1.name); + assertThat(transportManager.getTransportName(mTransport2.stub)).isEqualTo(mTransport2.name); + } + + @Test + public void getTransportName_transportNotBound_returnsNull() throws Exception { + TransportManager transportManager = + createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), + Collections.singletonList(mTransport1), mTransport1.name); + + assertThat(transportManager.getTransportName(mTransport1.stub)).isNull(); + assertThat(transportManager.getTransportName(mTransport2.stub)).isEqualTo(mTransport2.name); + } + + @Test + public void getTransportWhitelist_returnsCorrectWhiteList() throws Exception { + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)), + mTransport1.name, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + + assertThat(transportManager.getTransportWhitelist()).containsExactlyElementsIn( + Arrays.asList(mTransport1.componentName, mTransport2.componentName)); + } + + @Test + public void getTransportWhitelist_whiteListIsNull_returnsEmptyArray() throws Exception { + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + null /* whitelist */, + mTransport1.name, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + + assertThat(transportManager.getTransportWhitelist()).isEmpty(); + } + + @Test + public void selectTransport_setsTransportCorrectlyAndReturnsPreviousTransport() + throws Exception { + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + null /* whitelist */, + mTransport1.name, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + + assertThat(transportManager.selectTransport(mTransport2.name)).isEqualTo(mTransport1.name); + assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name); + } + + private void setUpPackageWithTransports(String packageName, List<TransportInfo> transports, + int flags) throws Exception { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + packageInfo.applicationInfo = new ApplicationInfo(); + packageInfo.applicationInfo.privateFlags = flags; + + mPackageManager.addPackage(packageInfo); + + List<ResolveInfo> transportsInfo = new ArrayList<>(); + for (TransportInfo transport : transports) { + ResolveInfo info = new ResolveInfo(); + info.serviceInfo = new ServiceInfo(); + info.serviceInfo.packageName = packageName; + info.serviceInfo.name = transport.name; + transportsInfo.add(info); + } + + Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); + intent.setPackage(packageName); + + mPackageManager.addResolveInfoForIntent(intent, transportsInfo); + } + + private TransportManager createTransportManagerAndSetUpTransports( + List<TransportInfo> availableTransports, String defaultTransportName) throws Exception { + return createTransportManagerAndSetUpTransports(availableTransports, + Collections.<TransportInfo>emptyList(), defaultTransportName); + } + + private TransportManager createTransportManagerAndSetUpTransports( + List<TransportInfo> availableTransports, List<TransportInfo> unavailableTransports, + String defaultTransportName) + throws Exception { + List<String> availableTransportsNames = new ArrayList<>(); + List<ComponentName> availableTransportsComponentNames = new ArrayList<>(); + for (TransportInfo transport : availableTransports) { + availableTransportsNames.add(transport.name); + availableTransportsComponentNames.add(transport.componentName); + } + + List<ComponentName> allTransportsComponentNames = new ArrayList<>(); + allTransportsComponentNames.addAll(availableTransportsComponentNames); + for (TransportInfo transport : unavailableTransports) { + allTransportsComponentNames.add(transport.componentName); + } + + for (TransportInfo transport : unavailableTransports) { + ShadowContextImplForBackup.sUnbindableComponents.add(transport.componentName); + } + + setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), + ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + + TransportManager transportManager = new TransportManager( + RuntimeEnvironment.application.getApplicationContext(), + new HashSet<>(allTransportsComponentNames), + defaultTransportName, + mTransportBoundListenerStub, + ShadowLooper.getMainLooper()); + transportManager.onPackageAdded(PACKAGE_NAME); + + assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( + availableTransportsComponentNames); + assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( + availableTransportsNames); + for (TransportInfo transport : availableTransports) { + assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.stub)).isTrue(); + } + for (TransportInfo transport : unavailableTransports) { + assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.stub)).isFalse(); + } + + mTransportBoundListenerStub.resetState(); + + return transportManager; + } + + private static class TransportInfo { + public final String packageName; + public final String name; + public final ComponentName componentName; + public final BackupTransportStub stub; + public final IBinder binder; + + TransportInfo(String packageName, String name) { + this.packageName = packageName; + this.name = name; + this.componentName = new ComponentName(packageName, name); + this.stub = new BackupTransportStub(name); + this.binder = mock(IBinder.class); + } + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/BackupTransportStub.java b/services/robotests/src/com/android/server/backup/testing/BackupTransportStub.java new file mode 100644 index 000000000000..ec09f908c90d --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/BackupTransportStub.java @@ -0,0 +1,179 @@ +/* + * 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.backup.testing; + +import android.app.backup.RestoreDescription; +import android.app.backup.RestoreSet; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import com.android.internal.backup.IBackupTransport; + +/** + * Stub backup transport, doing nothing and returning default values. + */ +public class BackupTransportStub implements IBackupTransport { + + private final String mName; + + public BackupTransportStub(String name) { + mName = name; + } + + @Override + public IBinder asBinder() { + return null; + } + + @Override + public String name() throws RemoteException { + return mName; + } + + @Override + public Intent configurationIntent() throws RemoteException { + return null; + } + + @Override + public String currentDestinationString() throws RemoteException { + return null; + } + + @Override + public Intent dataManagementIntent() throws RemoteException { + return null; + } + + @Override + public String dataManagementLabel() throws RemoteException { + return null; + } + + @Override + public String transportDirName() throws RemoteException { + return null; + } + + @Override + public long requestBackupTime() throws RemoteException { + return 0; + } + + @Override + public int initializeDevice() throws RemoteException { + return 0; + } + + @Override + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) + throws RemoteException { + return 0; + } + + @Override + public int clearBackupData(PackageInfo packageInfo) throws RemoteException { + return 0; + } + + @Override + public int finishBackup() throws RemoteException { + return 0; + } + + @Override + public RestoreSet[] getAvailableRestoreSets() throws RemoteException { + return new RestoreSet[0]; + } + + @Override + public long getCurrentRestoreSet() throws RemoteException { + return 0; + } + + @Override + public int startRestore(long token, PackageInfo[] packages) throws RemoteException { + return 0; + } + + @Override + public RestoreDescription nextRestorePackage() throws RemoteException { + return null; + } + + @Override + public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { + return 0; + } + + @Override + public void finishRestore() throws RemoteException { + + } + + @Override + public long requestFullBackupTime() throws RemoteException { + return 0; + } + + @Override + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, + int flags) + throws RemoteException { + return 0; + } + + @Override + public int checkFullBackupSize(long size) throws RemoteException { + return 0; + } + + @Override + public int sendBackupData(int numBytes) throws RemoteException { + return 0; + } + + @Override + public void cancelFullBackup() throws RemoteException { + + } + + @Override + public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) + throws RemoteException { + return false; + } + + @Override + public long getBackupQuota(String packageName, boolean isFullBackup) + throws RemoteException { + return 0; + } + + @Override + public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException { + return 0; + } + + @Override + public int abortFullRestore() throws RemoteException { + return 0; + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/DefaultPackageManagerWithQueryIntentServicesAsUser.java b/services/robotests/src/com/android/server/backup/testing/DefaultPackageManagerWithQueryIntentServicesAsUser.java new file mode 100644 index 000000000000..5a0967ba2420 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/DefaultPackageManagerWithQueryIntentServicesAsUser.java @@ -0,0 +1,43 @@ +/* + * 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.backup.testing; + +import android.content.Intent; +import android.content.pm.ResolveInfo; + +import org.robolectric.res.ResourceLoader; +import org.robolectric.res.builder.DefaultPackageManager; + +import java.util.List; + +/** + * Implementation of PackageManager for Robolectric which handles queryIntentServicesAsUser(). + */ +public class DefaultPackageManagerWithQueryIntentServicesAsUser extends + DefaultPackageManager { + + /* package */ + public DefaultPackageManagerWithQueryIntentServicesAsUser( + ResourceLoader appResourceLoader) { + super(appResourceLoader); + } + + @Override + public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { + return super.queryIntentServices(intent, flags); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowBackupTransportStub.java b/services/robotests/src/com/android/server/backup/testing/ShadowBackupTransportStub.java new file mode 100644 index 000000000000..48a12f0f5435 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/ShadowBackupTransportStub.java @@ -0,0 +1,40 @@ +/* + * 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.backup.testing; + +import android.os.IBinder; + +import com.android.internal.backup.IBackupTransport; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import java.util.HashMap; +import java.util.Map; + +/** + * Shadow IBackupTransport.Stub, returns a transport corresponding to the binder. + */ +@Implements(IBackupTransport.Stub.class) +public class ShadowBackupTransportStub { + public static Map<IBinder, IBackupTransport> sBinderTransportMap = new HashMap<>(); + + @Implementation + public static IBackupTransport asInterface(IBinder obj) { + return sBinderTransportMap.get(obj); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowContextImplForBackup.java b/services/robotests/src/com/android/server/backup/testing/ShadowContextImplForBackup.java new file mode 100644 index 000000000000..c3975db3057f --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/ShadowContextImplForBackup.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.testing; + +import android.annotation.RequiresPermission; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.UserHandle; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.shadows.ShadowContextImpl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of ContextImpl shadow, handling bindServiceAsUser(). + */ +@Implements(className = ShadowContextImpl.CLASS_NAME) +public class ShadowContextImplForBackup extends ShadowContextImpl { + public static Map<ComponentName, IBinder> sComponentBinderMap = new HashMap<>(); + public static Set<ComponentName> sUnbindableComponents = new HashSet<>(); + + @Implementation + public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn, + int flags, UserHandle user) { + if (sUnbindableComponents.contains(service.getComponent())) { + return false; + } + + ShadowApplication.getInstance().setComponentNameAndServiceForBindService( + service.getComponent(), sComponentBinderMap.get(service.getComponent())); + return bindService(service, conn, flags); + } + + + /** + * Resets backup-related shadow state. + */ + public static void resetBackupShadowState() { + sComponentBinderMap.clear(); + sUnbindableComponents.clear(); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java new file mode 100644 index 000000000000..84ac2c212854 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.testing; + +import com.android.internal.backup.IBackupTransport; +import com.android.server.backup.TransportManager; + +import java.util.HashSet; +import java.util.Set; + +/** + * Stub implementation of TransportBoundListener, which returns given result and can tell whether + * it was called for given transport. + */ +public class TransportBoundListenerStub implements + TransportManager.TransportBoundListener { + private boolean mAlwaysReturnSuccess; + private Set<IBackupTransport> mTransportsCalledFor = new HashSet<>(); + + public TransportBoundListenerStub(boolean alwaysReturnSuccess) { + this.mAlwaysReturnSuccess = alwaysReturnSuccess; + } + + @Override + public boolean onTransportBound(IBackupTransport binder) { + mTransportsCalledFor.add(binder); + return mAlwaysReturnSuccess; + } + + /** + * Returns whether the listener was called for the specified transport at least once. + */ + public boolean isCalledForTransport(IBackupTransport binder) { + return mTransportsCalledFor.contains(binder); + } + + /** + * Returns whether the listener was called at least once. + */ + public boolean isCalled() { + return !mTransportsCalledFor.isEmpty(); + } + + /** + * Resets listener calls. + */ + public void resetState() { + mTransportsCalledFor.clear(); + } +} diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index 04b42f1ee312..ddd21df78e85 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -429,6 +429,20 @@ public class NotificationManagerServiceTest extends NotificationTestCase { } @Test + public void testBlockedNotifications_blockedChannelGroup() throws Exception { + when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + mNotificationManagerService.setRankingHelper(mRankingHelper); + when(mRankingHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true); + + NotificationChannel channel = new NotificationChannel("id", "name", + NotificationManager.IMPORTANCE_HIGH); + channel.setGroup("something"); + NotificationRecord r = generateNotificationRecord(channel); + assertTrue(mNotificationManagerService.isBlocked(r, mUsageStats)); + verify(mUsageStats, times(1)).registerBlocked(eq(r)); + } + + @Test public void testEnqueuedBlockedNotifications_blockedApp() throws Exception { when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 65bf33084bb4..306dd98acaad 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -240,12 +240,23 @@ public class RankingHelperTest extends NotificationTestCase { private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) { assertEquals(expected.getId(), actual.getId()); assertEquals(expected.getName(), actual.getName()); + assertEquals(expected.getDescription(), actual.getDescription()); + assertEquals(expected.isBlocked(), actual.isBlocked()); } private NotificationChannel getChannel() { return new NotificationChannel("id", "name", IMPORTANCE_LOW); } + private NotificationChannel findChannel(List<NotificationChannel> channels, String id) { + for (NotificationChannel channel : channels) { + if (channel.getId().equals(id)) { + return channel; + } + } + return null; + } + @Test public void testFindAfterRankingWithASplitGroup() throws Exception { ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3); @@ -299,6 +310,8 @@ public class RankingHelperTest extends NotificationTestCase { @Test public void testChannelXml() throws Exception { NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye"); + ncg.setBlocked(true); + ncg.setDescription("group desc"); NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello"); NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); @@ -1294,6 +1307,26 @@ public class RankingHelperTest extends NotificationTestCase { } @Test + public void testCreateChannel_addToGroup() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("group", ""); + mHelper.createNotificationChannelGroup(PKG, UID, group, true); + NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(PKG, UID, nc, true); + NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false); + assertNull(actual.getGroup()); + + nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH); + nc.setGroup(group.getId()); + mHelper.createNotificationChannel(PKG, UID, nc, true); + + actual = mHelper.getNotificationChannel(PKG, UID, "id", false); + assertNotNull(actual.getGroup()); + assertEquals(IMPORTANCE_DEFAULT, actual.getImportance()); + + verify(mHandler, times(1)).requestSort(); + } + + @Test public void testDumpChannelsJson() throws Exception { final ApplicationInfo upgrade = new ApplicationInfo(); upgrade.targetSdkVersion = Build.VERSION_CODES.O; @@ -1388,4 +1421,77 @@ public class RankingHelperTest extends NotificationTestCase { assertEquals(newLabel, mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getName()); } + + @Test + public void testIsGroupBlocked_noGroup() throws Exception { + assertFalse(mHelper.isGroupBlocked(PKG, UID, null)); + + assertFalse(mHelper.isGroupBlocked(PKG, UID, "non existent group")); + } + + @Test + public void testIsGroupBlocked_notBlocked() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + mHelper.createNotificationChannelGroup(PKG, UID, group, true); + + assertFalse(mHelper.isGroupBlocked(PKG, UID, group.getId())); + } + + @Test + public void testIsGroupBlocked_blocked() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + mHelper.createNotificationChannelGroup(PKG, UID, group, true); + group.setBlocked(true); + mHelper.createNotificationChannelGroup(PKG, UID, group, false); + + assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId())); + } + + @Test + public void testIsGroup_appCannotResetBlock() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("id", "name"); + mHelper.createNotificationChannelGroup(PKG, UID, group, true); + NotificationChannelGroup group2 = group.clone(); + group2.setBlocked(true); + mHelper.createNotificationChannelGroup(PKG, UID, group2, false); + assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId())); + + NotificationChannelGroup group3 = group.clone(); + group3.setBlocked(false); + mHelper.createNotificationChannelGroup(PKG, UID, group3, true); + assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId())); + } + + @Test + public void testGetNotificationChannelGroupWithChannels() throws Exception { + NotificationChannelGroup group = new NotificationChannelGroup("group", ""); + NotificationChannelGroup other = new NotificationChannelGroup("something else", ""); + mHelper.createNotificationChannelGroup(PKG, UID, group, true); + mHelper.createNotificationChannelGroup(PKG, UID, other, true); + + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); + a.setGroup(group.getId()); + NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT); + b.setGroup(other.getId()); + NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT); + c.setGroup(group.getId()); + NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT); + + mHelper.createNotificationChannel(PKG, UID, a, true); + mHelper.createNotificationChannel(PKG, UID, b, true); + mHelper.createNotificationChannel(PKG, UID, c, true); + mHelper.createNotificationChannel(PKG, UID, d, true); + mHelper.deleteNotificationChannel(PKG, UID, c.getId()); + + NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels( + PKG, UID, group.getId(), true); + assertEquals(2, retrieved.getChannels().size()); + compareChannels(a, findChannel(retrieved.getChannels(), a.getId())); + compareChannels(c, findChannel(retrieved.getChannels(), c.getId())); + + retrieved = mHelper.getNotificationChannelGroupWithChannels( + PKG, UID, group.getId(), false); + assertEquals(1, retrieved.getChannels().size()); + compareChannels(a, findChannel(retrieved.getChannels(), a.getId())); + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java new file mode 100644 index 000000000000..50824e32e50d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java @@ -0,0 +1,535 @@ +/* + * 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.accessibility; + +import static android.util.ExceptionUtils.propagate; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_POINTER_DOWN; +import static android.view.MotionEvent.ACTION_POINTER_UP; + +import static com.android.server.testutils.TestUtils.strictMock; + +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.annotation.NonNull; +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.util.DebugUtils; +import android.view.InputDevice; +import android.view.MotionEvent; + +import com.android.server.testutils.OffsettableClock; +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.IntConsumer; + + +@RunWith(AndroidJUnit4.class) +public class MagnificationGestureHandlerTest { + + public static final int STATE_IDLE = 1; + public static final int STATE_ZOOMED = 2; + public static final int STATE_2TAPS = 3; + public static final int STATE_ZOOMED_2TAPS = 4; + public static final int STATE_SHORTCUT_TRIGGERED = 5; + public static final int STATE_DRAGGING_TMP = 6; + public static final int STATE_DRAGGING = 7; + public static final int STATE_PANNING = 8; + public static final int STATE_SCALING_AND_PANNING = 9; + + + public static final int FIRST_STATE = STATE_IDLE; + public static final int LAST_STATE = STATE_SCALING_AND_PANNING; + + // Co-prime x and y, to potentially catch x-y-swapped errors + public static final float DEFAULT_X = 301; + public static final float DEFAULT_Y = 299; + + private Context mContext; + private AccessibilityManagerService mAms; + private MagnificationController mMagnificationController; + private OffsettableClock mClock; + private MagnificationGestureHandler mMgh; + private TestHandler mHandler; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + mAms = new AccessibilityManagerService(mContext); + mMagnificationController = new MagnificationController( + mContext, mAms, /* lock */ new Object()) { + @Override + public boolean magnificationRegionContains(float x, float y) { + return true; + } + + @Override + void setForceShowMagnifiableBounds(boolean show) {} + }; + mMagnificationController.mRegistered = true; + mClock = new OffsettableClock.Stopped(); + + boolean detectTripleTap = true; + boolean detectShortcutTrigger = true; + mMgh = newInstance(detectTripleTap, detectShortcutTrigger); + } + + @NonNull + public MagnificationGestureHandler newInstance(boolean detectTripleTap, + boolean detectShortcutTrigger) { + MagnificationGestureHandler h = new MagnificationGestureHandler( + mContext, mMagnificationController, + detectTripleTap, detectShortcutTrigger); + mHandler = new TestHandler(h.mDetectingStateHandler, mClock); + h.mDetectingStateHandler.mHandler = mHandler; + h.setNext(strictMock(EventStreamTransformation.class)); + return h; + } + + @Test + public void testInitialState_isIdle() { + assertIn(STATE_IDLE); + } + + /** + * Covers paths to get to and back between each state and {@link #STATE_IDLE} + * This navigates between states using "canonical" paths, specified in + * {@link #goFromStateIdleTo} (for traversing away from {@link #STATE_IDLE}) and + * {@link #returnToNormalFrom} (for navigating back to {@link #STATE_IDLE}) + */ + @Test + public void testEachState_isReachableAndRecoverable() { + forEachState(state -> { + goFromStateIdleTo(state); + assertIn(state); + + returnToNormalFrom(state); + try { + assertIn(STATE_IDLE); + } catch (AssertionError e) { + throw new AssertionError("Failed while testing state " + stateToString(state), e); + } + }); + } + + @Test + public void testStates_areMutuallyExclusive() { + forEachState(state1 -> { + forEachState(state2 -> { + if (state1 < state2) { + goFromStateIdleTo(state1); + try { + assertIn(state2); + fail("State " + stateToString(state1) + " also implies state " + + stateToString(state2) + stateDump()); + } catch (AssertionError e) { + // expected + returnToNormalFrom(state1); + } + } + }); + }); + } + + /** + * Covers edges of the graph not covered by "canonical" transitions specified in + * {@link #goFromStateIdleTo} and {@link #returnToNormalFrom} + */ + @SuppressWarnings("Convert2MethodRef") + @Test + public void testAlternativeTransitions_areWorking() { + // A11y button followed by a tap&hold turns temporary "viewport dragging" zoom on + assertTransition(STATE_SHORTCUT_TRIGGERED, () -> { + send(downEvent()); + fastForward1sec(); + }, STATE_DRAGGING_TMP); + + // A11y button followed by a tap turns zoom on + assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ZOOMED); + + // A11y button pressed second time negates the 1st press + assertTransition(STATE_SHORTCUT_TRIGGERED, () -> triggerShortcut(), STATE_IDLE); + + // A11y button turns zoom off + assertTransition(STATE_ZOOMED, () -> triggerShortcut(), STATE_IDLE); + + + // Double tap times out while zoomed + assertTransition(STATE_ZOOMED_2TAPS, () -> { + allowEventDelegation(); + fastForward1sec(); + }, STATE_ZOOMED); + + // tap+tap+swipe gets delegated + assertTransition(STATE_2TAPS, () -> { + allowEventDelegation(); + swipe(); + }, STATE_IDLE); + } + + @Test + public void testNonTransitions_dontChangeState() { + // ACTION_POINTER_DOWN triggers event delegation if not magnifying + assertStaysIn(STATE_IDLE, () -> { + allowEventDelegation(); + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); + }); + + // Long tap breaks the triple-tap detection sequence + Runnable tapAndLongTap = () -> { + allowEventDelegation(); + tap(); + longTap(); + }; + assertStaysIn(STATE_IDLE, tapAndLongTap); + assertStaysIn(STATE_ZOOMED, tapAndLongTap); + + // Triple tap with delays in between doesn't count + Runnable slow3tap = () -> { + tap(); + fastForward1sec(); + tap(); + fastForward1sec(); + tap(); + }; + assertStaysIn(STATE_IDLE, slow3tap); + assertStaysIn(STATE_ZOOMED, slow3tap); + } + + @Test + public void testDisablingTripleTap_removesInputLag() { + mMgh = newInstance(/* detect3tap */ false, /* detectShortcut */ true); + goFromStateIdleTo(STATE_IDLE); + allowEventDelegation(); + tap(); + // no fast forward + verify(mMgh.mNext, times(2)).onMotionEvent(any(), any(), anyInt()); + } + + private void assertTransition(int fromState, Runnable transitionAction, int toState) { + goFromStateIdleTo(fromState); + transitionAction.run(); + assertIn(toState); + returnToNormalFrom(toState); + } + + private void assertStaysIn(int state, Runnable action) { + assertTransition(state, action, state); + } + + private void forEachState(IntConsumer action) { + for (int state = FIRST_STATE; state <= LAST_STATE; state++) { + action.accept(state); + } + } + + private void allowEventDelegation() { + doNothing().when(mMgh.mNext).onMotionEvent(any(), any(), anyInt()); + } + + private void fastForward1sec() { + fastForward(1000); + } + + private void fastForward(int ms) { + mClock.fastForward(ms); + mHandler.timeAdvance(); + } + + /** + * Asserts that {@link #mMgh the handler} is in the given {@code state} + */ + private void assertIn(int state) { + switch (state) { + + // Asserts on separate lines for accurate stack traces + + case STATE_IDLE: { + check(tapCount() < 2, state); + check(!mMgh.mShortcutTriggered, state); + check(!isZoomed(), state); + } break; + case STATE_ZOOMED: { + check(isZoomed(), state); + check(tapCount() < 2, state); + } break; + case STATE_2TAPS: { + check(!isZoomed(), state); + check(tapCount() == 2, state); + } break; + case STATE_ZOOMED_2TAPS: { + check(isZoomed(), state); + check(tapCount() == 2, state); + } break; + case STATE_DRAGGING: { + check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_VIEWPORT_DRAGGING, + state); + check(mMgh.mViewportDraggingStateHandler.mZoomedInBeforeDrag, state); + } break; + case STATE_DRAGGING_TMP: { + check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_VIEWPORT_DRAGGING, + state); + check(!mMgh.mViewportDraggingStateHandler.mZoomedInBeforeDrag, state); + } break; + case STATE_SHORTCUT_TRIGGERED: { + check(mMgh.mShortcutTriggered, state); + check(!isZoomed(), state); + } break; + case STATE_PANNING: { + check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_PANNING_SCALING, + state); + check(!mMgh.mPanningScalingStateHandler.mScaling, state); + } break; + case STATE_SCALING_AND_PANNING: { + check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_PANNING_SCALING, + state); + check(mMgh.mPanningScalingStateHandler.mScaling, state); + } break; + default: throw new IllegalArgumentException("Illegal state: " + state); + } + } + + /** + * Defines a "canonical" path from {@link #STATE_IDLE} to {@code state} + */ + private void goFromStateIdleTo(int state) { + try { + switch (state) { + case STATE_IDLE: { + mMgh.clearAndTransitionToStateDetecting(); + } break; + case STATE_2TAPS: { + goFromStateIdleTo(STATE_IDLE); + tap(); + tap(); + } break; + case STATE_ZOOMED: { + if (mMgh.mDetectTripleTap) { + goFromStateIdleTo(STATE_2TAPS); + tap(); + } else { + goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); + tap(); + } + } break; + case STATE_ZOOMED_2TAPS: { + goFromStateIdleTo(STATE_ZOOMED); + tap(); + tap(); + } break; + case STATE_DRAGGING: { + goFromStateIdleTo(STATE_ZOOMED_2TAPS); + send(downEvent()); + fastForward1sec(); + } break; + case STATE_DRAGGING_TMP: { + goFromStateIdleTo(STATE_2TAPS); + send(downEvent()); + fastForward1sec(); + } break; + case STATE_SHORTCUT_TRIGGERED: { + goFromStateIdleTo(STATE_IDLE); + triggerShortcut(); + } break; + case STATE_PANNING: { + goFromStateIdleTo(STATE_ZOOMED); + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); + } break; + case STATE_SCALING_AND_PANNING: { + goFromStateIdleTo(STATE_PANNING); + send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 3)); + send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 4)); + } break; + default: + throw new IllegalArgumentException("Illegal state: " + state); + } + } catch (Throwable t) { + throw new RuntimeException("Failed to go to state " + stateToString(state), t); + } + } + + /** + * Defines a "canonical" path from {@code state} to {@link #STATE_IDLE} + */ + private void returnToNormalFrom(int state) { + switch (state) { + case STATE_IDLE: { + // no op + } break; + case STATE_2TAPS: { + allowEventDelegation(); + fastForward1sec(); + } break; + case STATE_ZOOMED: { + if (mMgh.mDetectTripleTap) { + tap(); + tap(); + returnToNormalFrom(STATE_ZOOMED_2TAPS); + } else { + triggerShortcut(); + } + } break; + case STATE_ZOOMED_2TAPS: { + tap(); + } break; + case STATE_DRAGGING: { + send(upEvent()); + returnToNormalFrom(STATE_ZOOMED); + } break; + case STATE_DRAGGING_TMP: { + send(upEvent()); + } break; + case STATE_SHORTCUT_TRIGGERED: { + triggerShortcut(); + } break; + case STATE_PANNING: { + send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y)); + send(upEvent()); + returnToNormalFrom(STATE_ZOOMED); + } break; + case STATE_SCALING_AND_PANNING: { + returnToNormalFrom(STATE_PANNING); + } break; + default: throw new IllegalArgumentException("Illegal state: " + state); + } + } + + private void check(boolean condition, int expectedState) { + if (!condition) { + fail("Expected to be in state " + stateToString(expectedState) + stateDump()); + } + } + + private boolean isZoomed() { + return mMgh.mMagnificationController.isMagnifying(); + } + + private int tapCount() { + return mMgh.mDetectingStateHandler.tapCount(); + } + + private static String stateToString(int state) { + return DebugUtils.valueToString(MagnificationGestureHandlerTest.class, "STATE_", state); + } + + private void tap() { + MotionEvent downEvent = downEvent(); + send(downEvent); + send(upEvent(downEvent.getDownTime())); + } + + private void swipe() { + MotionEvent downEvent = downEvent(); + send(downEvent); + send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2)); + send(upEvent(downEvent.getDownTime())); + } + + private void longTap() { + MotionEvent downEvent = downEvent(); + send(downEvent); + fastForward(2000); + send(upEvent(downEvent.getDownTime())); + } + + private void triggerShortcut() { + mMgh.notifyShortcutTriggered(); + } + + private void send(MotionEvent event) { + event.setSource(InputDevice.SOURCE_TOUCHSCREEN); + try { + mMgh.onMotionEvent(event, event, /* policyFlags */ 0); + } catch (Throwable t) { + throw new RuntimeException("Exception while handling " + event, t); + } + fastForward(1); + } + + private MotionEvent moveEvent(float x, float y) { + return MotionEvent.obtain(defaultDownTime(), mClock.now(), ACTION_MOVE, x, y, 0); + } + + private MotionEvent downEvent() { + return MotionEvent.obtain(mClock.now(), mClock.now(), + ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0); + } + + private MotionEvent upEvent() { + return upEvent(defaultDownTime()); + } + + private MotionEvent upEvent(long downTime) { + return MotionEvent.obtain(downTime, mClock.now(), + MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0); + } + + private long defaultDownTime() { + MotionEvent lastDown = mMgh.mDetectingStateHandler.mLastDown; + return lastDown == null ? mClock.now() - 1 : lastDown.getDownTime(); + } + + private MotionEvent pointerEvent(int action, float x, float y) { + MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties(); + defPointerProperties.id = 0; + defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; + MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties(); + pointerProperties.id = 1; + pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER; + + MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords(); + defPointerCoords.x = DEFAULT_X; + defPointerCoords.y = DEFAULT_Y; + MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords(); + pointerCoords.x = x; + pointerCoords.y = y; + + return MotionEvent.obtain( + /* downTime */ mClock.now(), + /* eventTime */ mClock.now(), + /* action */ action, + /* pointerCount */ 2, + /* pointerProperties */ new MotionEvent.PointerProperties[] { + defPointerProperties, pointerProperties }, + /* pointerCoords */ new MotionEvent.PointerCoords[] { defPointerCoords, pointerCoords }, + /* metaState */ 0, + /* buttonState */ 0, + /* xPrecision */ 1.0f, + /* yPrecision */ 1.0f, + /* deviceId */ 0, + /* edgeFlags */ 0, + /* source */ InputDevice.SOURCE_TOUCHSCREEN, + /* flags */ 0); + } + + private String stateDump() { + return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java index 04c02510cb3d..bc162977de2b 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java @@ -16,7 +16,7 @@ package com.android.server.backup; -import static com.android.server.testutis.TestUtils.assertExpectException; +import static com.android.server.testutils.TestUtils.assertExpectException; import static com.google.common.truth.Truth.assertThat; 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 e3faa5280859..e8a1811e1a63 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -22,7 +22,7 @@ import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY; import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY; import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED; -import static com.android.server.testutis.TestUtils.assertExpectException; +import static com.android.server.testutils.TestUtils.assertExpectException; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; @@ -54,11 +54,11 @@ import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.PasswordMetrics; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.StringParceledListSlice; import android.content.pm.UserInfo; import android.graphics.Color; @@ -167,6 +167,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID; when(getServices().packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN))) .thenReturn(true); + doReturn(Collections.singletonList(new ResolveInfo())) + .when(getServices().packageManager).queryBroadcastReceiversAsUser( + any(Intent.class), + anyInt(), + any(UserHandle.class)); // By default, pretend all users are running and unlocked. when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/testutils/OffsettableClock.java b/services/tests/servicestests/src/com/android/server/testutils/OffsettableClock.java new file mode 100644 index 000000000000..8dabbc4d4356 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/testutils/OffsettableClock.java @@ -0,0 +1,79 @@ +/* + * 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.testutils; + +import android.os.SystemClock; + +import java.util.function.LongSupplier; + +/** + * A time supplier (in the format of a {@code long} as the amount of milliseconds) similar + * to {@link SystemClock#uptimeMillis()}, but with the ability to {@link #fastForward} + * and {@link #rewind} + * + * Implements {@link LongSupplier} to be interchangeable with {@code SystemClock::uptimeMillis} + * + * Can be provided to {@link TestHandler} to "mock time" for the delayed execution testing + * + * @see OffsettableClock.Stopped for a version of this clock that does not advance on its own + */ +public class OffsettableClock implements LongSupplier { + private long mOffset = 0L; + + /** + * @return Current time in milliseconds, according to this clock + */ + public long now() { + return realNow() + mOffset; + } + + /** + * Can be overriden with a constant for a clock that stands still, and is only ever moved + * manually + */ + public long realNow() { + return SystemClock.uptimeMillis(); + } + + public void fastForward(long timeMs) { + mOffset += timeMs; + } + public void rewind(long timeMs) { + fastForward(-timeMs); + } + public void reset() { + mOffset = 0; + } + + /** @deprecated Only present for {@link LongSupplier} contract */ + @Override + @Deprecated + public long getAsLong() { + return now(); + } + + /** + * An {@link OffsettableClock} that does not advance with real time, and can only be + * advanced manually via {@link #fastForward} + */ + public static class Stopped extends OffsettableClock { + @Override + public long realNow() { + return 0L; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java new file mode 100644 index 000000000000..2d4bc0f8b7d0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/testutils/TestHandler.java @@ -0,0 +1,156 @@ +/* + * 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.testutils; + + +import static android.util.ExceptionUtils.getRootCause; +import static android.util.ExceptionUtils.propagate; + +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.ArrayMap; + +import java.util.Map; +import java.util.PriorityQueue; +import java.util.function.LongSupplier; + +/** + * A test {@link Handler} that stores incoming {@link Message}s and {@link Runnable callbacks} + * in a {@link PriorityQueue} based on time, to be manually processed later in a correct order + * either all together with {@link #flush}, or only those due at the current time with + * {@link #timeAdvance}. + * + * For the latter use case this also supports providing a custom clock (in a format of a + * milliseconds-returning {@link LongSupplier}), that will be used for storing the messages' + * timestamps to be posted at, and checked against during {@link #timeAdvance}. + * + * This allows to test code that uses {@link Handler}'s delayed invocation capabilities, such as + * {@link Handler#sendMessageDelayed} or {@link Handler#postDelayed} without resorting to + * synchronously {@link Thread#sleep}ing in your test. + * + * @see OffsettableClock for a useful custom clock implementation to use with this handler + */ +public class TestHandler extends Handler { + private static final LongSupplier DEFAULT_CLOCK = SystemClock::uptimeMillis; + + private final PriorityQueue<MsgInfo> mMessages = new PriorityQueue<>(); + /** + * Map of: {@code message id -> count of such messages currently pending } + */ + // Boxing is ok here - both msg ids and their pending counts tend to be well below 128 + private final Map<Integer, Integer> mPendingMsgTypeCounts = new ArrayMap<>(); + private final LongSupplier mClock; + + public TestHandler(Callback callback) { + this(callback, DEFAULT_CLOCK); + } + + public TestHandler(Callback callback, LongSupplier clock) { + super(callback); + mClock = clock; + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + mPendingMsgTypeCounts.put(msg.what, + mPendingMsgTypeCounts.getOrDefault(msg.what, 0) + 1); + + // uptimeMillis is an absolute time obtained as SystemClock.uptimeMillis() + offsetMillis + // if custom clock is given, recalculate the time with regards to it + if (mClock != DEFAULT_CLOCK) { + uptimeMillis = uptimeMillis - SystemClock.uptimeMillis() + mClock.getAsLong(); + } + + // post a dummy queue entry to keep track of message removal + return super.sendMessageAtTime(msg, Long.MAX_VALUE) + && mMessages.add(new MsgInfo(Message.obtain(msg), uptimeMillis)); + } + + /** @see TestHandler */ + public void timeAdvance() { + long now = mClock.getAsLong(); + while (!mMessages.isEmpty() && mMessages.peek().sendTime <= now) { + dispatch(mMessages.poll()); + } + } + + /** + * Dispatch all messages in order + * + * @see TestHandler + */ + public void flush() { + MsgInfo msg; + while ((msg = mMessages.poll()) != null) { + dispatch(msg); + } + } + + public PriorityQueue<MsgInfo> getPendingMessages() { + return new PriorityQueue<>(mMessages); + } + + private void dispatch(MsgInfo msg) { + int msgId = msg.message.what; + + if (!hasMessages(msgId)) { + // Handler.removeMessages(msgId) must have been called + return; + } + + try { + Integer pendingMsgCount = mPendingMsgTypeCounts.getOrDefault(msgId, 0); + if (pendingMsgCount <= 1) { + removeMessages(msgId); + } + mPendingMsgTypeCounts.put(msgId, pendingMsgCount - 1); + + dispatchMessage(msg.message); + } catch (Throwable t) { + // Append stack trace of this message being posted as a cause for a helpful + // test error message + throw propagate(getRootCause(t).initCause(msg.postPoint)); + } finally { + msg.message.recycle(); + } + } + + private class MsgInfo implements Comparable<MsgInfo> { + public final Message message; + public final long sendTime; + public final RuntimeException postPoint; + + private MsgInfo(Message message, long sendTime) { + this.message = message; + this.sendTime = sendTime; + this.postPoint = new RuntimeException("Message originated from here:"); + } + + @Override + public int compareTo(MsgInfo o) { + return (int) (sendTime - o.sendTime); + } + + @Override + public String toString() { + return "MsgInfo{" + + "message=" + message + + ", sendTime=" + sendTime + + '}'; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java b/services/tests/servicestests/src/com/android/server/testutils/TestUtils.java index 88289888b0dd..b200293ee916 100644 --- a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java +++ b/services/tests/servicestests/src/com/android/server/testutils/TestUtils.java @@ -13,12 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.testutis; +package com.android.server.testutils; import android.test.MoreAsserts; import junit.framework.Assert; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; + public class TestUtils { private TestUtils() { } @@ -44,4 +47,17 @@ public class TestUtils { Assert.fail("Expected exception type " + expectedExceptionType.getName() + " was not thrown"); } + + /** + * EasyMock-style "strict" mock that throws immediately on any interaction that was not + * explicitly allowed. + * + * You can allow certain method calls on a whitelist basis by stubbing them e.g. with + * {@link Mockito#doAnswer}, {@link Mockito#doNothing}, etc. + */ + public static <T> T strictMock(Class<T> c) { + return Mockito.mock(c, (Answer) invocation -> { + throw new AssertionError("Unexpected invocation: " + invocation); + }); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java index 9d32496c7817..0081214a24da 100644 --- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -126,6 +126,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { boolean mMovedToFullscreen; boolean mAnimationStarted; boolean mSchedulePipModeChangedOnStart; + boolean mForcePipModeChangedCallback; boolean mAnimationEnded; Rect mAnimationEndFinalStackBounds; boolean mSchedulePipModeChangedOnEnd; @@ -140,6 +141,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mAnimationStarted = false; mAnimationEnded = false; mAnimationEndFinalStackBounds = null; + mForcePipModeChangedCallback = false; mSchedulePipModeChangedOnStart = false; mSchedulePipModeChangedOnEnd = false; mStackBounds = from; @@ -148,10 +150,11 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { } @Override - public void onAnimationStart(boolean schedulePipModeChangedCallback) { + public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) { mAwaitingAnimationStart = false; mAnimationStarted = true; mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback; + mForcePipModeChangedCallback = forceUpdate; } @Override @@ -232,7 +235,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { return this; } - BoundsAnimationDriver restart(Rect to) { + BoundsAnimationDriver restart(Rect to, boolean expectStartedAndPipModeChangedCallback) { if (mAnimator == null) { throw new IllegalArgumentException("Call start() to start a new animation"); } @@ -251,8 +254,15 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { assertSame(oldAnimator, mAnimator); } - // No animation start for replacing animation - assertTrue(!mTarget.mAnimationStarted); + if (expectStartedAndPipModeChangedCallback) { + // Replacing animation with pending pip mode changed callback, ensure we update + assertTrue(mTarget.mAnimationStarted); + assertTrue(mTarget.mSchedulePipModeChangedOnStart); + assertTrue(mTarget.mForcePipModeChangedCallback); + } else { + // No animation start for replacing animation + assertTrue(!mTarget.mAnimationStarted); + } mTarget.mAnimationStarted = true; return this; } @@ -467,7 +477,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) - .restart(BOUNDS_FLOATING) + .restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */) .end() .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); } @@ -478,7 +488,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) - .restart(BOUNDS_SMALLER_FLOATING) + .restart(BOUNDS_SMALLER_FLOATING, + false /* expectStartedAndPipModeChangedCallback */) .end() .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); } @@ -486,10 +497,12 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception { + // When animating from fullscreen and the animation is interruped, we expect the animation + // start callback to be made, with a forced pip mode change callback mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) - .restart(BOUNDS_FULL) + .restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */) .end() .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); } @@ -512,7 +525,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) .expectStarted(SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) - .restart(BOUNDS_FULL) + .restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */) .end() .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); } @@ -523,7 +536,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) .expectStarted(SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) - .restart(BOUNDS_SMALLER_FLOATING) + .restart(BOUNDS_SMALLER_FLOATING, + false /* expectStartedAndPipModeChangedCallback */) .end() .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); } diff --git a/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java index 0f9b7217bd24..d441df024599 100644 --- a/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java @@ -16,24 +16,30 @@ package com.android.server.wm; - -import org.junit.Test; -import org.junit.runner.RunWith; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import android.content.res.Configuration; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; -import static org.junit.Assert.assertEquals; - /** * Test class for {@link ConfigurationContainer}. * @@ -201,6 +207,62 @@ public class ConfigurationContainerTests { assertEquals(mergedConfig2, child2.getConfiguration()); } + @Test + public void testSetWindowingMode() throws Exception { + final TestConfigurationContainer root = new TestConfigurationContainer(); + root.setWindowingMode(WINDOWING_MODE_UNDEFINED); + final TestConfigurationContainer child = root.addChild(); + child.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(WINDOWING_MODE_UNDEFINED, root.getWindowingMode()); + assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode()); + + root.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + assertEquals(WINDOWING_MODE_FULLSCREEN, root.getWindowingMode()); + assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode()); + } + + @Test + public void testSetActivityType() throws Exception { + final TestConfigurationContainer root = new TestConfigurationContainer(); + root.setActivityType(ACTIVITY_TYPE_UNDEFINED); + final TestConfigurationContainer child = root.addChild(); + child.setActivityType(ACTIVITY_TYPE_STANDARD); + assertEquals(ACTIVITY_TYPE_UNDEFINED, root.getActivityType()); + assertEquals(ACTIVITY_TYPE_STANDARD, child.getActivityType()); + + boolean gotException = false; + try { + // Can't change activity type once set. + child.setActivityType(ACTIVITY_TYPE_HOME); + } catch (IllegalStateException e) { + gotException = true; + } + assertTrue("Can't change activity type once set.", gotException); + + gotException = false; + try { + // Parent can't change child's activity type once set. + root.setActivityType(ACTIVITY_TYPE_HOME); + } catch (IllegalStateException e) { + gotException = true; + } + assertTrue("Parent can't change activity type once set.", gotException); + assertEquals(ACTIVITY_TYPE_HOME, root.getActivityType()); + + final TestConfigurationContainer child2 = new TestConfigurationContainer(); + child2.setActivityType(ACTIVITY_TYPE_RECENTS); + + gotException = false; + try { + // Can't re-parent to a different activity type. + root.addChild(child2); + } catch (IllegalStateException e) { + gotException = true; + } + assertTrue("Can't re-parent to a different activity type.", gotException); + + } + /** * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed * for testing. diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java index a1ff2d7f9267..3f75b412d100 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java @@ -27,8 +27,9 @@ import android.support.test.runner.AndroidJUnit4; import android.view.DisplayInfo; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS; import static android.app.WindowConfiguration.WINDOW_CONFIG_WINDOWING_MODE; import static android.content.pm.ActivityInfo.CONFIG_WINDOW_CONFIGURATION; @@ -118,6 +119,22 @@ public class WindowConfigurationTests extends WindowTestsBase { assertEquals(blankWinConfig.compareTo(winConfig1), 1); } + @Test + public void testSetActivityType() throws Exception { + final WindowConfiguration config = new WindowConfiguration(); + config.setActivityType(ACTIVITY_TYPE_HOME); + assertEquals(ACTIVITY_TYPE_HOME, config.getActivityType()); + + boolean gotException = false; + try { + // Can't change activity type once set. + config.setActivityType(ACTIVITY_TYPE_STANDARD); + } catch (IllegalStateException e) { + gotException = true; + } + assertTrue("Can't change activity type once set.", gotException); + } + /** Ensures the configuration app bounds at the root level match the app dimensions. */ @Test public void testAppBounds_RootConfigurationBounds() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java index 6e253e743ddb..df3f7553e83c 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java @@ -75,7 +75,7 @@ public class WindowFrameTests extends WindowTestsBase { final Rect mInsetBounds = new Rect(); boolean mFullscreenForTest = true; TaskWithBounds(Rect bounds) { - super(0, mStubStack, 0, sWm, null, null, 0, false, false, new TaskDescription(), null); + super(0, mStubStack, 0, sWm, null, null, 0, false, new TaskDescription(), null); mBounds = bounds; } @Override diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java index 40c79bbb183d..ebe00ce8d51d 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java @@ -67,7 +67,7 @@ public class WindowTestUtils { public static Task createTaskInStack(WindowManagerService service, TaskStack stack, int userId) { final Task newTask = new Task(sNextTaskId++, stack, userId, service, null, EMPTY, 0, false, - false, new ActivityManager.TaskDescription(), null); + new ActivityManager.TaskDescription(), null); stack.addTask(newTask, POSITION_TOP); return newTask; } @@ -175,10 +175,9 @@ public class WindowTestUtils { TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds, Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, - boolean homeTask, TaskWindowContainerController controller) { + TaskWindowContainerController controller) { super(taskId, stack, userId, service, bounds, overrideConfig, resizeMode, - supportsPictureInPicture, homeTask, new ActivityManager.TaskDescription(), - controller); + supportsPictureInPicture, new ActivityManager.TaskDescription(), controller); } boolean shouldDeferRemoval() { @@ -229,7 +228,7 @@ public class WindowTestUtils { } }, stackController, 0 /* userId */, null /* bounds */, EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, - false /* supportsPictureInPicture */, false /* homeTask*/, true /* toTop*/, + false /* supportsPictureInPicture */, true /* toTop*/, true /* showForAllUsers */, new ActivityManager.TaskDescription(), stackController.mService); } @@ -237,9 +236,9 @@ public class WindowTestUtils { @Override TestTask createTask(int taskId, TaskStack stack, int userId, Rect bounds, Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, - boolean homeTask, ActivityManager.TaskDescription taskDescription) { + ActivityManager.TaskDescription taskDescription) { return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig, resizeMode, - supportsPictureInPicture, homeTask, this); + supportsPictureInPicture, this); } } diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index bf56ec024699..b982d0d2ea63 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -43,6 +43,7 @@ enum { SDK_NOUGAT_MR1 = 25, SDK_O = 26, SDK_O_MR1 = 27, + SDK_P = 10000, // STOPSHIP Replace with the real version. }; #endif // H_AAPT_SDK_CONSTANTS diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 5c32ed4fd849..13584c0bf840 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -53,6 +53,7 @@ enum : ApiVersion { SDK_NOUGAT_MR1 = 25, SDK_O = 26, SDK_O_MR1 = 27, + SDK_P = 10000, // STOPSHIP Replace with the real version. }; ApiVersion FindAttributeSdkLevel(const ResourceId& id); diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index a0ffefad1e1c..6fb1793d90ed 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -293,6 +293,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["instrumentation"]["meta-data"] = meta_data_action; manifest_action["original-package"]; + manifest_action["overlay"]; manifest_action["protected-broadcast"]; manifest_action["uses-permission"]; manifest_action["uses-permission-sdk-23"]; |