Merge "Connect Filter Event Callback between the TunerService and TunerClient" into sc-dev
diff --git a/Android.bp b/Android.bp
index d170913..dc92586 100644
--- a/Android.bp
+++ b/Android.bp
@@ -632,6 +632,7 @@
],
sdk_version: "core_platform",
static_libs: [
+ "bouncycastle-repackaged-unbundled",
"framework-internal-utils",
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
// in favor of an API stubs dependency in java_library "framework" below.
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index cdf5df6c..30ed7de 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -8,6 +8,7 @@
cmds/input/
cmds/uinput/
core/jni/
+ libs/hwui/
libs/input/
native/
services/core/jni/
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index e05f0b0..6ab1051 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -665,9 +665,18 @@
WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal,
List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) {
mConfigIdentifier = configIdentifier;
- mDefaultMaxTotal = mMaxTotal = defaultMaxTotal;
+ mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, MAX_JOB_CONTEXTS_COUNT);
+ int numReserved = 0;
for (int i = defaultMin.size() - 1; i >= 0; --i) {
mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second);
+ numReserved += defaultMin.get(i).second;
+ }
+ if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) {
+ // We only create new configs on boot, so this should trigger during development
+ // (before the code gets checked in), so this makes sure the hard-coded defaults
+ // make sense. DeviceConfig values will be handled gracefully in update().
+ throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal
+ + " min=" + defaultMin + " max=" + defaultMax);
}
for (int i = defaultMax.size() - 1; i >= 0; --i) {
mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second);
@@ -782,15 +791,10 @@
mNumUnspecialized = mConfigMaxTotal;
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
- mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP);
- mNumUnspecialized -= mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG);
- calculateUnspecializedRemaining();
- }
-
- private void calculateUnspecializedRemaining() {
- mNumUnspecializedRemaining = mNumUnspecialized;
+ mNumUnspecializedRemaining = mConfigMaxTotal;
for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
- mNumUnspecializedRemaining -= mNumRunningJobs.valueAt(i);
+ mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
+ mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i)));
}
}
@@ -873,24 +877,42 @@
mNumUnspecialized = mConfigMaxTotal;
final int numTop = mNumRunningJobs.get(WORK_TYPE_TOP)
+ mNumPendingJobs.get(WORK_TYPE_TOP);
- final int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop);
+ int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop);
mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop);
mNumUnspecialized -= resTop;
final int numBg = mNumRunningJobs.get(WORK_TYPE_BG) + mNumPendingJobs.get(WORK_TYPE_BG);
- final int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
+ int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg);
mNumUnspecialized -= resBg;
- calculateUnspecializedRemaining();
+
+ mNumUnspecializedRemaining = mNumUnspecialized;
+ // Account for already running jobs after we've assigned the minimum number of slots.
+ int unspecializedAssigned;
+ int extraRunning = (mNumRunningJobs.get(WORK_TYPE_TOP) - resTop);
+ if (extraRunning > 0) {
+ unspecializedAssigned = Math.max(0,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP) - resTop,
+ extraRunning));
+ resTop += unspecializedAssigned;
+ mNumUnspecializedRemaining -= extraRunning;
+ }
+ extraRunning = (mNumRunningJobs.get(WORK_TYPE_BG) - resBg);
+ if (extraRunning > 0) {
+ unspecializedAssigned = Math.max(0,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG) - resBg, extraRunning));
+ resBg += unspecializedAssigned;
+ mNumUnspecializedRemaining -= extraRunning;
+ }
// Assign remaining unspecialized based on ranking.
- int unspecializedAssigned = Math.max(0,
- Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
- Math.min(mNumUnspecializedRemaining, numTop - resTop)));
+ unspecializedAssigned = Math.max(0,
+ Math.min(mNumUnspecializedRemaining,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop));
mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned);
mNumUnspecializedRemaining -= unspecializedAssigned;
unspecializedAssigned = Math.max(0,
- Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
- Math.min(mNumUnspecializedRemaining, numBg - resBg)));
+ Math.min(mNumUnspecializedRemaining,
+ Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
mNumUnspecializedRemaining -= unspecializedAssigned;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 73386a7..8dae210 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -157,7 +157,7 @@
field public static final String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
field public static final String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
field public static final String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
- field public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
+ field @Deprecated public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
field public static final String START_VIEW_PERMISSION_USAGE = "android.permission.START_VIEW_PERMISSION_USAGE";
field public static final String STATUS_BAR = "android.permission.STATUS_BAR";
field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
@@ -722,6 +722,7 @@
field public static final int gwpAsanMode = 16844310; // 0x1010616
field public static final int hand_hour = 16843011; // 0x1010103
field public static final int hand_minute = 16843012; // 0x1010104
+ field public static final int hand_second = 16844323; // 0x1010623
field public static final int handle = 16843354; // 0x101025a
field public static final int handleProfiling = 16842786; // 0x1010022
field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
@@ -8350,6 +8351,7 @@
method public android.appwidget.AppWidgetProviderInfo clone();
method public int describeContents();
method public final android.os.UserHandle getProfile();
+ method @NonNull public android.content.pm.ActivityInfo getProviderInfo();
method @Nullable public final String loadDescription(@NonNull android.content.Context);
method public final android.graphics.drawable.Drawable loadIcon(@NonNull android.content.Context, int);
method public final String loadLabel(android.content.pm.PackageManager);
@@ -11868,6 +11870,7 @@
}
public class LauncherActivityInfo {
+ method @NonNull public android.content.pm.ActivityInfo getActivityInfo();
method public android.content.pm.ApplicationInfo getApplicationInfo();
method public android.graphics.drawable.Drawable getBadgedIcon(int);
method public android.content.ComponentName getComponentName();
@@ -12240,6 +12243,7 @@
method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo, @Nullable android.content.res.Configuration) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
@@ -12468,6 +12472,7 @@
field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
field public static final int PERMISSION_DENIED = -1; // 0xffffffff
field public static final int PERMISSION_GRANTED = 0; // 0x0
+ field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
field public static final int SIGNATURE_MATCH = 0; // 0x0
field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -16800,6 +16805,18 @@
package android.hardware {
+ public abstract class Battery {
+ ctor public Battery();
+ method @FloatRange(from=-1.0F, to=1.0f) public abstract float getCapacity();
+ method public abstract int getStatus();
+ method public abstract boolean hasBattery();
+ field public static final int STATUS_CHARGING = 2; // 0x2
+ field public static final int STATUS_DISCHARGING = 3; // 0x3
+ field public static final int STATUS_FULL = 5; // 0x5
+ field public static final int STATUS_NOT_CHARGING = 4; // 0x4
+ field public static final int STATUS_UNKNOWN = 1; // 0x1
+ }
+
@Deprecated public class Camera {
method @Deprecated public final void addCallbackBuffer(byte[]);
method @Deprecated public final void autoFocus(android.hardware.Camera.AutoFocusCallback);
@@ -31847,6 +31864,10 @@
public final class ImplicitDirectBootViolation extends android.os.strictmode.Violation {
}
+ public final class IncorrectContextUseViolation extends android.os.strictmode.Violation {
+ ctor public IncorrectContextUseViolation(@NonNull String, @NonNull Throwable);
+ }
+
public class InstanceCountViolation extends android.os.strictmode.Violation {
method public long getNumberOfInstances();
}
@@ -46360,6 +46381,7 @@
public final class InputDevice implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public android.hardware.Battery getBattery();
method public int getControllerNumber();
method public String getDescriptor();
method public static android.view.InputDevice getDevice(int);
@@ -46414,6 +46436,7 @@
field public static final int SOURCE_MOUSE = 8194; // 0x2002
field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
+ field public static final int SOURCE_SENSOR = 67108864; // 0x4000000
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -53055,6 +53078,10 @@
ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet);
ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int);
ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int, int);
+ method @Deprecated public void setDial(@NonNull android.graphics.drawable.Icon);
+ method @Deprecated public void setHourHand(@NonNull android.graphics.drawable.Icon);
+ method @Deprecated public void setMinuteHand(@NonNull android.graphics.drawable.Icon);
+ method @Deprecated public void setSecondHand(@Nullable android.graphics.drawable.Icon);
}
public class ArrayAdapter<T> extends android.widget.BaseAdapter implements android.widget.Filterable android.widget.ThemedSpinnerAdapter {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 0f49dc5..e8650fa 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -157,6 +157,10 @@
package android.net {
+ public final class ConnectivityFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ }
+
public class ConnectivityManager {
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 845bb3d..683bc84 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -138,6 +138,7 @@
field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER";
field public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
+ field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS";
@@ -1493,6 +1494,169 @@
}
+package android.app.smartspace {
+
+ public final class SmartspaceAction implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public CharSequence getContentDescription();
+ method @Nullable public android.os.Bundle getExtras();
+ method @Nullable public android.graphics.drawable.Icon getIcon();
+ method @NonNull public String getId();
+ method @Nullable public android.content.Intent getIntent();
+ method @Nullable public android.app.PendingIntent getPendingIntent();
+ method @Nullable public CharSequence getSubtitle();
+ method @NonNull public CharSequence getTitle();
+ method @Nullable public android.os.UserHandle getUserHandle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceAction> CREATOR;
+ }
+
+ public static final class SmartspaceAction.Builder {
+ ctor public SmartspaceAction.Builder(@NonNull String, @NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceAction build();
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setContentDescription(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIntent(@Nullable android.content.Intent);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setSubtitle(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setUserHandle(@Nullable android.os.UserHandle);
+ }
+
+ public final class SmartspaceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.os.Bundle getExtras();
+ method @NonNull public String getPackageName();
+ method @NonNull public int getSmartspaceTargetCount();
+ method @NonNull public String getUiSurface();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceConfig> CREATOR;
+ }
+
+ public static final class SmartspaceConfig.Builder {
+ ctor public SmartspaceConfig.Builder(@NonNull android.content.Context, @NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceConfig build();
+ method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setSmartspaceTargetCount(int);
+ }
+
+ public final class SmartspaceManager {
+ method @NonNull public android.app.smartspace.SmartspaceSession createSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig);
+ }
+
+ public final class SmartspaceSession implements java.lang.AutoCloseable {
+ method public void close();
+ method public void destroy();
+ method protected void finalize();
+ method public void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceTargetEvent);
+ method public void registerSmartspaceUpdates(@NonNull java.util.concurrent.Executor, @NonNull android.app.smartspace.SmartspaceSession.Callback);
+ method public void requestSmartspaceUpdate();
+ method public void unregisterSmartspaceUpdates(@NonNull android.app.smartspace.SmartspaceSession.Callback);
+ }
+
+ public static interface SmartspaceSession.Callback {
+ method public void onTargetsAvailable(@NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+ }
+
+ public final class SmartspaceSessionId implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getId();
+ method @NonNull public int getUserId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceSessionId> CREATOR;
+ }
+
+ public final class SmartspaceTarget implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getActionChips();
+ method @Nullable public String getAssociatedSmartspaceTargetId();
+ method @Nullable public android.app.smartspace.SmartspaceAction getBaseAction();
+ method @NonNull public android.content.ComponentName getComponentName();
+ method @NonNull public long getCreationTimeMillis();
+ method @NonNull public long getExpiryTimeMillis();
+ method @NonNull public int getFeatureType();
+ method @Nullable public android.app.smartspace.SmartspaceAction getHeaderAction();
+ method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getIconGrid();
+ method @NonNull public float getScore();
+ method @Nullable public android.net.Uri getSliceUri();
+ method @NonNull public String getSmartspaceTargetId();
+ method @Nullable public String getSourceNotificationKey();
+ method @NonNull public android.os.UserHandle getUserHandle();
+ method @Nullable public android.appwidget.AppWidgetProviderInfo getWidgetId();
+ method @NonNull public boolean isSensitive();
+ method @NonNull public boolean shouldShowExpanded();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTarget> CREATOR;
+ field public static final int FEATURE_ALARM = 7; // 0x7
+ field public static final int FEATURE_BEDTIME_ROUTINE = 16; // 0x10
+ field public static final int FEATURE_CALENDAR = 2; // 0x2
+ field public static final int FEATURE_COMMUTE_TIME = 3; // 0x3
+ field public static final int FEATURE_CONSENT = 11; // 0xb
+ field public static final int FEATURE_ETA_MONITORING = 18; // 0x12
+ field public static final int FEATURE_FITNESS_TRACKING = 17; // 0x11
+ field public static final int FEATURE_FLIGHT = 4; // 0x4
+ field public static final int FEATURE_LOYALTY_CARD = 14; // 0xe
+ field public static final int FEATURE_MEDIA = 15; // 0xf
+ field public static final int FEATURE_MISSED_CALL = 19; // 0x13
+ field public static final int FEATURE_ONBOARDING = 8; // 0x8
+ field public static final int FEATURE_PACKAGE_TRACKING = 20; // 0x14
+ field public static final int FEATURE_REMINDER = 6; // 0x6
+ field public static final int FEATURE_SHOPPING_LIST = 13; // 0xd
+ field public static final int FEATURE_SPORTS = 9; // 0x9
+ field public static final int FEATURE_STOCK_PRICE_CHANGE = 12; // 0xc
+ field public static final int FEATURE_STOPWATCH = 22; // 0x16
+ field public static final int FEATURE_TIMER = 21; // 0x15
+ field public static final int FEATURE_TIPS = 5; // 0x5
+ field public static final int FEATURE_UNDEFINED = 0; // 0x0
+ field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17
+ field public static final int FEATURE_WEATHER = 1; // 0x1
+ field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa
+ }
+
+ public static final class SmartspaceTarget.Builder {
+ ctor public SmartspaceTarget.Builder(@NonNull String, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+ method @NonNull public android.app.smartspace.SmartspaceTarget build();
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setActionChips(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setAssociatedSmartspaceTargetId(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setBaseAction(@NonNull android.app.smartspace.SmartspaceAction);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setCreationTimeMillis(@NonNull long);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setExpiryTimeMillis(@NonNull long);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setFeatureType(@NonNull int);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setHeaderAction(@NonNull android.app.smartspace.SmartspaceAction);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setIconGrid(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setScore(@NonNull float);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSensitive(@NonNull boolean);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(@NonNull boolean);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidgetId(@NonNull android.appwidget.AppWidgetProviderInfo);
+ }
+
+ public final class SmartspaceTargetEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public int getEventType();
+ method @Nullable public String getSmartspaceActionId();
+ method @Nullable public android.app.smartspace.SmartspaceTarget getSmartspaceTarget();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTargetEvent> CREATOR;
+ field public static final int EVENT_TARGET_BLOCK = 5; // 0x5
+ field public static final int EVENT_TARGET_DISMISS = 4; // 0x4
+ field public static final int EVENT_TARGET_INTERACTION = 1; // 0x1
+ field public static final int EVENT_TARGET_IN_VIEW = 2; // 0x2
+ field public static final int EVENT_TARGET_OUT_OF_VIEW = 3; // 0x3
+ field public static final int EVENT_UI_SURFACE_IN_VIEW = 6; // 0x6
+ field public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7; // 0x7
+ }
+
+ public static final class SmartspaceTargetEvent.Builder {
+ ctor public SmartspaceTargetEvent.Builder(int);
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent build();
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceActionId(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceTarget(@NonNull android.app.smartspace.SmartspaceTarget);
+ }
+
+}
+
package android.app.time {
public final class TimeManager {
@@ -1955,6 +2119,7 @@
field public static final String ROLLBACK_SERVICE = "rollback";
field public static final String SEARCH_UI_SERVICE = "search_ui";
field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
+ field public static final String SMARTSPACE_SERVICE = "smartspace";
field public static final String STATS_MANAGER = "stats";
field public static final String STATUS_BAR_SERVICE = "statusbar";
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
@@ -3183,6 +3348,7 @@
public class ContextHubClientCallback {
ctor public ContextHubClientCallback();
+ method public void onClientAuthorizationChanged(@NonNull android.hardware.location.ContextHubClient, long, int);
method public void onHubReset(android.hardware.location.ContextHubClient);
method public void onMessageFromNanoApp(android.hardware.location.ContextHubClient, android.hardware.location.NanoAppMessage);
method public void onNanoAppAborted(android.hardware.location.ContextHubClient, long, int);
@@ -3219,6 +3385,7 @@
public class ContextHubIntentEvent {
method @NonNull public static android.hardware.location.ContextHubIntentEvent fromIntent(@NonNull android.content.Intent);
+ method public int getClientAuthorizationState();
method @NonNull public android.hardware.location.ContextHubInfo getContextHubInfo();
method public int getEventType();
method public int getNanoAppAbortCode();
@@ -3227,8 +3394,10 @@
}
public final class ContextHubManager {
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@Nullable android.content.Context, @NonNull android.hardware.location.ContextHubInfo, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.location.ContextHubClientCallback);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.ContextHubClientCallback, @NonNull java.util.concurrent.Executor);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.ContextHubClientCallback);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@Nullable android.content.Context, @NonNull android.hardware.location.ContextHubInfo, @NonNull android.app.PendingIntent, long);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.app.PendingIntent, long);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
@@ -3246,6 +3415,10 @@
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public int unloadNanoApp(int);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
+ field public static final int AUTHORIZATION_DENIED = 0; // 0x0
+ field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
+ field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
+ field public static final int EVENT_CLIENT_AUTHORIZATION = 7; // 0x7
field public static final int EVENT_HUB_RESET = 6; // 0x6
field public static final int EVENT_NANOAPP_ABORTED = 4; // 0x4
field public static final int EVENT_NANOAPP_DISABLED = 3; // 0x3
@@ -3253,6 +3426,7 @@
field public static final int EVENT_NANOAPP_LOADED = 0; // 0x0
field public static final int EVENT_NANOAPP_MESSAGE = 5; // 0x5
field public static final int EVENT_NANOAPP_UNLOADED = 1; // 0x1
+ field public static final String EXTRA_CLIENT_AUTHORIZATION_STATE = "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE";
field public static final String EXTRA_CONTEXT_HUB_INFO = "android.hardware.location.extra.CONTEXT_HUB_INFO";
field public static final String EXTRA_EVENT_TYPE = "android.hardware.location.extra.EVENT_TYPE";
field public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE";
@@ -3491,8 +3665,10 @@
public final class NanoAppState implements android.os.Parcelable {
ctor public NanoAppState(long, int, boolean);
+ ctor public NanoAppState(long, int, boolean, @NonNull java.util.List<java.lang.String>);
method public int describeContents();
method public long getNanoAppId();
+ method @NonNull public java.util.List<java.lang.String> getNanoAppPermissions();
method public long getNanoAppVersion();
method public boolean isEnabled();
method public void writeToParcel(android.os.Parcel, int);
@@ -4635,6 +4811,7 @@
public static class AudioAttributes.Builder {
method public android.media.AudioAttributes.Builder addBundle(@NonNull android.os.Bundle);
method public android.media.AudioAttributes.Builder setCapturePreset(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioAttributes.Builder setHotwordMode();
method public android.media.AudioAttributes.Builder setInternalCapturePreset(int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int);
}
@@ -7105,6 +7282,7 @@
method @Nullable public String getSsid();
method @NonNull public int[] getTransportTypes();
method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+ field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
@@ -9851,6 +10029,7 @@
method public void onNotificationDirectReplied(@NonNull String);
method @Nullable public abstract android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification);
method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
+ method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel, @NonNull android.service.notification.NotificationListenerService.RankingMap);
method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
method public void onNotificationVisibilityChanged(@NonNull String, boolean);
@@ -10076,6 +10255,21 @@
}
+package android.service.smartspace {
+
+ public abstract class SmartspaceService extends android.app.Service {
+ ctor public SmartspaceService();
+ method @MainThread public abstract void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull android.app.smartspace.SmartspaceTargetEvent);
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onCreateSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig, @NonNull android.app.smartspace.SmartspaceSessionId);
+ method @MainThread public abstract void onDestroy(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method public abstract void onDestroySmartspaceSession(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method @MainThread public abstract void onRequestSmartspaceUpdate(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method public final void updateSmartspaceTargets(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+ }
+
+}
+
package android.service.storage {
public abstract class ExternalStorageService extends android.app.Service {
@@ -13160,6 +13354,7 @@
field public static final String IPTYPE_IPV6 = "IPV6";
field public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = "sip_config_auhentication_header_string";
field public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = "sip_config_authentication_nonce_string";
+ field public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING = "sip_config_cellular_network_info_header_string";
field public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string";
field public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string";
field public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string";
@@ -13192,6 +13387,7 @@
field public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = "sip_config_ue_public_port_with_nat_int";
field public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = "sip_config_ue_public_user_id_string";
field public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = "sip_config_uri_user_part_string";
+ field public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING = "sip_config_sip_user_agent_header_string";
field public static final String SIP_TRANSPORT_TCP = "TCP";
field public static final String SIP_TRANSPORT_UDP = "UDP";
}
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 8b3cee4..58f6c52 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -6,9 +6,11 @@
BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
+
ExecutorRegistration: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
Registration methods should have overload that accepts delivery Executor: `setOnRtpRxNoticeListener`
-
+
+
GenericException: android.app.prediction.AppPredictor#finalize():
GenericException: android.hardware.location.ContextHubClient#finalize():
@@ -21,6 +23,8 @@
IntentBuilderName: android.app.search.SearchAction#getIntent():
+IntentBuilderName: android.app.smartspace.SmartspaceAction#getIntent():
+ Methods creating an Intent should be named `create<Foo>Intent()`, was `getIntent`
KotlinKeyword: android.app.Notification#when:
@@ -83,6 +87,10 @@
+OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent):
+ Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent`
+
+
ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
@@ -187,11 +195,10 @@
SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
- SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-
SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
@@ -258,3 +265,5 @@
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle):
+UserHandleName: android.app.smartspace.SmartspaceAction.Builder#setUserHandle(android.os.UserHandle):
+ Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bc1858b..e0391ee 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -401,10 +401,10 @@
field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
- field public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
+ field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
field public static final int CODE_OK = 0; // 0x0
field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
- field public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
+ field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
field public static final int CODE_SYSTEM_USER = 10; // 0xa
field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
@@ -2324,6 +2324,8 @@
}
public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
+ method @Nullable public final android.os.IBinder getWindowContextToken();
+ method public final void setWindowContextToken(@NonNull android.os.IBinder);
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40
field public CharSequence accessibilityTitle;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9b141b7..1f9cb64 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2291,9 +2291,10 @@
* Resources if one has already been created.
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
- String[] libDirs, LoadedApk pkgInfo) {
+ String[] libDirs, LoadedApk pkgInfo, Configuration overrideConfig) {
return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
- null, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null);
+ null, overrideConfig, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(),
+ null);
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index d716a3c..7410a1c 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -50,7 +50,7 @@
*
* <p>
* Application process could die for many reasons, for example {@link #REASON_LOW_MEMORY}
- * when it was killed by the ystem because it was running low on memory. Reason
+ * when it was killed by the system because it was running low on memory. Reason
* of the death can be retrieved via {@link #getReason}. Besides the reason, there are a few other
* auxiliary APIs like {@link #getStatus} and {@link #getImportance} to help the caller with
* additional diagnostic information.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 77542bd..7e7f887 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -70,6 +70,7 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
@@ -1691,20 +1692,29 @@
@Override
public Resources getResourcesForApplication(@NonNull ApplicationInfo app)
throws NameNotFoundException {
+ return getResourcesForApplication(app, null);
+ }
+
+ @Override
+ public Resources getResourcesForApplication(@NonNull ApplicationInfo app,
+ @Nullable Configuration configuration) throws NameNotFoundException {
if (app.packageName.equals("system")) {
- return mContext.mMainThread.getSystemUiContext().getResources();
+ Context sysuiContext = mContext.mMainThread.getSystemUiContext();
+ if (configuration != null) {
+ sysuiContext = sysuiContext.createConfigurationContext(configuration);
+ }
+ return sysuiContext.getResources();
}
final boolean sameUid = (app.uid == Process.myUid());
final Resources r = mContext.mMainThread.getTopLevelResources(
- sameUid ? app.sourceDir : app.publicSourceDir,
- sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
- app.resourceDirs, app.sharedLibraryFiles,
- mContext.mPackageInfo);
+ sameUid ? app.sourceDir : app.publicSourceDir,
+ sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
+ app.resourceDirs, app.sharedLibraryFiles,
+ mContext.mPackageInfo, configuration);
if (r != null) {
return r;
}
throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
-
}
@Override
diff --git a/graphics/java/android/graphics/GameManager.java b/core/java/android/app/GameManager.java
similarity index 98%
rename from graphics/java/android/graphics/GameManager.java
rename to core/java/android/app/GameManager.java
index a58aeb4..8b6570f 100644
--- a/graphics/java/android/graphics/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.graphics;
+package android.app;
import android.annotation.IntDef;
import android.annotation.SystemService;
diff --git a/graphics/java/android/graphics/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
similarity index 96%
rename from graphics/java/android/graphics/IGameManagerService.aidl
rename to core/java/android/app/IGameManagerService.aidl
index 7d3a4fb..c8e1478 100644
--- a/graphics/java/android/graphics/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -14,7 +14,7 @@
** limitations under the License.
*/
-package android.graphics;
+package android.app;
/**
* @hide
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 534f3e2..671315f 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemApi.Client;
import android.annotation.TestApi;
@@ -1275,6 +1276,7 @@
* @param flags MATCH_* flags from {@link android.content.pm.PackageManager}.
* @hide
*/
+ @SuppressLint("NullableCollection")
@RequiresPermission(permission.GET_INTENT_SENDER_INTENT)
@SystemApi(client = Client.MODULE_LIBRARIES)
@TestApi
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 1498dae7..f8c33b5 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,7 @@
import android.app.role.RoleFrameworkInitializer;
import android.app.search.SearchUiManager;
import android.app.slice.SliceManager;
+import android.app.smartspace.SmartspaceManager;
import android.app.time.TimeManager;
import android.app.timedetector.TimeDetector;
import android.app.timedetector.TimeDetectorImpl;
@@ -72,7 +73,6 @@
import android.content.rollback.RollbackManagerFrameworkInitializer;
import android.debug.AdbManager;
import android.debug.IAdbManager;
-import android.graphics.GameManager;
import android.graphics.fonts.FontManager;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
@@ -120,21 +120,16 @@
import android.media.tv.TvInputManager;
import android.media.tv.tunerresourcemanager.ITunerResourceManager;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
-import android.net.ConnectivityDiagnosticsManager;
-import android.net.ConnectivityManager;
+import android.net.ConnectivityFrameworkInitializer;
import android.net.EthernetManager;
-import android.net.IConnectivityManager;
import android.net.IEthernetManager;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
-import android.net.ITestNetworkManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
-import android.net.TestNetworkManager;
import android.net.TetheringManager;
-import android.net.VpnManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
@@ -163,7 +158,6 @@
import android.os.IncidentManager;
import android.os.PowerManager;
import android.os.RecoverySystem;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StatsFrameworkInitializer;
@@ -370,15 +364,6 @@
// (which extends it).
SYSTEM_SERVICE_NAMES.put(android.text.ClipboardManager.class, Context.CLIPBOARD_SERVICE);
- registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class,
- new StaticApplicationContextServiceFetcher<ConnectivityManager>() {
- @Override
- public ConnectivityManager createService(Context context) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- return new ConnectivityManager(context, service);
- }});
-
registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() {
@Override
public IBinder createService() throws ServiceNotFoundException {
@@ -412,50 +397,6 @@
return new IpSecManager(ctx, service);
}});
- registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
- new CachedServiceFetcher<VpnManager>() {
- @Override
- public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- return new VpnManager(ctx, service);
- }});
-
- registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
- ConnectivityDiagnosticsManager.class,
- new CachedServiceFetcher<ConnectivityDiagnosticsManager>() {
- @Override
- public ConnectivityDiagnosticsManager createService(ContextImpl ctx)
- throws ServiceNotFoundException {
- // ConnectivityDiagnosticsManager is backed by ConnectivityService
- IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- return new ConnectivityDiagnosticsManager(ctx, service);
- }});
-
- registerService(
- Context.TEST_NETWORK_SERVICE,
- TestNetworkManager.class,
- new StaticApplicationContextServiceFetcher<TestNetworkManager>() {
- @Override
- public TestNetworkManager createService(Context context)
- throws ServiceNotFoundException {
- IBinder csBinder =
- ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager csMgr =
- IConnectivityManager.Stub.asInterface(csBinder);
-
- final IBinder tnBinder;
- try {
- tnBinder = csMgr.startOrGetTestNetworkService();
- } catch (RemoteException e) {
- throw new ServiceNotFoundException(Context.TEST_NETWORK_SERVICE);
- }
- ITestNetworkManager tnMgr = ITestNetworkManager.Stub.asInterface(tnBinder);
- return new TestNetworkManager(tnMgr);
- }
- });
-
registerService(Context.COUNTRY_DETECTOR, CountryDetector.class,
new StaticServiceFetcher<CountryDetector>() {
@Override
@@ -1224,6 +1165,16 @@
}
});
+ registerService(Context.SMARTSPACE_SERVICE, SmartspaceManager.class,
+ new CachedServiceFetcher<SmartspaceManager>() {
+ @Override
+ public SmartspaceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+ return b == null ? null : new SmartspaceManager(ctx);
+ }
+ });
+
registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
new CachedServiceFetcher<AppPredictionManager>() {
@Override
@@ -1441,6 +1392,7 @@
try {
// Note: the following functions need to be @SystemApis, once they become mainline
// modules.
+ ConnectivityFrameworkInitializer.registerServiceWrappers();
JobSchedulerFrameworkInitializer.registerServiceWrappers();
BlobStoreManagerFrameworkInitializer.initialize();
TelephonyFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ada703b..e84d4a5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -453,59 +453,6 @@
"android.app.action.PROVISION_FINANCED_DEVICE";
/**
- * Activity action: Starts the provisioning flow which sets up a managed device.
- * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
- *
- * <p>NOTE: This is only supported on split system user devices, and puts the device into a
- * management state that is distinct from that reached by
- * {@link #ACTION_PROVISION_MANAGED_DEVICE} - specifically the device owner runs on the system
- * user, and only has control over device-wide policies, not individual users and their data.
- * The primary benefit is that multiple non-system users are supported when provisioning using
- * this form of device management.
- *
- * <p>During device owner provisioning a device admin app is set as the owner of the device.
- * A device owner has full control over the device. The device owner can not be modified by the
- * user.
- *
- * <p>A typical use case would be a device that is owned by a company, but used by either an
- * employee or client.
- *
- * <p>An intent with this action can be sent only on an unprovisioned device.
- * It is possible to check if provisioning is allowed or not by querying the method
- * {@link #isProvisioningAllowed(String)}.
- *
- * <p>The intent contains the following extras:
- * <ul>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
- * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
- * </ul>
- *
- * <p>When device owner provisioning has completed, an intent of the type
- * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
- * device owner.
- *
- * <p>From version {@link android.os.Build.VERSION_CODES#O}, when device owner provisioning has
- * completed, along with the above broadcast, activity intent
- * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the device owner.
- *
- * <p>If provisioning fails, the device is factory reset.
- *
- * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
- * of the provisioning flow was successful, although this doesn't guarantee the full flow will
- * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
- * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
- = "android.app.action.PROVISION_MANAGED_SHAREABLE_DEVICE";
-
- /**
* Activity action: Finalizes management provisioning, should be used after user-setup
* has been completed and {@link #getUserProvisioningState()} returns one of:
* <ul>
@@ -1990,8 +1937,8 @@
* Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when provisioning is allowed.
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}
+ * when provisioning is allowed.
*
* @hide
*/
@@ -2001,9 +1948,8 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the device already has a device
- * owner.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the device already has a
+ * device owner.
*
* @hide
*/
@@ -2013,9 +1959,8 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user has a profile owner and for
- * {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user has a profile owner
+ * and for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
*
* @hide
*/
@@ -2025,8 +1970,7 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user isn't running.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user isn't running.
*
* @hide
*/
@@ -2036,9 +1980,8 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the device has already been setup and
- * for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the device has already been
+ * setup and for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
*
* @hide
*/
@@ -2064,8 +2007,7 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the user is not a system user.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the user is not a system user.
*
* @hide
*/
@@ -2075,9 +2017,8 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} and {@link #ACTION_PROVISION_MANAGED_USER}
- * when the device is a watch and is already paired.
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
+ * {@link #ACTION_PROVISION_MANAGED_USER} when the device is a watch and is already paired.
*
* @hide
*/
@@ -2121,14 +2062,11 @@
/**
* TODO (b/137101239): clean up split system user codes
- * Result code for {@link #checkProvisioningPreCondition}.
- *
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices not running with split system
- * user.
*
* @hide
- */
+ * @deprecated not used anymore but can't be removed since it's a @TestApi.
+ **/
+ @Deprecated
@TestApi
public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
@@ -2136,8 +2074,7 @@
* Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices which do not support device
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support device
* admins.
*
* @hide
@@ -2149,11 +2086,10 @@
* TODO (b/137101239): clean up split system user codes
* Result code for {@link #checkProvisioningPreCondition}.
*
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a
- * system user on a split system user device.
- *
* @hide
+ * @deprecated not used anymore but can't be removed since it's a @TestApi.
*/
+ @Deprecated
@TestApi
public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
@@ -7147,17 +7083,9 @@
}
/**
+ * TODO (b/137101239): remove this method in follow-up CL
+ * since it's only used for split system user.
* Called by a device owner to set whether all users created on the device should be ephemeral.
- * <p>
- * The system user is exempt from this policy - it is never ephemeral.
- * <p>
- * The calling device admin must be the device owner. If it is not, a security exception will be
- * thrown.
- *
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param forceEphemeralUsers If true, all the existing users will be deleted and all
- * subsequently created users will be ephemeral.
- * @throws SecurityException if {@code admin} is not a device owner.
* @hide
*/
public void setForceEphemeralUsers(
@@ -7173,6 +7101,8 @@
}
/**
+ * TODO (b/137101239): remove this method in follow-up CL
+ * since it's only used for split system user.
* @return true if all users are created ephemeral.
* @throws SecurityException if {@code admin} is not a device owner.
* @hide
@@ -10731,9 +10661,7 @@
* profile or user, setting the given package as owner.
*
* @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_PROFILE},
- * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE},
- * {@link #ACTION_PROVISION_MANAGED_USER}
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE}
* @param packageName The package of the component that would be set as device, user, or profile
* owner.
* @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 65a2164..2b52875 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -2,6 +2,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Activity;
import android.content.ComponentName;
@@ -1542,6 +1543,7 @@
* {@link View#getOnReceiveContentMimeTypes()} for details.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public String[] getOnReceiveContentMimeTypes() {
return mOnReceiveContentMimeTypes;
}
diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java
index 447ca31..3ab20bb 100644
--- a/core/java/android/app/search/Query.java
+++ b/core/java/android/app/search/Query.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
@@ -46,6 +47,7 @@
public Query(@NonNull String input,
long timestamp,
+ @SuppressLint("NullableCollection")
@Nullable Bundle extras) {
mInput = input;
mTimestamp = timestamp;
@@ -69,6 +71,7 @@
}
@Nullable
+ @SuppressLint("NullableCollection")
public Bundle getExtras() {
return mExtras;
}
diff --git a/core/java/android/app/search/SearchAction.java b/core/java/android/app/search/SearchAction.java
index a76154a..9e40e7e 100644
--- a/core/java/android/app/search/SearchAction.java
+++ b/core/java/android/app/search/SearchAction.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.content.Intent;
@@ -167,6 +168,7 @@
/**
* Returns the extra bundle for this object.
*/
+ @SuppressLint("NullableCollection")
public @Nullable Bundle getExtras() {
return mExtras;
}
@@ -325,7 +327,8 @@
* Sets the extra.
*/
@NonNull
- public SearchAction.Builder setExtras(@Nullable Bundle extras) {
+ public SearchAction.Builder setExtras(
+ @SuppressLint("NullableCollection") @Nullable Bundle extras) {
mExtras = extras;
return this;
}
diff --git a/core/java/android/app/search/SearchContext.java b/core/java/android/app/search/SearchContext.java
index 9bf766d..548b7da 100644
--- a/core/java/android/app/search/SearchContext.java
+++ b/core/java/android/app/search/SearchContext.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
@@ -52,7 +53,7 @@
public SearchContext(int resultTypes,
int queryTimeoutMillis,
- @Nullable Bundle extras) {
+ @SuppressLint("NullableCollection") @Nullable Bundle extras) {
mResultTypes = resultTypes;
mTimeoutMillis = queryTimeoutMillis;
mExtras = extras;
@@ -83,6 +84,7 @@
}
@Nullable
+ @SuppressLint("NullableCollection")
public Bundle getExtras() {
return mExtras;
}
diff --git a/core/java/android/app/search/SearchTarget.java b/core/java/android/app/search/SearchTarget.java
index cac22d81..6a80f8b 100644
--- a/core/java/android/app/search/SearchTarget.java
+++ b/core/java/android/app/search/SearchTarget.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.appwidget.AppWidgetProviderInfo;
import android.content.pm.ShortcutInfo;
@@ -224,6 +225,7 @@
* Return extra bundle.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public Bundle getExtras() {
return mExtras;
}
@@ -386,7 +388,7 @@
* TODO: add comment
*/
@NonNull
- public Builder setExtras(@Nullable Bundle extras) {
+ public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
mExtras = extras;
return this;
}
diff --git a/core/java/android/app/smartspace/ISmartspaceCallback.aidl b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
new file mode 100644
index 0000000..df105f9
--- /dev/null
+++ b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+oneway interface ISmartspaceCallback {
+
+ void onResult(in ParceledListSlice result);
+}
diff --git a/core/java/android/app/smartspace/ISmartspaceManager.aidl b/core/java/android/app/smartspace/ISmartspaceManager.aidl
new file mode 100644
index 0000000..e7ec889
--- /dev/null
+++ b/core/java/android/app/smartspace/ISmartspaceManager.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+interface ISmartspaceManager {
+
+ void createSmartspaceSession(in SmartspaceConfig config, in SmartspaceSessionId sessionId,
+ in IBinder token);
+
+ void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+ void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+ void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void destroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/app/smartspace/OWNERS b/core/java/android/app/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/core/java/android/app/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceAction.java b/core/java/android/app/smartspace/SmartspaceAction.java
new file mode 100644
index 0000000..033cda1
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceAction.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceAction} represents an action which can be taken by a user by tapping on either
+ * the title, the subtitle or on the icon. Supported instances are Intents, PendingIntents or a
+ * ShortcutInfo (by putting the ShortcutInfoId in the bundle). These actions can be called from
+ * another process or within the client process.
+ *
+ * Clients can also receive conditional Intents/PendingIntents in the extras bundle which are
+ * supposed to be fired when the conditions are met. For example, a user can invoke a dismiss/block
+ * action on a game score card but the intention is to only block the team and not the entire
+ * feature.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceAction implements Parcelable {
+
+ private static final String TAG = "SmartspaceAction";
+
+ /** A unique Id of this {@link SmartspaceAction}. */
+ @NonNull
+ private final String mId;
+
+ /** An Icon which can be displayed in the UI. */
+ @Nullable
+ private final Icon mIcon;
+
+ /** Title associated with an action. */
+ @NonNull
+ private final CharSequence mTitle;
+
+ /** Subtitle associated with an action. */
+ @Nullable
+ private final CharSequence mSubtitle;
+
+ @Nullable
+ private final CharSequence mContentDescription;
+
+ @Nullable
+ private final PendingIntent mPendingIntent;
+
+ @Nullable
+ private final Intent mIntent;
+
+ @Nullable
+ private final UserHandle mUserHandle;
+
+ @Nullable
+ private Bundle mExtras;
+
+ SmartspaceAction(Parcel in) {
+ mId = in.readString();
+ mIcon = in.readTypedObject(Icon.CREATOR);
+ mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+ mExtras = in.readTypedObject(Bundle.CREATOR);
+ }
+
+ private SmartspaceAction(
+ @NonNull String id,
+ @Nullable Icon icon,
+ @NonNull CharSequence title,
+ @Nullable CharSequence subtitle,
+ @Nullable CharSequence contentDescription,
+ @Nullable PendingIntent pendingIntent,
+ @Nullable Intent intent,
+ @Nullable UserHandle userHandle,
+ @Nullable Bundle extras) {
+ mId = Objects.requireNonNull(id);
+ mIcon = icon;
+ mTitle = Objects.requireNonNull(title);
+ mSubtitle = subtitle;
+ mContentDescription = contentDescription;
+ mPendingIntent = pendingIntent;
+ mIntent = intent;
+ mUserHandle = userHandle;
+ mExtras = extras;
+ }
+
+ /**
+ * Returns the unique id of this object.
+ */
+ public @NonNull String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns an icon representing the action.
+ */
+ public @Nullable Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns a title representing the action.
+ */
+ public @NonNull CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns a subtitle representing the action.
+ */
+ public @Nullable CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * Returns a content description representing the action.
+ */
+ public @Nullable CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Returns the action intent.
+ */
+ public @Nullable PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ /**
+ * Returns the intent.
+ */
+ public @Nullable Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Returns the user handle.
+ */
+ public @Nullable UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ /**
+ * Returns the extra bundle for this object.
+ */
+ public @Nullable Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceAction)) return false;
+ SmartspaceAction that = (SmartspaceAction) o;
+ return mId.equals(that.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mId);
+ out.writeTypedObject(mIcon, flags);
+ TextUtils.writeToParcel(mTitle, out, flags);
+ TextUtils.writeToParcel(mSubtitle, out, flags);
+ TextUtils.writeToParcel(mContentDescription, out, flags);
+ out.writeTypedObject(mPendingIntent, flags);
+ out.writeTypedObject(mIntent, flags);
+ out.writeTypedObject(mUserHandle, flags);
+ out.writeBundle(mExtras);
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceAction{"
+ + "mId='" + mId + '\''
+ + ", mIcon=" + mIcon
+ + ", mTitle=" + mTitle
+ + ", mSubtitle=" + mSubtitle
+ + ", mContentDescription=" + mContentDescription
+ + ", mPendingIntent=" + mPendingIntent
+ + ", mIntent=" + mIntent
+ + ", mUserHandle=" + mUserHandle
+ + ", mExtras=" + mExtras
+ + '}';
+ }
+
+ public static final @NonNull Creator<SmartspaceAction> CREATOR =
+ new Creator<SmartspaceAction>() {
+ public SmartspaceAction createFromParcel(Parcel in) {
+ return new SmartspaceAction(in);
+ }
+ public SmartspaceAction[] newArray(int size) {
+ return new SmartspaceAction[size];
+ }
+ };
+
+ /**
+ * A builder for Smartspace action object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @NonNull
+ private String mId;
+
+ @Nullable
+ private Icon mIcon;
+
+ @NonNull
+ private CharSequence mTitle;
+
+ @Nullable
+ private CharSequence mSubtitle;
+
+ @Nullable
+ private CharSequence mContentDescription;
+
+ @Nullable
+ private PendingIntent mPendingIntent;
+
+ @Nullable
+ private Intent mIntent;
+
+ @Nullable
+ private UserHandle mUserHandle;
+
+ @Nullable
+ private Bundle mExtras;
+
+ /**
+ * Id and title are required.
+ */
+ public Builder(@NonNull String id, @NonNull String title) {
+ mId = Objects.requireNonNull(id);
+ mTitle = Objects.requireNonNull(title);
+ }
+
+ /**
+ * Sets the icon.
+ */
+ @NonNull
+ public Builder setIcon(
+ @Nullable Icon icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the subtitle.
+ */
+ @NonNull
+ public Builder setSubtitle(
+ @Nullable CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Sets the content description.
+ */
+ @NonNull
+ public Builder setContentDescription(
+ @Nullable CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Sets the pending intent.
+ */
+ @NonNull
+ public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the user handle.
+ */
+ @NonNull
+ public Builder setUserHandle(@Nullable UserHandle userHandle) {
+ mUserHandle = userHandle;
+ return this;
+ }
+
+ /**
+ * Sets the intent.
+ */
+ @NonNull
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the extra.
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceAction instance.
+ *
+ * @throws IllegalStateException if no target is set
+ */
+ @NonNull
+ public SmartspaceAction build() {
+ return new SmartspaceAction(mId, mIcon, mTitle, mSubtitle, mContentDescription,
+ mPendingIntent, mIntent, mUserHandle, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceConfig.aidl b/core/java/android/app/smartspace/SmartspaceConfig.aidl
new file mode 100644
index 0000000..136b6f4
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceConfig.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.app.smartspace;
+
+parcelable SmartspaceConfig;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceConfig.java b/core/java/android/app/smartspace/SmartspaceConfig.java
new file mode 100644
index 0000000..1f9cbb5
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceConfig.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceConfig} instance is supposed to be created by a smartspace client for each
+ * UISurface. The client can specify some initialization conditions for the UISurface like its name,
+ * expected number of smartspace cards etc. The clients can also specify if they want periodic
+ * updates or their desired maximum refresh frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceConfig implements Parcelable {
+
+ /**
+ * The least number of smartspace targets expected to be predicted by the backend. The backend
+ * will always try to satisfy this threshold but it is not guaranteed to always meet it.
+ */
+ private final int mSmartspaceTargetCount;
+
+ /**
+ * A {@link mUiSurface} is the name of the surface which will be used to display the cards. A
+ * few examples are homescreen, lockscreen, aod etc.
+ */
+ @NonNull
+ private final String mUiSurface;
+
+ /** Package name of the client. */
+ @NonNull
+ private String mPackageName;
+
+ /** Send other client UI configurations in extras.
+ *
+ * This can include:
+ *
+ * - Desired maximum update frequency
+ * - Request to get periodic updates
+ * - Request to support multiple clients for the same UISurface.
+ */
+ @Nullable
+ private final Bundle mExtras;
+
+ private SmartspaceConfig(@NonNull String uiSurface, int numPredictedTargets,
+ @NonNull String packageName, @Nullable Bundle extras) {
+ mUiSurface = uiSurface;
+ mSmartspaceTargetCount = numPredictedTargets;
+ mPackageName = packageName;
+ mExtras = extras;
+ }
+
+ private SmartspaceConfig(Parcel parcel) {
+ mUiSurface = parcel.readString();
+ mSmartspaceTargetCount = parcel.readInt();
+ mPackageName = parcel.readString();
+ mExtras = parcel.readBundle();
+ }
+
+ /** Returns the package name of the prediction context. */
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /** Returns the number of smartspace targets requested by the user. */
+ @NonNull
+ public int getSmartspaceTargetCount() {
+ return mSmartspaceTargetCount;
+ }
+
+ /** Returns the UISurface requested by the client. */
+ @NonNull
+ public String getUiSurface() {
+ return mUiSurface;
+ }
+
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mUiSurface);
+ dest.writeInt(mSmartspaceTargetCount);
+ dest.writeString(mPackageName);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SmartspaceConfig that = (SmartspaceConfig) o;
+ return mSmartspaceTargetCount == that.mSmartspaceTargetCount
+ && Objects.equals(mUiSurface, that.mUiSurface)
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mExtras, that.mExtras);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSmartspaceTargetCount, mUiSurface, mPackageName, mExtras);
+ }
+
+ /**
+ * @see Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceConfig> CREATOR =
+ new Creator<SmartspaceConfig>() {
+ public SmartspaceConfig createFromParcel(Parcel parcel) {
+ return new SmartspaceConfig(parcel);
+ }
+
+ public SmartspaceConfig[] newArray(int size) {
+ return new SmartspaceConfig[size];
+ }
+ };
+
+ /**
+ * A builder for {@link SmartspaceConfig}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @NonNull
+ private int mSmartspaceTargetCount = 5; // Default count is 5
+ @NonNull
+ private final String mUiSurface;
+ @NonNull
+ private final String mPackageName;
+ @NonNull
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * @param context The {@link Context} which is used to fetch the package name.
+ * @param uiSurface the UI Surface name associated with this context.
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull Context context, @NonNull String uiSurface) {
+ mPackageName = context.getPackageName();
+ this.mUiSurface = uiSurface;
+ }
+
+ /**
+ * Used to set the expected number of cards for this context.
+ */
+ @NonNull
+ public Builder setSmartspaceTargetCount(int smartspaceTargetCount) {
+ this.mSmartspaceTargetCount = smartspaceTargetCount;
+ return this;
+ }
+
+ /**
+ * Used to send a bundle containing extras for the {@link SmartspaceConfig}.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ this.mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Returns an instance of {@link SmartspaceConfig}.
+ */
+ @NonNull
+ public SmartspaceConfig build() {
+ return new SmartspaceConfig(mUiSurface, mSmartspaceTargetCount, mPackageName, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceManager.java b/core/java/android/app/smartspace/SmartspaceManager.java
new file mode 100644
index 0000000..ff5eb10
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceManager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+
+import java.util.Objects;
+
+/**
+ * Smartspace is a container in Android which is used to show contextual content powered by the
+ * intelligence service running on the device. A smartspace container can be on AoD, lockscreen or
+ * on the homescreen and can show personalized cards which are either derived from on device or
+ * online signals.
+ *
+ * {@link SmartspaceManager} is a system service that provides methods to create Smartspace session
+ * clients. An instance of this class is returned when a client calls
+ * <code> context.getSystemService("smartspace"); </code>.
+ *
+ * After receiving the service, a client must call
+ * {@link SmartspaceManager#createSmartspaceSession(SmartspaceConfig)} with a corresponding
+ * {@link SmartspaceConfig} to get an instance of {@link SmartspaceSession}.
+ * This session is then a client's point of contact with the api. They can send events, request for
+ * updates using the session. It is client's duty to call {@link SmartspaceSession#destroy()} to
+ * destroy the session once they no longer need it.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceManager {
+
+ private final Context mContext;
+
+ /**
+ * @hide
+ */
+ public SmartspaceManager(Context context) {
+ mContext = Objects.requireNonNull(context);
+ }
+
+ /**
+ * Creates a new Smartspace session.
+ */
+ @NonNull
+ public SmartspaceSession createSmartspaceSession(
+ @NonNull SmartspaceConfig smartspaceConfig) {
+ return new SmartspaceSession(mContext, smartspaceConfig);
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java
new file mode 100644
index 0000000..16def61
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSession.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.smartspace.ISmartspaceCallback.Stub;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * Client API to share information about the Smartspace UI state and execute query.
+ *
+ * <p>
+ * Usage: <pre> {@code
+ *
+ * class MyActivity {
+ * private SmartspaceSession mSmartspaceSession;
+ *
+ * void onCreate() {
+ * mSmartspaceSession = mSmartspaceManager.createSmartspaceSession(smartspaceConfig)
+ * mSmartspaceSession.registerSmartspaceUpdates(...)
+ * }
+ *
+ * void onStart() {
+ * mSmartspaceSession.requestSmartspaceUpdate()
+ * }
+ *
+ * void onTouch(...) OR
+ * void onStateTransitionStarted(...) OR
+ * void onResume(...) OR
+ * void onStop(...) {
+ * mSmartspaceSession.notifyEvent(event);
+ * }
+ *
+ * void onDestroy() {
+ * mSmartspaceSession.unregisterPredictionUpdates()
+ * mSmartspaceSession.destroy();
+ * }
+ *
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSession implements AutoCloseable {
+
+ private static final String TAG = SmartspaceSession.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private final android.app.smartspace.ISmartspaceManager mInterface;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ private final SmartspaceSessionId mSessionId;
+ private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
+ private final IBinder mToken = new Binder();
+
+ /**
+ * Creates a new Smartspace ui client.
+ * <p>
+ * The caller should call {@link SmartspaceSession#destroy()} to dispose the client once it
+ * no longer used.
+ *
+ * @param context the {@link Context} of the user of this {@link SmartspaceSession}.
+ * @param smartspaceConfig the Smartspace context.
+ */
+ // b/177858121 Create weak reference child objects to not leak context.
+ SmartspaceSession(@NonNull Context context, @NonNull SmartspaceConfig smartspaceConfig) {
+ IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+ mInterface = android.app.smartspace.ISmartspaceManager.Stub.asInterface(b);
+ mSessionId = new SmartspaceSessionId(
+ context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+ try {
+ mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to cerate Smartspace session", e);
+ e.rethrowFromSystemServer();
+ }
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Notifies the Smartspace service of a Smartspace target event.
+ *
+ * @param event The {@link SmartspaceTargetEvent} that represents the Smartspace target event.
+ */
+ public void notifySmartspaceEvent(@NonNull SmartspaceTargetEvent event) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ try {
+ mInterface.notifySmartspaceEvent(mSessionId, event);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify event", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests the smartspace service for an update.
+ */
+ public void requestSmartspaceUpdate() {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ try {
+ mInterface.requestSmartspaceUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to request update.", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests the smartspace service provide continuous updates of smartspace cards via the
+ * provided callback, until the given callback is unregistered.
+ *
+ * @param callbackExecutor The callback executor to use when calling the callback.
+ * @param callback The Callback to be called when updates of Smartspace targets are
+ * available.
+ */
+ public void registerSmartspaceUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull Callback callback) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ if (mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback is already registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+ callback::onTargetsAvailable);
+ mRegisteredCallbacks.put(callback, callbackWrapper);
+ mInterface.registerSmartspaceUpdates(mSessionId, callbackWrapper);
+ mInterface.requestSmartspaceUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register for smartspace updates", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Requests the smartspace service to stop providing continuous updates to the provided
+ * callback until the callback is re-registered.
+ *
+ * @see {@link SmartspaceSession#registerSmartspaceUpdates(Executor, Callback)}.
+ *
+ * @param callback The callback to be unregistered.
+ */
+ public void unregisterSmartspaceUpdates(@NonNull Callback callback) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ if (!mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback was never registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+ mInterface.unregisterSmartspaceUpdates(mSessionId, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister for smartspace updates", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Destroys the client and unregisters the callback. Any method on this class after this call
+ * will throw {@link IllegalStateException}.
+ */
+ public void destroy() {
+ if (!mIsClosed.getAndSet(true)) {
+ mCloseGuard.close();
+
+ // Do destroy;
+ try {
+ mInterface.destroySmartspaceSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify Smartspace target event", e);
+ e.rethrowFromSystemServer();
+ }
+ } else {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ if (!mIsClosed.get()) {
+ destroy();
+ }
+ } finally {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ finalize();
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ }
+
+ /**
+ * Callback for receiving smartspace updates.
+ */
+ public interface Callback {
+
+ /**
+ * Called when a new set of smartspace targets are available.
+ *
+ * @param targets Sorted list of smartspace targets.
+ */
+ void onTargetsAvailable(@NonNull List<SmartspaceTarget> targets);
+ }
+
+ static class CallbackWrapper extends Stub {
+
+ private final Consumer<List<SmartspaceTarget>> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull Consumer<List<SmartspaceTarget>> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onResult(ParceledListSlice result) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
+ }
+ mExecutor.execute(() -> mCallback.accept(result.getList()));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceSessionId.aidl b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
new file mode 100644
index 0000000..a864412
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.app.smartspace;
+
+parcelable SmartspaceSessionId;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceSessionId.java b/core/java/android/app/smartspace/SmartspaceSessionId.java
new file mode 100644
index 0000000..5220c35
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The id for an Smartspace session. See {@link SmartspaceSession}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSessionId implements Parcelable {
+
+ @NonNull
+ private final String mId;
+
+ @NonNull
+ private final int mUserId;
+
+ /**
+ * Creates a new id for a Smartspace session.
+ *
+ * @hide
+ */
+ public SmartspaceSessionId(@NonNull final String id, @NonNull final int userId) {
+ mId = id;
+ mUserId = userId;
+ }
+
+ private SmartspaceSessionId(Parcel p) {
+ mId = p.readString();
+ mUserId = p.readInt();
+ }
+
+ /**
+ * Returns a {@link String} Id of this sessionId.
+ */
+ @Nullable
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the userId associated with this sessionId.
+ */
+ @NonNull
+ public int getUserId() {
+ return mUserId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+ SmartspaceSessionId other = (SmartspaceSessionId) o;
+ return mId.equals(other.mId) && mUserId == other.mUserId;
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceSessionId{"
+ + "mId='" + mId + '\''
+ + ", mUserId=" + mUserId
+ + '}';
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mUserId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeInt(mUserId);
+ }
+
+ public static final @NonNull Creator<SmartspaceSessionId> CREATOR =
+ new Creator<SmartspaceSessionId>() {
+ public SmartspaceSessionId createFromParcel(Parcel parcel) {
+ return new SmartspaceSessionId(parcel);
+ }
+
+ public SmartspaceSessionId[] newArray(int size) {
+ return new SmartspaceSessionId[size];
+ }
+ };
+}
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.aidl b/core/java/android/app/smartspace/SmartspaceTarget.aidl
new file mode 100644
index 0000000..3442cf2
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTarget.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.app.smartspace;
+
+parcelable SmartspaceTarget;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
new file mode 100644
index 0000000..ce5040e
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceTarget} is a data class which holds all properties necessary to inflate a
+ * smartspace card. It contains data and related metadata which is supposed to be utilized by
+ * smartspace clients based on their own UI/UX requirements. Some of the properties have
+ * {@link SmartspaceAction} as their type because they can have associated actions.
+ *
+ * <p><b>NOTE: </b>
+ * If {@link mWidgetId} is set, it should be preferred over all other properties.
+ * Else, if {@link mSliceUri} is set, it should be preferred over all other data properties.
+ * Otherwise, the instance should be treated as a data object.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTarget implements Parcelable {
+
+ /** A unique Id for an instance of {@link SmartspaceTarget}. */
+ @NonNull
+ private final String mSmartspaceTargetId;
+
+ /** A {@link SmartspaceAction} for the header in the Smartspace card. */
+ @Nullable
+ private final SmartspaceAction mHeaderAction;
+
+ /** A {@link SmartspaceAction} for the base action in the Smartspace card. */
+ @Nullable
+ private final SmartspaceAction mBaseAction;
+
+ /** A timestamp indicating when the card was created. */
+ @NonNull
+ private final long mCreationTimeMillis;
+
+ /**
+ * A timestamp indicating when the card should be removed from view, in case the service
+ * disconnects or restarts.
+ */
+ @NonNull
+ private final long mExpiryTimeMillis;
+
+ /** A score assigned to a target. */
+ @NonNull
+ private final float mScore;
+
+ /** A {@link List<SmartspaceAction>} containing all action chips. */
+ @NonNull
+ private final List<SmartspaceAction> mActionChips;
+
+ /** A {@link List<SmartspaceAction>} containing all icons for the grid. */
+ @NonNull
+ private final List<SmartspaceAction> mIconGrid;
+
+ /**
+ * {@link FeatureType} indicating the feature type of this card.
+ *
+ * @see FeatureType
+ */
+ @FeatureType
+ @NonNull
+ private final int mFeatureType;
+
+ /**
+ * Indicates whether the content is sensitive. Certain UI surfaces may choose to skip rendering
+ * real content until the device is unlocked.
+ */
+ @NonNull
+ private final boolean mSensitive;
+
+ /** Indicating if the UI should show this target in its expanded state. */
+ @NonNull
+ private final boolean mShouldShowExpanded;
+
+ /** A Notification key if the target was generated using a notification. */
+ @Nullable
+ private final String mSourceNotificationKey;
+
+ /** {@link ComponentName} for this target. */
+ @NonNull
+ private final ComponentName mComponentName;
+
+ /** {@link UserHandle} for this target. */
+ @NonNull
+ private final UserHandle mUserHandle;
+
+ /** Target Ids of other {@link SmartspaceTarget}s if they are associated with this target. */
+ @Nullable
+ private final String mAssociatedSmartspaceTargetId;
+
+ /** {@link Uri} Slice Uri if this target is a slice. */
+ @Nullable
+ private final Uri mSliceUri;
+
+ /** {@link AppWidgetProviderInfo} if this target is a widget. */
+ @Nullable
+ private final AppWidgetProviderInfo mWidgetId;
+
+ public static final int FEATURE_UNDEFINED = 0;
+ public static final int FEATURE_WEATHER = 1;
+ public static final int FEATURE_CALENDAR = 2;
+ public static final int FEATURE_COMMUTE_TIME = 3;
+ public static final int FEATURE_FLIGHT = 4;
+ public static final int FEATURE_TIPS = 5;
+ public static final int FEATURE_REMINDER = 6;
+ public static final int FEATURE_ALARM = 7;
+ public static final int FEATURE_ONBOARDING = 8;
+ public static final int FEATURE_SPORTS = 9;
+ public static final int FEATURE_WEATHER_ALERT = 10;
+ public static final int FEATURE_CONSENT = 11;
+ public static final int FEATURE_STOCK_PRICE_CHANGE = 12;
+ public static final int FEATURE_SHOPPING_LIST = 13;
+ public static final int FEATURE_LOYALTY_CARD = 14;
+ public static final int FEATURE_MEDIA = 15;
+ public static final int FEATURE_BEDTIME_ROUTINE = 16;
+ public static final int FEATURE_FITNESS_TRACKING = 17;
+ public static final int FEATURE_ETA_MONITORING = 18;
+ public static final int FEATURE_MISSED_CALL = 19;
+ public static final int FEATURE_PACKAGE_TRACKING = 20;
+ public static final int FEATURE_TIMER = 21;
+ public static final int FEATURE_STOPWATCH = 22;
+ public static final int FEATURE_UPCOMING_ALARM = 23;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"FEATURE_"}, value = {
+ FEATURE_UNDEFINED,
+ FEATURE_WEATHER,
+ FEATURE_CALENDAR,
+ FEATURE_COMMUTE_TIME,
+ FEATURE_FLIGHT,
+ FEATURE_TIPS,
+ FEATURE_REMINDER,
+ FEATURE_ALARM,
+ FEATURE_ONBOARDING,
+ FEATURE_SPORTS,
+ FEATURE_WEATHER_ALERT,
+ FEATURE_CONSENT,
+ FEATURE_STOCK_PRICE_CHANGE,
+ FEATURE_SHOPPING_LIST,
+ FEATURE_LOYALTY_CARD,
+ FEATURE_MEDIA,
+ FEATURE_BEDTIME_ROUTINE,
+ FEATURE_FITNESS_TRACKING,
+ FEATURE_ETA_MONITORING,
+ FEATURE_MISSED_CALL,
+ FEATURE_PACKAGE_TRACKING,
+ FEATURE_TIMER,
+ FEATURE_STOPWATCH,
+ FEATURE_UPCOMING_ALARM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FeatureType {
+ }
+
+ private SmartspaceTarget(Parcel in) {
+ this.mSmartspaceTargetId = in.readString();
+ this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR);
+ this.mBaseAction = in.readTypedObject(SmartspaceAction.CREATOR);
+ this.mCreationTimeMillis = in.readLong();
+ this.mExpiryTimeMillis = in.readLong();
+ this.mScore = in.readFloat();
+ this.mActionChips = in.createTypedArrayList(SmartspaceAction.CREATOR);
+ this.mIconGrid = in.createTypedArrayList(SmartspaceAction.CREATOR);
+ this.mFeatureType = in.readInt();
+ this.mSensitive = in.readBoolean();
+ this.mShouldShowExpanded = in.readBoolean();
+ this.mSourceNotificationKey = in.readString();
+ this.mComponentName = in.readTypedObject(ComponentName.CREATOR);
+ this.mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+ this.mAssociatedSmartspaceTargetId = in.readString();
+ this.mSliceUri = in.readTypedObject(Uri.CREATOR);
+ this.mWidgetId = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
+ }
+
+ private SmartspaceTarget(String smartspaceTargetId,
+ SmartspaceAction headerAction, SmartspaceAction baseAction, long creationTimeMillis,
+ long expiryTimeMillis, float score,
+ List<SmartspaceAction> actionChips,
+ List<SmartspaceAction> iconGrid, int featureType, boolean sensitive,
+ boolean shouldShowExpanded, String sourceNotificationKey,
+ ComponentName componentName, UserHandle userHandle,
+ String associatedSmartspaceTargetId, Uri sliceUri,
+ AppWidgetProviderInfo widgetId) {
+ mSmartspaceTargetId = smartspaceTargetId;
+ mHeaderAction = headerAction;
+ mBaseAction = baseAction;
+ mCreationTimeMillis = creationTimeMillis;
+ mExpiryTimeMillis = expiryTimeMillis;
+ mScore = score;
+ mActionChips = actionChips;
+ mIconGrid = iconGrid;
+ mFeatureType = featureType;
+ mSensitive = sensitive;
+ mShouldShowExpanded = shouldShowExpanded;
+ mSourceNotificationKey = sourceNotificationKey;
+ mComponentName = componentName;
+ mUserHandle = userHandle;
+ mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+ mSliceUri = sliceUri;
+ mWidgetId = widgetId;
+ }
+
+ /**
+ * Returns the Id of the target.
+ */
+ @NonNull
+ public String getSmartspaceTargetId() {
+ return mSmartspaceTargetId;
+ }
+
+ /**
+ * Returns the header action of the target.
+ */
+ @Nullable
+ public SmartspaceAction getHeaderAction() {
+ return mHeaderAction;
+ }
+
+ /**
+ * Returns the base action of the target.
+ */
+ @Nullable
+ public SmartspaceAction getBaseAction() {
+ return mBaseAction;
+ }
+
+ /**
+ * Returns the creation time of the target.
+ */
+ @NonNull
+ public long getCreationTimeMillis() {
+ return mCreationTimeMillis;
+ }
+
+ /**
+ * Returns the expiry time of the target.
+ */
+ @NonNull
+ public long getExpiryTimeMillis() {
+ return mExpiryTimeMillis;
+ }
+
+ /**
+ * Returns the score of the target.
+ */
+ @NonNull
+ public float getScore() {
+ return mScore;
+ }
+
+ /**
+ * Return the action chips of the target.
+ */
+ @NonNull
+ public List<SmartspaceAction> getActionChips() {
+ return mActionChips;
+ }
+
+ /**
+ * Return the icons of the target.
+ */
+ @NonNull
+ public List<SmartspaceAction> getIconGrid() {
+ return mIconGrid;
+ }
+
+ /**
+ * Returns the feature type of the target.
+ */
+ @NonNull
+ public int getFeatureType() {
+ return mFeatureType;
+ }
+
+ /**
+ * Returns whether the target is sensitive or not.
+ */
+ @NonNull
+ public boolean isSensitive() {
+ return mSensitive;
+ }
+
+ /**
+ * Returns whether the target should be shown in expanded state.
+ */
+ @NonNull
+ public boolean shouldShowExpanded() {
+ return mShouldShowExpanded;
+ }
+
+ /**
+ * Returns the source notification key of the target.
+ */
+ @Nullable
+ public String getSourceNotificationKey() {
+ return mSourceNotificationKey;
+ }
+
+ /**
+ * Returns the component name of the target.
+ */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * Returns the user handle of the target.
+ */
+ @NonNull
+ public UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ /**
+ * Returns the id of a target associated with this instance.
+ */
+ @Nullable
+ public String getAssociatedSmartspaceTargetId() {
+ return mAssociatedSmartspaceTargetId;
+ }
+
+ /**
+ * Returns the slice uri, if the target is a slice.
+ */
+ @Nullable
+ public Uri getSliceUri() {
+ return mSliceUri;
+ }
+
+ /**
+ * Returns the AppWidgetProviderInfo, if the target is a widget.
+ */
+ @Nullable
+ public AppWidgetProviderInfo getWidgetId() {
+ return mWidgetId;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceTarget> CREATOR = new Creator<SmartspaceTarget>() {
+ @Override
+ public SmartspaceTarget createFromParcel(Parcel source) {
+ return new SmartspaceTarget(source);
+ }
+
+ @Override
+ public SmartspaceTarget[] newArray(int size) {
+ return new SmartspaceTarget[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(this.mSmartspaceTargetId);
+ dest.writeTypedObject(this.mHeaderAction, flags);
+ dest.writeTypedObject(this.mBaseAction, flags);
+ dest.writeLong(this.mCreationTimeMillis);
+ dest.writeLong(this.mExpiryTimeMillis);
+ dest.writeFloat(this.mScore);
+ dest.writeTypedList(this.mActionChips);
+ dest.writeTypedList(this.mIconGrid);
+ dest.writeInt(this.mFeatureType);
+ dest.writeBoolean(this.mSensitive);
+ dest.writeBoolean(this.mShouldShowExpanded);
+ dest.writeString(this.mSourceNotificationKey);
+ dest.writeTypedObject(this.mComponentName, flags);
+ dest.writeTypedObject(this.mUserHandle, flags);
+ dest.writeString(this.mAssociatedSmartspaceTargetId);
+ dest.writeTypedObject(this.mSliceUri, flags);
+ dest.writeTypedObject(this.mWidgetId, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceTarget{"
+ + "mSmartspaceTargetId='" + mSmartspaceTargetId + '\''
+ + ", mHeaderAction=" + mHeaderAction
+ + ", mBaseAction=" + mBaseAction
+ + ", mCreationTimeMillis=" + mCreationTimeMillis
+ + ", mExpiryTimeMillis=" + mExpiryTimeMillis
+ + ", mScore=" + mScore
+ + ", mActionChips=" + mActionChips
+ + ", mIconGrid=" + mIconGrid
+ + ", mFeatureType=" + mFeatureType
+ + ", mSensitive=" + mSensitive
+ + ", mShouldShowExpanded=" + mShouldShowExpanded
+ + ", mSourceNotificationKey='" + mSourceNotificationKey + '\''
+ + ", mComponentName=" + mComponentName
+ + ", mUserHandle=" + mUserHandle
+ + ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\''
+ + ", mSliceUri=" + mSliceUri
+ + ", mWidgetId=" + mWidgetId
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SmartspaceTarget that = (SmartspaceTarget) o;
+ return mCreationTimeMillis == that.mCreationTimeMillis
+ && mExpiryTimeMillis == that.mExpiryTimeMillis
+ && Float.compare(that.mScore, mScore) == 0
+ && mFeatureType == that.mFeatureType
+ && mSensitive == that.mSensitive
+ && mShouldShowExpanded == that.mShouldShowExpanded
+ && mSmartspaceTargetId.equals(that.mSmartspaceTargetId)
+ && Objects.equals(mHeaderAction, that.mHeaderAction)
+ && Objects.equals(mBaseAction, that.mBaseAction)
+ && Objects.equals(mActionChips, that.mActionChips)
+ && Objects.equals(mIconGrid, that.mIconGrid)
+ && Objects.equals(mSourceNotificationKey, that.mSourceNotificationKey)
+ && mComponentName.equals(that.mComponentName)
+ && mUserHandle.equals(that.mUserHandle)
+ && Objects.equals(mAssociatedSmartspaceTargetId,
+ that.mAssociatedSmartspaceTargetId)
+ && Objects.equals(mSliceUri, that.mSliceUri)
+ && Objects.equals(mWidgetId, that.mWidgetId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
+ mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
+ mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+ }
+
+ /**
+ * A builder for {@link SmartspaceTarget} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private final String mSmartspaceTargetId;
+ private SmartspaceAction mHeaderAction;
+ private SmartspaceAction mBaseAction;
+ private long mCreationTimeMillis;
+ private long mExpiryTimeMillis;
+ private float mScore;
+ private List<SmartspaceAction> mActionChips = new ArrayList<>();
+ private List<SmartspaceAction> mIconGrid = new ArrayList<>();
+ private int mFeatureType;
+ private boolean mSensitive;
+ private boolean mShouldShowExpanded;
+ private String mSourceNotificationKey;
+ private final ComponentName mComponentName;
+ private final UserHandle mUserHandle;
+ private String mAssociatedSmartspaceTargetId;
+ private Uri mSliceUri;
+ private AppWidgetProviderInfo mWidgetId;
+
+ /**
+ * A builder for {@link SmartspaceTarget}.
+ *
+ * @param smartspaceTargetId the id of this target
+ * @param componentName the componentName of this target
+ * @param userHandle the userHandle of this target
+ */
+ public Builder(@NonNull String smartspaceTargetId,
+ @NonNull ComponentName componentName, @NonNull UserHandle userHandle) {
+ this.mSmartspaceTargetId = smartspaceTargetId;
+ this.mComponentName = componentName;
+ this.mUserHandle = userHandle;
+ }
+
+ /**
+ * Sets the header action.
+ */
+ @NonNull
+ public Builder setHeaderAction(@NonNull SmartspaceAction headerAction) {
+ this.mHeaderAction = headerAction;
+ return this;
+ }
+
+ /**
+ * Sets the base action.
+ */
+ @NonNull
+ public Builder setBaseAction(@NonNull SmartspaceAction baseAction) {
+ this.mBaseAction = baseAction;
+ return this;
+ }
+
+ /**
+ * Sets the creation time.
+ */
+ @NonNull
+ public Builder setCreationTimeMillis(@NonNull long creationTimeMillis) {
+ this.mCreationTimeMillis = creationTimeMillis;
+ return this;
+ }
+
+ /**
+ * Sets the expiration time.
+ */
+ @NonNull
+ public Builder setExpiryTimeMillis(@NonNull long expiryTimeMillis) {
+ this.mExpiryTimeMillis = expiryTimeMillis;
+ return this;
+ }
+
+ /**
+ * Sets the score.
+ */
+ @NonNull
+ public Builder setScore(@NonNull float score) {
+ this.mScore = score;
+ return this;
+ }
+
+ /**
+ * Sets the action chips.
+ */
+ @NonNull
+ public Builder setActionChips(@NonNull List<SmartspaceAction> actionChips) {
+ this.mActionChips = actionChips;
+ return this;
+ }
+
+ /**
+ * Sets the icon grid.
+ */
+ @NonNull
+ public Builder setIconGrid(@NonNull List<SmartspaceAction> iconGrid) {
+ this.mIconGrid = iconGrid;
+ return this;
+ }
+
+ /**
+ * Sets the feature type.
+ */
+ @NonNull
+ public Builder setFeatureType(@NonNull int featureType) {
+ this.mFeatureType = featureType;
+ return this;
+ }
+
+ /**
+ * Sets whether the contents are sensitive.
+ */
+ @NonNull
+ public Builder setSensitive(@NonNull boolean sensitive) {
+ this.mSensitive = sensitive;
+ return this;
+ }
+
+ /**
+ * Sets whether to show the card as expanded.
+ */
+ @NonNull
+ public Builder setShouldShowExpanded(@NonNull boolean shouldShowExpanded) {
+ this.mShouldShowExpanded = shouldShowExpanded;
+ return this;
+ }
+
+ /**
+ * Sets the source notification key.
+ */
+ @NonNull
+ public Builder setSourceNotificationKey(@NonNull String sourceNotificationKey) {
+ this.mSourceNotificationKey = sourceNotificationKey;
+ return this;
+ }
+
+ /**
+ * Sets the associated smartspace target id.
+ */
+ @NonNull
+ public Builder setAssociatedSmartspaceTargetId(
+ @NonNull String associatedSmartspaceTargetId) {
+ this.mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+ return this;
+ }
+
+ /**
+ * Sets the slice uri.
+ *
+ * <p><b>NOTE: </b> If {@link mWidgetId} is also set, {@link mSliceUri} should be ignored.
+ */
+ @NonNull
+ public Builder setSliceUri(@NonNull Uri sliceUri) {
+ this.mSliceUri = sliceUri;
+ return this;
+ }
+
+ /**
+ * Sets the widget id.
+ *
+ * <p><b>NOTE: </b> If {@link mWidgetId} is set, all other @Nullable params should be
+ * ignored.
+ */
+ @NonNull
+ public Builder setWidgetId(@NonNull AppWidgetProviderInfo widgetId) {
+ this.mWidgetId = widgetId;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link SmartspaceTarget}.
+ *
+ * @throws IllegalStateException when non null fields are set as null.
+ */
+ @NonNull
+ public SmartspaceTarget build() {
+ if (mSmartspaceTargetId == null
+ || mComponentName == null
+ || mUserHandle == null) {
+ throw new IllegalStateException("Please assign a value to all @NonNull args.");
+ }
+ return new SmartspaceTarget(mSmartspaceTargetId,
+ mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
+ mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
+ mSourceNotificationKey, mComponentName, mUserHandle,
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
new file mode 100644
index 0000000..e797a9b
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.app.smartspace;
+
+parcelable SmartspaceTargetEvent;
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
new file mode 100644
index 0000000..1e0653d
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 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.app.smartspace;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A representation of a smartspace event.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTargetEvent implements Parcelable {
+
+ /**
+ * User interacted with the target.
+ */
+ public static final int EVENT_TARGET_INTERACTION = 1;
+
+ /**
+ * Smartspace target was brought into view.
+ */
+ public static final int EVENT_TARGET_IN_VIEW = 2;
+ /**
+ * Smartspace target went out of view.
+ */
+ public static final int EVENT_TARGET_OUT_OF_VIEW = 3;
+ /**
+ * A dismiss action was issued by the user.
+ */
+ public static final int EVENT_TARGET_DISMISS = 4;
+ /**
+ * A block action was issued by the user.
+ */
+ public static final int EVENT_TARGET_BLOCK = 5;
+ /**
+ * The Ui surface came into view.
+ */
+ public static final int EVENT_UI_SURFACE_IN_VIEW = 6;
+ /**
+ * The Ui surface went out of view.
+ */
+ public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7;
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceTargetEvent> CREATOR =
+ new Creator<SmartspaceTargetEvent>() {
+ public SmartspaceTargetEvent createFromParcel(Parcel parcel) {
+ return new SmartspaceTargetEvent(parcel);
+ }
+
+ public SmartspaceTargetEvent[] newArray(int size) {
+ return new SmartspaceTargetEvent[size];
+ }
+ };
+
+ @Nullable
+ private final SmartspaceTarget mSmartspaceTarget;
+
+ @Nullable
+ private final String mSmartspaceActionId;
+
+ @EventType
+ private final int mEventType;
+
+ private SmartspaceTargetEvent(@Nullable SmartspaceTarget smartspaceTarget,
+ @Nullable String smartspaceActionId,
+ @EventType int eventType) {
+ mSmartspaceTarget = smartspaceTarget;
+ mSmartspaceActionId = smartspaceActionId;
+ mEventType = eventType;
+ }
+
+ private SmartspaceTargetEvent(Parcel parcel) {
+ mSmartspaceTarget = parcel.readParcelable(null);
+ mSmartspaceActionId = parcel.readString();
+ mEventType = parcel.readInt();
+ }
+
+ /**
+ * Get the {@link SmartspaceTarget} associated with this event.
+ */
+ @Nullable
+ public SmartspaceTarget getSmartspaceTarget() {
+ return mSmartspaceTarget;
+ }
+
+ /**
+ * Get the action id of the Smartspace Action associated with this event.
+ */
+ @Nullable
+ public String getSmartspaceActionId() {
+ return mSmartspaceActionId;
+ }
+
+ /**
+ * Get the {@link EventType} of this event.
+ */
+ @NonNull
+ @EventType
+ public int getEventType() {
+ return mEventType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mSmartspaceTarget, flags);
+ dest.writeString(mSmartspaceActionId);
+ dest.writeInt(mEventType);
+ }
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"EVENT_"}, value = {
+ EVENT_TARGET_INTERACTION,
+ EVENT_TARGET_IN_VIEW,
+ EVENT_TARGET_OUT_OF_VIEW,
+ EVENT_TARGET_DISMISS,
+ EVENT_TARGET_BLOCK,
+ EVENT_UI_SURFACE_IN_VIEW,
+ EVENT_UI_SURFACE_OUT_OF_VIEW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {
+ }
+
+ /**
+ * A builder for {@link SmartspaceTargetEvent}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @EventType
+ private final int mEventType;
+ @Nullable
+ private SmartspaceTarget mSmartspaceTarget;
+ @Nullable
+ private String mSmartspaceActionId;
+
+ /**
+ * A builder for {@link SmartspaceTargetEvent}.
+ */
+ public Builder(@EventType int eventType) {
+ mEventType = eventType;
+ }
+
+ /**
+ * Sets the SmartspaceTarget for this event.
+ */
+ @NonNull
+ public Builder setSmartspaceTarget(@NonNull SmartspaceTarget smartspaceTarget) {
+ mSmartspaceTarget = smartspaceTarget;
+ return this;
+ }
+
+ /**
+ * Sets the Smartspace action id for this event.
+ */
+ @NonNull
+ public Builder setSmartspaceActionId(@NonNull String smartspaceActionId) {
+ mSmartspaceActionId = smartspaceActionId;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link SmartspaceTargetEvent} instance.
+ */
+ @NonNull
+ public SmartspaceTargetEvent build() {
+ return new SmartspaceTargetEvent(mSmartspaceTarget, mSmartspaceActionId, mEventType);
+ }
+ }
+}
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 93d96d0..e96e22c4 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -406,6 +406,14 @@
return new UserHandle(UserHandle.getUserId(providerInfo.applicationInfo.uid));
}
+ /**
+ * Returns the broadcast receiver that is providing this widget.
+ */
+ @NonNull
+ public ActivityInfo getProviderInfo() {
+ return providerInfo;
+ }
+
@Override
@SuppressWarnings("deprecation")
public void writeToParcel(Parcel out, int flags) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 125b5ff..4dc41b2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@
import android.annotation.UiContext;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.GameManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.VrManager;
@@ -4625,6 +4626,18 @@
public static final String SEARCH_UI_SERVICE = "search_ui";
/**
+ * Used for getting the smartspace service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(SMARTSPACE_SERVICE)} should check for {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String SMARTSPACE_SERVICE = "smartspace";
+
+ /**
* Use with {@link #getSystemService(String)} to access the
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
*
@@ -5429,7 +5442,7 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
- * {@link android.graphics.GameManager}.
+ * {@link GameManager}.
*
* @see #getSystemService(String)
*
@@ -6154,6 +6167,7 @@
@UiContext
@NonNull
public Context createWindowContext(@NonNull Display display, @WindowType int type,
+ @SuppressLint("NullableCollection")
@Nullable Bundle options) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 82d7b63..16e720e 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -17,6 +17,7 @@
package android.content.pm;
import android.annotation.FloatRange;
+import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -32,8 +33,6 @@
* and badged icon for the activity.
*/
public class LauncherActivityInfo {
- private static final String TAG = "LauncherActivityInfo";
-
private final PackageManager mPm;
private UserHandle mUser;
private final LauncherActivityInfoInternal mInternal;
@@ -81,7 +80,7 @@
*/
public CharSequence getLabel() {
// TODO: Go through LauncherAppsService
- return mInternal.getActivityInfo().loadLabel(mPm);
+ return getActivityInfo().loadLabel(mPm);
}
/**
@@ -101,20 +100,20 @@
*/
public Drawable getIcon(int density) {
// TODO: Go through LauncherAppsService
- final int iconRes = mInternal.getActivityInfo().getIconResource();
+ final int iconRes = getActivityInfo().getIconResource();
Drawable icon = null;
// Get the preferred density icon from the app's resources
if (density != 0 && iconRes != 0) {
try {
final Resources resources = mPm.getResourcesForApplication(
- mInternal.getActivityInfo().applicationInfo);
+ getActivityInfo().applicationInfo);
icon = resources.getDrawableForDensity(iconRes, density);
} catch (NameNotFoundException | Resources.NotFoundException exc) {
}
}
// Get the default density icon
if (icon == null) {
- icon = mInternal.getActivityInfo().loadIcon(mPm);
+ icon = getActivityInfo().loadIcon(mPm);
}
return icon;
}
@@ -126,25 +125,25 @@
* @hide remove before shipping
*/
public int getApplicationFlags() {
- return mInternal.getActivityInfo().flags;
+ return getActivityInfo().flags;
}
/**
* Returns the ActivityInfo of the activity.
*
* @return Activity Info
- * @hide
*/
+ @NonNull
public ActivityInfo getActivityInfo() {
return mInternal.getActivityInfo();
}
/**
- * Returns the application info for the appliction this activity belongs to.
+ * Returns the application info for the application this activity belongs to.
* @return
*/
public ApplicationInfo getApplicationInfo() {
- return mInternal.getActivityInfo().applicationInfo;
+ return getActivityInfo().applicationInfo;
}
/**
@@ -155,7 +154,7 @@
public long getFirstInstallTime() {
try {
// TODO: Go through LauncherAppsService
- return mPm.getPackageInfo(mInternal.getActivityInfo().packageName,
+ return mPm.getPackageInfo(getActivityInfo().packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES).firstInstallTime;
} catch (NameNotFoundException nnfe) {
// Sorry, can't find package
@@ -164,11 +163,11 @@
}
/**
- * Returns the name for the acitivty from android:name in the manifest.
- * @return the name from android:name for the acitivity.
+ * Returns the name for the activity from android:name in the manifest.
+ * @return the name from android:name for the activity.
*/
public String getName() {
- return mInternal.getActivityInfo().name;
+ return getActivityInfo().name;
}
/**
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index f991306..e8ef077 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -222,7 +222,7 @@
* <attribution>} tags included under <manifest>, or null if there were none. This
* is only filled if the flag {@link PackageManager#GET_ATTRIBUTIONS} was set.
*/
- @SuppressWarnings("ArrayReturn")
+ @SuppressWarnings({"ArrayReturn", "NullableCollection"})
public @Nullable Attribution[] attributions;
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9ae9c25..72fb1ca 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -54,6 +54,7 @@
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
@@ -124,6 +125,19 @@
}
/**
+ * <application> level {@link android.content.pm.PackageManager.Property} tag specifying
+ * the XML resource ID containing an application's media capabilities XML file
+ *
+ * For example:
+ * <application>
+ * <property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES"
+ * android:resource="@xml/media_capabilities">
+ * <application>
+ */
+ public static final String PROPERTY_MEDIA_CAPABILITIES =
+ "android.media.PROPERTY_MEDIA_CAPABILITIES";
+
+ /**
* A property value set within the manifest.
* <p>
* The value of a property will only have a single type, as defined by
@@ -6703,6 +6717,22 @@
throws NameNotFoundException;
/**
+ * Retrieve the resources for an application for the provided configuration.
+ *
+ * @param app Information about the desired application.
+ * @param configuration Overridden configuration when loading the Resources
+ *
+ * @return Returns the application's Resources.
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded (most likely because it was uninstalled).
+ */
+ @NonNull
+ public Resources getResourcesForApplication(@NonNull ApplicationInfo app, @Nullable
+ Configuration configuration) throws NameNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Retrieve the resources associated with an application. Given the full
* package name of an application, retrieves the information about it and
* calls getResources() to return its application's resources. If the
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 02fb06b..bce4b87 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -256,6 +256,8 @@
try {
if (obj != null) {
Signature other = (Signature)obj;
+ // Note, some classes, such as PackageParser.SigningDetails, rely on equals
+ // only comparing the mSignature arrays without the flags.
return this == other || Arrays.equals(mSignature, other.mSignature);
}
} catch (ClassCastException e) {
@@ -268,6 +270,8 @@
if (mHaveHashCode) {
return mHashCode;
}
+ // Note, similar to equals some classes rely on the hash code not including
+ // the flags for Set membership checks.
mHashCode = Arrays.hashCode(mSignature);
mHaveHashCode = true;
return mHashCode;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index f6edb2e..abf694f 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -89,6 +89,11 @@
private static final int NEEDS_COMPAT_RES = 16;
/**
+ * Set if the application needs to be forcibly downscaled
+ */
+ private static final int HAS_OVERRIDE_SCALING = 32;
+
+ /**
* The effective screen density we have selected for this application.
*/
public final int applicationDensity;
@@ -107,6 +112,11 @@
@UnsupportedAppUsage
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
boolean forceCompat) {
+ this(appInfo, screenLayout, sw, forceCompat, 1f);
+ }
+
+ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+ boolean forceCompat, float overrideScale) {
int compatFlags = 0;
if (appInfo.targetSdkVersion < VERSION_CODES.O) {
@@ -241,7 +251,12 @@
compatFlags |= NEVER_NEEDS_COMPAT;
}
- if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+ if (overrideScale != 1.0f) {
+ applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
+ applicationScale = overrideScale;
+ applicationInvertedScale = 1.0f / overrideScale;
+ compatFlags |= HAS_OVERRIDE_SCALING;
+ } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
applicationScale = 1.0f;
applicationInvertedScale = 1.0f;
@@ -277,7 +292,7 @@
*/
@UnsupportedAppUsage
public boolean isScalingRequired() {
- return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
+ return (mCompatibilityFlags & (SCALING_REQUIRED | HAS_OVERRIDE_SCALING)) != 0;
}
@UnsupportedAppUsage
@@ -303,7 +318,7 @@
*/
@UnsupportedAppUsage
public Translator getTranslator() {
- return isScalingRequired() ? new Translator() : null;
+ return (mCompatibilityFlags & SCALING_REQUIRED) != 0 ? new Translator() : null;
}
/**
@@ -504,6 +519,16 @@
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
+ inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
+ inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
+ inoutConfig.smallestScreenWidthDp =
+ (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
+ inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
+ inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
+ final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
+ if (appBounds != null) {
+ appBounds.scale(invertedRatio);
+ }
}
}
diff --git a/core/java/android/hardware/Battery.java b/core/java/android/hardware/Battery.java
new file mode 100644
index 0000000..24c8d76
--- /dev/null
+++ b/core/java/android/hardware/Battery.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.hardware;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.os.BatteryManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The Battery class is a representation of a single battery on a device.
+ */
+public abstract class Battery {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_UNKNOWN,
+ STATUS_CHARGING,
+ STATUS_DISCHARGING,
+ STATUS_NOT_CHARGING,
+ STATUS_FULL
+ })
+ public @interface BatteryStatus {
+ }
+
+ /** Battery status is unknown. */
+ public static final int STATUS_UNKNOWN = BatteryManager.BATTERY_STATUS_UNKNOWN;
+ /** Battery is charging. */
+ public static final int STATUS_CHARGING = BatteryManager.BATTERY_STATUS_CHARGING;
+ /** Battery is discharging. */
+ public static final int STATUS_DISCHARGING = BatteryManager.BATTERY_STATUS_DISCHARGING;
+ /** Battery is connected to power but not charging. */
+ public static final int STATUS_NOT_CHARGING = BatteryManager.BATTERY_STATUS_NOT_CHARGING;
+ /** Battery is full. */
+ public static final int STATUS_FULL = BatteryManager.BATTERY_STATUS_FULL;
+
+ /**
+ * Check whether the hardware has a battery.
+ *
+ * @return True if the hardware has a battery, else false.
+ */
+ public abstract boolean hasBattery();
+
+ /**
+ * Get the battery status.
+ *
+ * @return the battery status.
+ */
+ public abstract @BatteryStatus int getStatus();
+
+ /**
+ * Get remaining battery capacity as float percentage [0.0f, 1.0f] of total capacity
+ * Returns -1 when battery capacity can't be read.
+ *
+ * @return the battery capacity.
+ */
+ public abstract @FloatRange(from = -1.0f, to = 1.0f) float getCapacity();
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index b39df4d..c69c47f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -93,6 +93,10 @@
int[] getVibratorIds(int deviceId);
boolean isVibrating(int deviceId);
+ // Input device battery query.
+ int getBatteryStatus(int deviceId);
+ int getBatteryCapacity(int deviceId);
+
void setPointerIconType(int typeId);
void setCustomPointerIcon(in PointerIcon icon);
diff --git a/core/java/android/hardware/input/InputDeviceBattery.java b/core/java/android/hardware/input/InputDeviceBattery.java
new file mode 100644
index 0000000..0fe124e
--- /dev/null
+++ b/core/java/android/hardware/input/InputDeviceBattery.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.hardware.input;
+
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.IInputConstants.INVALID_BATTERY_CAPACITY;
+
+import android.hardware.Battery;
+
+/**
+ * Battery implementation for input devices.
+ *
+ * @hide
+ */
+public final class InputDeviceBattery extends Battery {
+ private static final float NULL_BATTERY_CAPACITY = -1.0f;
+
+ private final InputManager mInputManager;
+ private final int mDeviceId;
+ private final boolean mHasBattery;
+
+ InputDeviceBattery(InputManager inputManager, int deviceId, boolean hasBattery) {
+ mInputManager = inputManager;
+ mDeviceId = deviceId;
+ mHasBattery = hasBattery;
+ }
+
+ @Override
+ public boolean hasBattery() {
+ return mHasBattery;
+ }
+
+ @Override
+ public int getStatus() {
+ if (!mHasBattery) {
+ return BATTERY_STATUS_UNKNOWN;
+ }
+ return mInputManager.getBatteryStatus(mDeviceId);
+ }
+
+ @Override
+ public float getCapacity() {
+ if (mHasBattery) {
+ int capacity = mInputManager.getBatteryCapacity(mDeviceId);
+ if (capacity != INVALID_BATTERY_CAPACITY) {
+ return (float) capacity / 100.0f;
+ }
+ }
+ return NULL_BATTERY_CAPACITY;
+ }
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index e63dc11..185c59d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1245,6 +1245,32 @@
}
/**
+ * Get the battery status of the input device
+ * @param deviceId The input device ID
+ * @hide
+ */
+ public int getBatteryStatus(int deviceId) {
+ try {
+ return mIm.getBatteryStatus(deviceId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the remaining battery capacity of the input device
+ * @param deviceId The input device ID
+ * @hide
+ */
+ public int getBatteryCapacity(int deviceId) {
+ try {
+ return mIm.getBatteryCapacity(deviceId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Add a runtime association between the input port and the display port. This overrides any
* static associations.
* @param inputPort The port of the input device.
@@ -1471,6 +1497,15 @@
}
/**
+ * Gets a battery object associated with an input device, assuming it has one.
+ * @return The battery, never null.
+ * @hide
+ */
+ public InputDeviceBattery getInputDeviceBattery(int deviceId, boolean hasBattery) {
+ return new InputDeviceBattery(this, deviceId, hasBattery);
+ }
+
+ /**
* Listens for changes in input devices.
*/
public interface InputDeviceListener {
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index 20fa753..b31b85f 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import java.util.concurrent.Executor;
@@ -101,4 +102,34 @@
* @param nanoAppId the ID of the nanoapp that had been disabled
*/
public void onNanoAppDisabled(ContextHubClient client, long nanoAppId) {}
+
+ /**
+ * Callback invoked when a {@link ContextHubClient}'s authorization to communicate with a
+ * nanoapp changes. This typically happens as a result of the app that created the
+ * {@link ContextHubClient} gaining or losing the permissions required to communicate with a
+ * nanoapp.
+ *
+ * An example of the connection callbacks looks like:
+ * 1) {@link ContextHubClient} sends message to nanoapp and holds required permissions
+ * 2) {@link ContextHubClient} loses required permissions
+ * 3) Callback invoked with the nanoapp ID and
+ * {@link ContextHubManager#AUTHORIZATION_DENIED_GRACE_PERIOD}
+ * 4) {@link ContextHubClient} performs any cleanup required with the nanoapp
+ * 5) Callback invoked with the nanoapp ID and {@link ContextHubManager#AUTHORIZATION_DENIED}.
+ * At this point, any further attempts of communication between the nanoapp and the
+ * {@link ContextHubClient} will be dropped by the contexthub along with
+ * {@link ContextHubManager#AUTHORIZATION_DENIED} being sent. The {@link ContextHubClient}
+ * should assume no communciation can happen again until
+ * {@link ContextHubManager#AUTHORIZATION_GRANTED} is received.
+ *
+ * @param client the client that is associated with this callback
+ * @param nanoAppId the ID of the nanoapp associated with the new
+ * authorization state
+ * @param authorization the authorization state denoting the ability of the
+ * client to communicate with the nanoapp
+ */
+ public void onClientAuthorizationChanged(
+ @NonNull ContextHubClient client,
+ long nanoAppId,
+ @ContextHubManager.AuthorizationState int authorization) {}
}
diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java
index 917f62b..3e8f421 100644
--- a/core/java/android/hardware/location/ContextHubIntentEvent.java
+++ b/core/java/android/hardware/location/ContextHubIntentEvent.java
@@ -43,39 +43,45 @@
private final int mNanoAppAbortCode;
+ private final int mClientAuthorizationState;
+
private ContextHubIntentEvent(
@NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
- long nanoAppId, NanoAppMessage nanoAppMessage, int nanoAppAbortCode) {
+ long nanoAppId, NanoAppMessage nanoAppMessage, int nanoAppAbortCode,
+ @ContextHubManager.AuthorizationState int clientAuthorizationState) {
mContextHubInfo = contextHubInfo;
mEventType = eventType;
mNanoAppId = nanoAppId;
mNanoAppMessage = nanoAppMessage;
mNanoAppAbortCode = nanoAppAbortCode;
+ mClientAuthorizationState = clientAuthorizationState;
}
private ContextHubIntentEvent(
@NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType) {
this(contextHubInfo, eventType, -1 /* nanoAppId */, null /* nanoAppMessage */,
- -1 /* nanoAppAbortCode */);
+ -1 /* nanoAppAbortCode */, 0 /* clientAuthorizationState */);
}
private ContextHubIntentEvent(
@NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
long nanoAppId) {
this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */,
- -1 /* nanoAppAbortCode */);
+ -1 /* nanoAppAbortCode */, 0 /* clientAuthorizationState */);
}
private ContextHubIntentEvent(
@NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
long nanoAppId, @NonNull NanoAppMessage nanoAppMessage) {
- this(contextHubInfo, eventType, nanoAppId, nanoAppMessage, -1 /* nanoAppAbortCode */);
+ this(contextHubInfo, eventType, nanoAppId, nanoAppMessage, -1 /* nanoAppAbortCode */,
+ 0 /* clientAuthorizationState */);
}
private ContextHubIntentEvent(
@NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
long nanoAppId, int nanoAppAbortCode) {
- this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, nanoAppAbortCode);
+ this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, nanoAppAbortCode,
+ 0 /* clientAuthorizationState */);
}
/**
@@ -105,7 +111,8 @@
case ContextHubManager.EVENT_NANOAPP_ENABLED:
case ContextHubManager.EVENT_NANOAPP_DISABLED:
case ContextHubManager.EVENT_NANOAPP_ABORTED:
- case ContextHubManager.EVENT_NANOAPP_MESSAGE: // fall through
+ case ContextHubManager.EVENT_NANOAPP_MESSAGE:
+ case ContextHubManager.EVENT_CLIENT_AUTHORIZATION: // fall through
long nanoAppId = getLongExtraOrThrow(intent, ContextHubManager.EXTRA_NANOAPP_ID);
if (eventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
hasExtraOrThrow(intent, ContextHubManager.EXTRA_MESSAGE);
@@ -120,6 +127,11 @@
int nanoAppAbortCode = getIntExtraOrThrow(
intent, ContextHubManager.EXTRA_NANOAPP_ABORT_CODE);
event = new ContextHubIntentEvent(info, eventType, nanoAppId, nanoAppAbortCode);
+ } else if (eventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+ int authState = getIntExtraOrThrow(
+ intent, ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE);
+ event = new ContextHubIntentEvent(info, eventType, nanoAppId,
+ null /* nanoAppMessage */, -1 /* nanoAppAbortCode */, authState);
} else {
event = new ContextHubIntentEvent(info, eventType, nanoAppId);
}
@@ -192,6 +204,21 @@
return mNanoAppMessage;
}
+ /**
+ * @return the client authorization state
+ *
+ * @throws UnsupportedOperationException if this was not a client authorization state event
+ */
+ @ContextHubManager.AuthorizationState
+ public int getClientAuthorizationState() {
+ if (mEventType != ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+ throw new UnsupportedOperationException(
+ "Cannot invoke getClientAuthorizationState() on non-authorization event: "
+ + mEventType);
+ }
+ return mClientAuthorizationState;
+ }
+
@NonNull
@Override
public String toString() {
@@ -207,6 +234,9 @@
if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
out += ", nanoAppMessage = " + mNanoAppMessage;
}
+ if (mEventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+ out += ", clientAuthState = " + mClientAuthorizationState;
+ }
return out + "]";
}
@@ -233,6 +263,9 @@
if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
isEqual &= other.getNanoAppMessage().equals(mNanoAppMessage);
}
+ if (mEventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+ isEqual &= other.getClientAuthorizationState() == mClientAuthorizationState;
+ }
} catch (UnsupportedOperationException e) {
isEqual = false;
}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index d444807..ebb3021 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -59,6 +59,12 @@
private static final String TAG = "ContextHubManager";
/**
+ * An extra of type int describing the client's authorization state.
+ */
+ public static final String EXTRA_CLIENT_AUTHORIZATION_STATE =
+ "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE";
+
+ /**
* An extra of type {@link ContextHubInfo} describing the source of the event.
*/
public static final String EXTRA_CONTEXT_HUB_INFO =
@@ -86,6 +92,42 @@
public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE";
/**
+ * Constants describing if a {@link ContextHubClient} and a {@link NanoApp} are authorized to
+ * communicate.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "AUTHORIZATION_" }, value = {
+ AUTHORIZATION_DENIED,
+ AUTHORIZATION_DENIED_GRACE_PERIOD,
+ AUTHORIZATION_GRANTED,
+ })
+ public @interface AuthorizationState { }
+
+ /**
+ * Indicates that the {@link ContextHubClient} can no longer communicate with a nanoapp. If the
+ * {@link ContextHubClient} attempts to send messages to the nanoapp, it will continue to
+ * receive this authorization state if the connection is still closed.
+ */
+ public static final int AUTHORIZATION_DENIED = 0;
+
+ /**
+ * Indicates the {@link ContextHubClient} will soon lose its authorization to communicate with a
+ * nanoapp. The {@link ContextHubClient} must perform any cleanup with the nanoapp as soon as
+ * possible.
+ *
+ * Note that the time between this state event and {@link AUTHORIZATION_DENIED} must be enough
+ * for the {@link ContextHubClient} to send at least one message to the nanoapp.
+ */
+ public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1;
+
+ /**
+ * The {@link ContextHubClient} is authorized to communicate with the nanoapp.
+ */
+ public static final int AUTHORIZATION_GRANTED = 2;
+
+ /**
* Constants describing the type of events from a Context Hub.
* {@hide}
*/
@@ -98,6 +140,7 @@
EVENT_NANOAPP_ABORTED,
EVENT_NANOAPP_MESSAGE,
EVENT_HUB_RESET,
+ EVENT_CLIENT_AUTHORIZATION,
})
public @interface Event { }
@@ -138,6 +181,14 @@
*/
public static final int EVENT_HUB_RESET = 6;
+ /**
+ * An event describing a client authorization state change. See
+ * {@link ContextHubClientCallback#onClientAuthorizationChanged} for more details on when this
+ * event will be sent. Contains the EXTRA_NANOAPP_ID and EXTRA_CLIENT_AUTHORIZATION_STATE
+ * extras.
+ */
+ public static final int EVENT_CLIENT_AUTHORIZATION = 7;
+
private final Looper mMainLooper;
private final IContextHubService mService;
private Callback mCallback;
@@ -747,6 +798,14 @@
public void onNanoAppDisabled(long nanoAppId) {
executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId));
}
+
+ @Override
+ public void onClientAuthorizationChanged(
+ long nanoAppId, @ContextHubManager.AuthorizationState int authorization) {
+ executor.execute(
+ () -> callback.onClientAuthorizationChanged(
+ client, nanoAppId, authorization));
+ }
};
}
@@ -757,9 +816,10 @@
* registration succeeds, the client can send messages to nanoapps through the returned
* {@link ContextHubClient} object, and receive notifications through the provided callback.
*
+ * @param context the context of the application
* @param hubInfo the hub to attach this client to
- * @param callback the notification callback to register
* @param executor the executor to invoke the callback
+ * @param callback the notification callback to register
* @return the registered client object
*
* @throws IllegalArgumentException if hubInfo does not represent a valid hub
@@ -773,8 +833,9 @@
android.Manifest.permission.ACCESS_CONTEXT_HUB
})
@NonNull public ContextHubClient createClient(
- @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
- @NonNull @CallbackExecutor Executor executor) {
+ @Nullable Context context, @NonNull ContextHubInfo hubInfo,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ContextHubClientCallback callback) {
Objects.requireNonNull(callback, "Callback cannot be null");
Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
Objects.requireNonNull(executor, "Executor cannot be null");
@@ -783,9 +844,14 @@
IContextHubClientCallback clientInterface = createClientCallback(
client, callback, executor);
+ String attributionTag = null;
+ if (context != null) {
+ attributionTag = context.getAttributionTag();
+ }
+
IContextHubClient clientProxy;
try {
- clientProxy = mService.createClient(hubInfo.getId(), clientInterface);
+ clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -794,19 +860,25 @@
return client;
}
+
+ /**
+ * Equivalent to
+ * {@link #createClient(ContextHubInfo, Executor, String, ContextHubClientCallback)}
+ * with the {@link Context} being set to null.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.LOCATION_HARDWARE,
+ android.Manifest.permission.ACCESS_CONTEXT_HUB
+ })
+ @NonNull public ContextHubClient createClient(
+ @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
+ @NonNull @CallbackExecutor Executor executor) {
+ return createClient(null /* context */, hubInfo, executor, callback);
+ }
+
/**
* Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
* with the executor using the main thread's Looper.
- *
- * @param hubInfo the hub to attach this client to
- * @param callback the notification callback to register
- * @return the registered client object
- *
- * @throws IllegalArgumentException if hubInfo does not represent a valid hub
- * @throws IllegalStateException if there were too many registered clients at the service
- * @throws NullPointerException if callback or hubInfo is null
- *
- * @see ContextHubClientCallback
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.LOCATION_HARDWARE,
@@ -814,7 +886,8 @@
})
@NonNull public ContextHubClient createClient(
@NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) {
- return createClient(hubInfo, callback, new HandlerExecutor(Handler.getMain()));
+ return createClient(null /* context */, hubInfo, new HandlerExecutor(Handler.getMain()),
+ callback);
}
/**
@@ -848,6 +921,8 @@
* on the provided PendingIntent, then the client will be automatically unregistered by the
* service.
*
+ * @param context the context of the application. If a PendingIntent client is recreated,
+ * the latest state in the context will be used and old state will be discarded
* @param hubInfo the hub to attach this client to
* @param pendingIntent the PendingIntent to register to the client
* @param nanoAppId the ID of the nanoapp that Intent events will be generated for
@@ -862,16 +937,22 @@
android.Manifest.permission.ACCESS_CONTEXT_HUB
})
@NonNull public ContextHubClient createClient(
- @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+ @Nullable Context context, @NonNull ContextHubInfo hubInfo,
+ @NonNull PendingIntent pendingIntent, long nanoAppId) {
Objects.requireNonNull(pendingIntent);
Objects.requireNonNull(hubInfo);
ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);
+ String attributionTag = null;
+ if (context != null) {
+ attributionTag = context.getAttributionTag();
+ }
+
IContextHubClient clientProxy;
try {
clientProxy = mService.createPendingIntentClient(
- hubInfo.getId(), pendingIntent, nanoAppId);
+ hubInfo.getId(), pendingIntent, nanoAppId, attributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -881,6 +962,19 @@
}
/**
+ * Equivalent to {@link #createClient(ContextHubInfo, PendingIntent, long, String)}
+ * with {@link Context} being set to null.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.LOCATION_HARDWARE,
+ android.Manifest.permission.ACCESS_CONTEXT_HUB
+ })
+ @NonNull public ContextHubClient createClient(
+ @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+ return createClient(null /* context */, hubInfo, pendingIntent, nanoAppId);
+ }
+
+ /**
* Unregister a callback for receive messages from the context hub.
*
* @see Callback
diff --git a/core/java/android/hardware/location/IContextHubClientCallback.aidl b/core/java/android/hardware/location/IContextHubClientCallback.aidl
index 1c76bcb..bcd6b08 100644
--- a/core/java/android/hardware/location/IContextHubClientCallback.aidl
+++ b/core/java/android/hardware/location/IContextHubClientCallback.aidl
@@ -46,4 +46,7 @@
// Callback invoked when a nanoapp is disabled at the attached Context Hub.
void onNanoAppDisabled(long nanoAppId);
+
+ // Callback invoked when the authorization state of a client changes.
+ void onClientAuthorizationChanged(long nanoAppId, int authorization);
}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 04cc563..4961195 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -59,11 +59,13 @@
int sendMessage(int contextHubHandle, int nanoAppHandle, in ContextHubMessage msg);
// Creates a client to send and receive messages
- IContextHubClient createClient(int contextHubId, in IContextHubClientCallback client);
+ IContextHubClient createClient(
+ int contextHubId, in IContextHubClientCallback client, in String attributionTag);
// Creates a PendingIntent-based client to send and receive messages
IContextHubClient createPendingIntentClient(
- int contextHubId, in PendingIntent pendingIntent, long nanoAppId);
+ int contextHubId, in PendingIntent pendingIntent, long nanoAppId,
+ in String attributionTag);
// Returns a list of ContextHub objects of available hubs
List<ContextHubInfo> getContextHubs();
diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java
index 8de7ecd..96b1f19 100644
--- a/core/java/android/hardware/location/NanoAppState.java
+++ b/core/java/android/hardware/location/NanoAppState.java
@@ -15,10 +15,14 @@
*/
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A class describing the nanoapp state information resulting from a query to a Context Hub.
*
@@ -29,11 +33,21 @@
private long mNanoAppId;
private int mNanoAppVersion;
private boolean mIsEnabled;
+ private List<String> mNanoAppPermissions;
public NanoAppState(long nanoAppId, int appVersion, boolean enabled) {
mNanoAppId = nanoAppId;
mNanoAppVersion = appVersion;
mIsEnabled = enabled;
+ mNanoAppPermissions = new ArrayList<String>();
+ }
+
+ public NanoAppState(long nanoAppId, int appVersion, boolean enabled,
+ @NonNull List<String> nanoAppPermissions) {
+ mNanoAppId = nanoAppId;
+ mNanoAppVersion = appVersion;
+ mIsEnabled = enabled;
+ mNanoAppPermissions = nanoAppPermissions;
}
/**
@@ -57,10 +71,19 @@
return mIsEnabled;
}
+ /**
+ * @return List of Android permissions that are required to communicate with this app.
+ */
+ public @NonNull List<String> getNanoAppPermissions() {
+ return mNanoAppPermissions;
+ }
+
private NanoAppState(Parcel in) {
mNanoAppId = in.readLong();
mNanoAppVersion = in.readInt();
mIsEnabled = (in.readInt() == 1);
+ mNanoAppPermissions = new ArrayList<String>();
+ in.readStringList(mNanoAppPermissions);
}
@Override
@@ -73,6 +96,7 @@
out.writeLong(mNanoAppId);
out.writeInt(mNanoAppVersion);
out.writeInt(mIsEnabled ? 1 : 0);
+ out.writeStringList(mNanoAppPermissions);
}
public static final @android.annotation.NonNull Creator<NanoAppState> CREATOR =
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 250cff2..a22d41a 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -26,7 +26,7 @@
import android.widget.TextView;
import com.android.internal.util.HexDump;
-import com.android.org.bouncycastle.asn1.x509.X509Name;
+import com.android.internal.org.bouncycastle.asn1.x509.X509Name;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 33beb6a..fa090f5 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -67,7 +67,6 @@
public class VcnManager {
@NonNull private static final String TAG = VcnManager.class.getSimpleName();
- /** @hide */
@VisibleForTesting
public static final Map<
VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index 869a727..cb4e9cb 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -364,8 +364,22 @@
@Override
public long getDuration() {
long maxDuration = Long.MIN_VALUE;
+ boolean hasUnknownStep = false;
for (int i = 0; i < mEffects.size(); i++) {
- maxDuration = Math.max(maxDuration, mEffects.valueAt(i).getDuration());
+ long duration = mEffects.valueAt(i).getDuration();
+ if (duration == Long.MAX_VALUE) {
+ // If any duration is repeating, this combination duration is also repeating.
+ return duration;
+ }
+ maxDuration = Math.max(maxDuration, duration);
+ // If any step is unknown, this combination duration will also be unknown, unless
+ // any step is repeating. Repeating vibrations take precedence over non-repeating
+ // ones in the service, so continue looping to check for repeating steps.
+ hasUnknownStep |= duration < 0;
+ }
+ if (hasUnknownStep) {
+ // If any step is unknown, this combination duration is also unknown.
+ return -1;
}
return maxDuration;
}
@@ -477,16 +491,25 @@
@Override
public long getDuration() {
+ boolean hasUnknownStep = false;
long durations = 0;
final int effectCount = mEffects.size();
for (int i = 0; i < effectCount; i++) {
CombinedVibrationEffect effect = mEffects.get(i);
long duration = effect.getDuration();
- if (duration < 0) {
- // If any duration is unknown, this combination duration is also unknown.
+ if (duration == Long.MAX_VALUE) {
+ // If any duration is repeating, this combination duration is also repeating.
return duration;
}
durations += duration;
+ // If any step is unknown, this combination duration will also be unknown, unless
+ // any step is repeating. Repeating vibrations take precedence over non-repeating
+ // ones in the service, so continue looping to check for repeating steps.
+ hasUnknownStep |= duration < 0;
+ }
+ if (hasUnknownStep) {
+ // If any step is unknown, this combination duration is also unknown.
+ return -1;
}
long delays = 0;
for (int i = 0; i < effectCount; i++) {
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 804dc10..f9e2947 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.CombinedVibrationEffect;
+import android.os.IVibratorStateListener;
import android.os.VibrationAttributes;
import android.os.VibratorInfo;
@@ -24,6 +25,9 @@
interface IVibratorManagerService {
int[] getVibratorIds();
VibratorInfo getVibratorInfo(int vibratorId);
+ boolean isVibrating(int vibratorId);
+ boolean registerVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
+ boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
in CombinedVibrationEffect effect, in VibrationAttributes attributes);
void vibrate(int uid, String opPkg, in CombinedVibrationEffect effect,
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 2093077..217f178 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -268,7 +268,7 @@
}
/** @hide */
- public String usageToString(int usage) {
+ public static String usageToString(int usage) {
switch (usage) {
case USAGE_UNKNOWN:
return "UNKNOWN";
diff --git a/core/java/android/os/strictmode/IncorrectContextUseViolation.java b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
index 647db17..11d26ca 100644
--- a/core/java/android/os/strictmode/IncorrectContextUseViolation.java
+++ b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
@@ -16,19 +16,20 @@
package android.os.strictmode;
+import android.annotation.NonNull;
import android.content.Context;
/**
- * Incorrect usage of {@link Context}, such as obtaining a visual service from non-visual
- * {@link Context} instance.
+ * Incorrect usage of {@link Context}, such as obtaining a UI service from non-UI {@link Context}
+ * instance.
+ *
* @see Context#getSystemService(String)
- * @see Context#getDisplayNoVerify()
- * @hide
+ * @see Context#isUiContext(Context)
+ * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
*/
public final class IncorrectContextUseViolation extends Violation {
- /** @hide */
- public IncorrectContextUseViolation(String message, Throwable originStack) {
+ public IncorrectContextUseViolation(@NonNull String message, @NonNull Throwable originStack) {
super(message);
initCause(originStack);
}
diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md
index 4224b7a..dfe748b 100644
--- a/core/java/android/permission/Permissions.md
+++ b/core/java/android/permission/Permissions.md
@@ -809,6 +809,9 @@
permissions. It is unlikely that 3rd party apps will be able to use APIs protected by signature
permissions as they are usually not signed with the platform certificate.
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
Such permissions are defined and checked like an install time permission.
### Preinstalled permissions
@@ -819,6 +822,9 @@
Hence this permission level is discouraged unless there are
[further restrictions](#restricted-by-tests).
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
Such permissions are defined and checked like an install time permission.
### Privileged permissions
@@ -833,6 +839,9 @@
Hence this permission level is discouraged unless there are
[further restrictions](#restricted-by-tests).
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
Such permissions are defined and checked like an install time permission.
#### Restricted by tests
@@ -890,8 +899,16 @@
Which apps qualify for such a permission level is flexible and custom for each such level. Usually
they refer to a single or small set of apps, usually - but not always - apps defined in AOSP.
+This type of permission is deprecated in favor of
+[role protected permissions](#role-protected-permissions).
+
These permissions are defined and checked like an install time permission.
+### Role protected permissions
+
+See
+[Using role for permission protection](../../../../../../packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md).
+
### Development permissions
> Not recommended
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6dab4c0..f6ef8a3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13393,6 +13393,24 @@
"euicc_factory_reset_timeout_millis";
/**
+ * Flag to set the waiting time for euicc slot switch.
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String EUICC_SWITCH_SLOT_TIMEOUT_MILLIS =
+ "euicc_switch_slot_timeout_millis";
+
+ /**
+ * Flag to set the waiting time for enabling multi SIM slot.
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String ENABLE_MULTI_SLOT_TIMEOUT_MILLIS =
+ "enable_multi_slot_timeout_millis";
+
+ /**
* Flag to set the timeout for when to refresh the storage settings cached data.
* Type: long
*
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 4679c56..e3d0741 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -129,14 +129,14 @@
/** @hide */
@TestApi
- @SuppressLint("ConcreteCollection")
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
public @Nullable ArrayList<AutofillId> getFieldIds() {
return mFieldIds;
}
/** @hide */
@TestApi
- @SuppressLint("ConcreteCollection")
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
public @Nullable ArrayList<AutofillValue> getFieldValues() {
return mFieldValues;
}
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index e35b8b7..ad6316c 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -119,6 +119,7 @@
IoUtils.closeQuietly(control.incremental.cmd);
IoUtils.closeQuietly(control.incremental.pendingReads);
IoUtils.closeQuietly(control.incremental.log);
+ IoUtils.closeQuietly(control.incremental.blocksWritten);
}
}
}
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index e0f3018..44daeff 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -46,7 +46,7 @@
void onNotificationChannelGroupModification(String pkgName, in UserHandle user, in NotificationChannelGroup group, int modificationType);
// assistants only
- void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel);
+ void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel, in NotificationRankingUpdate update);
void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
void onNotificationsSeen(in List<String> keys);
void onPanelRevealed(int items);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index cf2152c..1d49a72 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -126,7 +126,7 @@
* {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel)}.</p>
*
* @param sbn the new notification
- * @return an adjustment or null to take no action, within 100ms.
+ * @return an adjustment or null to take no action, within 200ms.
*/
abstract public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn);
@@ -135,7 +135,7 @@
*
* @param sbn the new notification
* @param channel the channel the notification was posted to
- * @return an adjustment or null to take no action, within 100ms.
+ * @return an adjustment or null to take no action, within 200ms.
*/
public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn,
@NonNull NotificationChannel channel) {
@@ -143,6 +143,20 @@
}
/**
+ * A notification was posted by an app. Called before post.
+ *
+ * @param sbn the new notification
+ * @param channel the channel the notification was posted to
+ * @param rankingMap The current ranking map that can be used to retrieve ranking information
+ * for active notifications.
+ * @return an adjustment or null to take no action, within 200ms.
+ */
+ public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn,
+ @NonNull NotificationChannel channel, @NonNull RankingMap rankingMap) {
+ return onNotificationEnqueued(sbn, channel);
+ }
+
+ /**
* Implement this method to learn when notifications are removed, how they were interacted with
* before removal, and why they were removed.
* <p>
@@ -316,7 +330,7 @@
private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
@Override
public void onNotificationEnqueuedWithChannel(IStatusBarNotificationHolder sbnHolder,
- NotificationChannel channel) {
+ NotificationChannel channel, NotificationRankingUpdate update) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
@@ -330,9 +344,11 @@
return;
}
+ applyUpdateLocked(update);
SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = channel;
+ args.arg3 = getCurrentRanking();
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
args).sendToTarget();
}
@@ -472,8 +488,9 @@
SomeArgs args = (SomeArgs) msg.obj;
StatusBarNotification sbn = (StatusBarNotification) args.arg1;
NotificationChannel channel = (NotificationChannel) args.arg2;
+ RankingMap ranking = (RankingMap) args.arg3;
args.recycle();
- Adjustment adjustment = onNotificationEnqueued(sbn, channel);
+ Adjustment adjustment = onNotificationEnqueued(sbn, channel, ranking);
setAdjustmentIssuer(adjustment);
if (adjustment != null) {
if (!isBound()) {
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index c41e599..64cddc3 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1431,7 +1431,8 @@
@Override
public void onNotificationEnqueuedWithChannel(
- IStatusBarNotificationHolder notificationHolder, NotificationChannel channel)
+ IStatusBarNotificationHolder notificationHolder, NotificationChannel channel,
+ NotificationRankingUpdate update)
throws RemoteException {
// no-op in the listener
}
diff --git a/core/java/android/service/smartspace/ISmartspaceService.aidl b/core/java/android/service/smartspace/ISmartspaceService.aidl
new file mode 100644
index 0000000..c9c6807
--- /dev/null
+++ b/core/java/android/service/smartspace/ISmartspaceService.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.service.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * Interface from the system to Smartspace service.
+ *
+ * @hide
+ */
+oneway interface ISmartspaceService {
+
+ void onCreateSmartspaceSession(in SmartspaceConfig context, in SmartspaceSessionId sessionId);
+
+ void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+ void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+ void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void onDestroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/service/smartspace/OWNERS b/core/java/android/service/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/core/java/android/service/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
new file mode 100644
index 0000000..09b7310
--- /dev/null
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2021 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.service.smartspace;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService.Stub;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A service used to share the lifecycle of smartspace UI (open, close, interaction)
+ * and also to return smartspace result on a query.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SmartspaceService extends Service {
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ *
+ * <p>The service must also require the {@link android.permission#MANAGE_SMARTSPACE}
+ * permission.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.smartspace.SmartspaceService";
+ private static final boolean DEBUG = false;
+ private static final String TAG = "SmartspaceService";
+ private final ArrayMap<SmartspaceSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks =
+ new ArrayMap<>();
+ private Handler mHandler;
+
+ private final android.service.smartspace.ISmartspaceService mInterface = new Stub() {
+
+ @Override
+ public void onCreateSmartspaceSession(SmartspaceConfig smartspaceConfig,
+ SmartspaceSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doCreateSmartspaceSession,
+ SmartspaceService.this, smartspaceConfig, sessionId));
+ }
+
+ @Override
+ public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+ SmartspaceTargetEvent event) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::notifySmartspaceEvent,
+ SmartspaceService.this, sessionId, event));
+ }
+
+ @Override
+ public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doRequestPredictionUpdate,
+ SmartspaceService.this, sessionId));
+ }
+
+ @Override
+ public void registerSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doRegisterSmartspaceUpdates,
+ SmartspaceService.this, sessionId, callback));
+ }
+
+ @Override
+ public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doUnregisterSmartspaceUpdates,
+ SmartspaceService.this, sessionId, callback));
+ }
+
+ @Override
+ public void onDestroySmartspaceSession(SmartspaceSessionId sessionId) {
+
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doDestroy,
+ SmartspaceService.this, sessionId));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ @Override
+ @NonNull
+ public final IBinder onBind(@NonNull Intent intent) {
+ Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks);
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Slog.w(TAG, "Tried to bind to wrong intent (should be "
+ + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+
+ private void doCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+ @NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+ mSessionCallbacks.put(sessionId, new ArrayList<>());
+ onCreateSmartspaceSession(config, sessionId);
+ }
+
+ /**
+ * Gets called when the client calls <code> SmartspaceManager#createSmartspaceSession </code>.
+ */
+ public abstract void onCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+ @NonNull SmartspaceSessionId sessionId);
+
+ /**
+ * Gets called when the client calls <code> SmartspaceSession#notifySmartspaceEvent </code>.
+ */
+ @MainThread
+ public abstract void notifySmartspaceEvent(@NonNull SmartspaceSessionId sessionId,
+ @NonNull SmartspaceTargetEvent event);
+
+ /**
+ * Gets called when the client calls <code> SmartspaceSession#requestSmartspaceUpdate </code>.
+ */
+ @MainThread
+ public abstract void onRequestSmartspaceUpdate(@NonNull SmartspaceSessionId sessionId);
+
+ private void doRegisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper == null) {
+ callbacks.add(new CallbackWrapper(callback,
+ callbackWrapper ->
+ mHandler.post(
+ () -> removeCallbackWrapper(callbacks, callbackWrapper))));
+ }
+ }
+
+ private void doUnregisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper != null) {
+ removeCallbackWrapper(callbacks, wrapper);
+ }
+ }
+
+ private void doRequestPredictionUpdate(@NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks);
+ // Just an optimization, if there are no callbacks, then don't bother notifying the service
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null && !callbacks.isEmpty()) {
+ onRequestSmartspaceUpdate(sessionId);
+ }
+ }
+
+ /**
+ * Finds the callback wrapper for the given callback.
+ */
+ private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+ ISmartspaceCallback callback) {
+ for (int i = callbacks.size() - 1; i >= 0; i--) {
+ if (callbacks.get(i).isCallback(callback)) {
+ return callbacks.get(i);
+ }
+ }
+ return null;
+ }
+
+ private void removeCallbackWrapper(
+ ArrayList<CallbackWrapper> callbacks, CallbackWrapper wrapper) {
+ if (callbacks == null) {
+ return;
+ }
+ callbacks.remove(wrapper);
+ }
+
+ /**
+ * Gets called when the client calls <code> SmartspaceManager#destroy() </code>.
+ */
+ public abstract void onDestroySmartspaceSession(@NonNull SmartspaceSessionId sessionId);
+
+ private void doDestroy(@NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks);
+ super.onDestroy();
+ mSessionCallbacks.remove(sessionId);
+ onDestroySmartspaceSession(sessionId);
+ }
+
+ /**
+ * Used by the prediction factory to send back results the client app. The can be called
+ * in response to {@link #onRequestSmartspaceUpdate(SmartspaceSessionId)} or proactively as
+ * a result of changes in predictions.
+ */
+ public final void updateSmartspaceTargets(@NonNull SmartspaceSessionId sessionId,
+ @NonNull List<SmartspaceTarget> targets) {
+ Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks);
+ List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null) {
+ for (CallbackWrapper callback : callbacks) {
+ callback.accept(targets);
+ }
+ }
+ }
+
+ /**
+ * Destroys a smartspace session.
+ */
+ @MainThread
+ public abstract void onDestroy(@NonNull SmartspaceSessionId sessionId);
+
+ private static final class CallbackWrapper implements Consumer<List<SmartspaceTarget>>,
+ IBinder.DeathRecipient {
+
+ private final Consumer<CallbackWrapper> mOnBinderDied;
+ private ISmartspaceCallback mCallback;
+
+ CallbackWrapper(ISmartspaceCallback callback,
+ @Nullable Consumer<CallbackWrapper> onBinderDied) {
+ mCallback = callback;
+ mOnBinderDied = onBinderDied;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death: " + e);
+ }
+ }
+
+ public boolean isCallback(@NonNull ISmartspaceCallback callback) {
+ if (mCallback == null) {
+ Slog.e(TAG, "Callback is null, likely the binder has died.");
+ return false;
+ }
+ return mCallback.equals(callback);
+ }
+
+ @Override
+ public void accept(List<SmartspaceTarget> smartspaceTargets) {
+ try {
+ if (mCallback != null) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "CallbackWrapper.accept smartspaceTargets=" + smartspaceTargets);
+ }
+ mCallback.onResult(new ParceledListSlice(smartspaceTargets));
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result:" + e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ mCallback = null;
+ if (mOnBinderDied != null) {
+ mOnBinderDied.accept(this);
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 68d6f3f..25f8090 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -345,7 +345,8 @@
*/
@SystemApi
@HotwordConfigResult
- public final int setHotwordDetectionConfig(@Nullable Bundle options) {
+ public final int setHotwordDetectionConfig(
+ @SuppressLint("NullableCollection") @Nullable Bundle options) {
if (mSystemService == null) {
throw new IllegalStateException("Not available until onReady() is called");
}
diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
index 723f1dd..49ff237 100644
--- a/core/java/android/util/imetracing/ImeTracing.java
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -28,6 +28,8 @@
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;
+import com.android.internal.inputmethod.Completable;
+import com.android.internal.inputmethod.ResultCallbacks;
import com.android.internal.view.IInputMethodManager;
import java.io.PrintWriter;
@@ -91,7 +93,9 @@
* @param where
*/
public void sendToService(byte[] protoDump, int source, String where) throws RemoteException {
- mService.startProtoDump(protoDump, source, where);
+ final Completable.Void value = Completable.createVoid();
+ mService.startProtoDump(protoDump, source, where, ResultCallbacks.of(value));
+ Completable.getResult(value);
}
/**
diff --git a/core/java/android/util/imetracing/OWNERS b/core/java/android/util/imetracing/OWNERS
new file mode 100644
index 0000000..885fd0a
--- /dev/null
+++ b/core/java/android/util/imetracing/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/android/view/ContentInfo.java b/core/java/android/view/ContentInfo.java
index bc66ea1..547bc9d 100644
--- a/core/java/android/view/ContentInfo.java
+++ b/core/java/android/view/ContentInfo.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -204,6 +205,7 @@
* the IME.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public Bundle getExtras() {
return mExtras;
}
@@ -347,7 +349,7 @@
* @return this builder
*/
@NonNull
- public Builder setExtras(@Nullable Bundle extras) {
+ public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
mExtras = extras;
return this;
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f4b90e1..bc03222 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -22,6 +22,7 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.Battery;
import android.hardware.SensorManager;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
@@ -73,6 +74,7 @@
private final boolean mHasMicrophone;
private final boolean mHasButtonUnderPad;
private final boolean mHasSensor;
+ private final boolean mHasBattery;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@GuardedBy("mMotionRanges")
@@ -84,6 +86,9 @@
@GuardedBy("mMotionRanges")
private SensorManager mSensorManager;
+ @GuardedBy("mMotionRanges")
+ private Battery mBattery;
+
/**
* A mask for input source classes.
*
@@ -323,6 +328,13 @@
public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON;
/**
+ * The input source is a sensor associated with the input device.
+ *
+ * @see #SOURCE_CLASS_NONE
+ */
+ public static final int SOURCE_SENSOR = 0x04000000 | SOURCE_CLASS_NONE;
+
+ /**
* A special input source constant that is used when filtering input devices
* to match devices that provide any type of input source.
*/
@@ -448,7 +460,7 @@
public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone,
- boolean hasButtonUnderPad, boolean hasSensor) {
+ boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -464,6 +476,7 @@
mHasMicrophone = hasMicrophone;
mHasButtonUnderPad = hasButtonUnderPad;
mHasSensor = hasSensor;
+ mHasBattery = hasBattery;
mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
}
@@ -483,6 +496,7 @@
mHasMicrophone = in.readInt() != 0;
mHasButtonUnderPad = in.readInt() != 0;
mHasSensor = in.readInt() != 0;
+ mHasBattery = in.readInt() != 0;
mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
int numRanges = in.readInt();
@@ -830,6 +844,22 @@
}
/**
+ * Gets the battery object associated with the device, if there is one.
+ * Even if the device does not have a battery, the result is never null.
+ * Use {@link Battery#hasBattery} to determine whether a battery is
+ * present.
+ *
+ * @return The battery object associated with the device, never null.
+ */
+ @NonNull
+ public Battery getBattery() {
+ if (mBattery == null) {
+ mBattery = InputManager.getInstance().getInputDeviceBattery(mId, mHasBattery);
+ }
+ return mBattery;
+ }
+
+ /**
* Gets the sensor manager service associated with the input device.
* Even if the device does not have a sensor, the result is never null.
* Use {@link SensorManager#getSensorList} to get a full list of all supported sensors.
@@ -1051,6 +1081,7 @@
out.writeInt(mHasMicrophone ? 1 : 0);
out.writeInt(mHasButtonUnderPad ? 1 : 0);
out.writeInt(mHasSensor ? 1 : 0);
+ out.writeInt(mHasBattery ? 1 : 0);
final int numRanges = mMotionRanges.size();
out.writeInt(numRanges);
@@ -1097,6 +1128,8 @@
description.append(" Has Sensor: ").append(mHasSensor).append("\n");
+ description.append(" Has battery: ").append(mHasBattery).append("\n");
+
description.append(" Has mic: ").append(mHasMicrophone).append("\n");
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index bf377b0..d68e903 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -106,7 +106,9 @@
public static final int ITYPE_NAVIGATION_BAR = 1;
public static final int ITYPE_CAPTION_BAR = 2;
- public static final int ITYPE_TOP_GESTURES = 3;
+ // The always visible types are visible to all windows regardless of the z-order.
+ public static final int FIRST_ALWAYS_VISIBLE_TYPE = 3;
+ public static final int ITYPE_TOP_GESTURES = FIRST_ALWAYS_VISIBLE_TYPE;
public static final int ITYPE_BOTTOM_GESTURES = 4;
public static final int ITYPE_LEFT_GESTURES = 5;
public static final int ITYPE_RIGHT_GESTURES = 6;
@@ -117,15 +119,16 @@
public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
- public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 11;
- public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 12;
- public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 13;
- public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 14;
+ public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11;
+ public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
+ public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
+ public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
+ public static final int LAST_ALWAYS_VISIBLE_TYPE = ITYPE_BOTTOM_DISPLAY_CUTOUT;
- public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 15;
- public static final int ITYPE_TOP_DISPLAY_CUTOUT = 16;
- public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 17;
- public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 18;
+ public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
+ public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
+ public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17;
+ public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18;
/** Input method window. */
public static final int ITYPE_IME = 19;
@@ -182,6 +185,18 @@
}
/**
+ * Mirror the always visible sources from the other state. They will share the same object for
+ * the always visible types.
+ *
+ * @param other the state to mirror the mirrored sources from.
+ */
+ public void mirrorAlwaysVisibleInsetsSources(InsetsState other) {
+ for (int type = FIRST_ALWAYS_VISIBLE_TYPE; type <= LAST_ALWAYS_VISIBLE_TYPE; type++) {
+ mSources[type] = other.mSources[type];
+ }
+ }
+
+ /**
* Calculates {@link WindowInsets} based on the current source configuration.
*
* @param frame The frame to calculate the insets relative to.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0a1a231..acd2507 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -750,18 +750,22 @@
private abstract static class CaptureArgs {
private final int mPixelFormat;
private final Rect mSourceCrop = new Rect();
- private final float mFrameScale;
+ private final float mFrameScaleX;
+ private final float mFrameScaleY;
private final boolean mCaptureSecureLayers;
private final boolean mAllowProtected;
private final long mUid;
+ private final boolean mGrayscale;
private CaptureArgs(Builder<? extends Builder<?>> builder) {
mPixelFormat = builder.mPixelFormat;
mSourceCrop.set(builder.mSourceCrop);
- mFrameScale = builder.mFrameScale;
+ mFrameScaleX = builder.mFrameScaleX;
+ mFrameScaleY = builder.mFrameScaleY;
mCaptureSecureLayers = builder.mCaptureSecureLayers;
mAllowProtected = builder.mAllowProtected;
mUid = builder.mUid;
+ mGrayscale = builder.mGrayscale;
}
/**
@@ -772,10 +776,12 @@
abstract static class Builder<T extends Builder<T>> {
private int mPixelFormat = PixelFormat.RGBA_8888;
private final Rect mSourceCrop = new Rect();
- private float mFrameScale = 1;
+ private float mFrameScaleX = 1;
+ private float mFrameScaleY = 1;
private boolean mCaptureSecureLayers;
private boolean mAllowProtected;
private long mUid = -1;
+ private boolean mGrayscale;
/**
* The desired pixel format of the returned buffer.
@@ -798,7 +804,18 @@
* The desired scale of the returned buffer. The raw screen will be scaled up/down.
*/
public T setFrameScale(float frameScale) {
- mFrameScale = frameScale;
+ mFrameScaleX = frameScale;
+ mFrameScaleY = frameScale;
+ return getThis();
+ }
+
+ /**
+ * The desired scale of the returned buffer, allowing separate values for x and y scale.
+ * The raw screen will be scaled up/down.
+ */
+ public T setFrameScale(float frameScaleX, float frameScaleY) {
+ mFrameScaleX = frameScaleX;
+ mFrameScaleY = frameScaleY;
return getThis();
}
@@ -834,6 +851,14 @@
}
/**
+ * Set whether the screenshot should use grayscale or not.
+ */
+ public T setGrayscale(boolean grayscale) {
+ mGrayscale = grayscale;
+ return getThis();
+ }
+
+ /**
* Each sub class should return itself to allow the builder to chain properly
*/
abstract T getThis();
@@ -929,7 +954,7 @@
/**
* The arguments class used to make layer capture requests.
*
- * @see #nativeCaptureLayers(LayerCaptureArgs)
+ * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
* @hide
*/
public static class LayerCaptureArgs extends CaptureArgs {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5c0e156..9a412fc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -41,6 +41,7 @@
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.annotation.UiThread;
@@ -9030,7 +9031,8 @@
* not be null or empty if a non-null listener is passed in.
* @param listener The listener to use. This can be null to reset to the default behavior.
*/
- public void setOnReceiveContentListener(@Nullable String[] mimeTypes,
+ public void setOnReceiveContentListener(
+ @SuppressLint("NullableCollection") @Nullable String[] mimeTypes,
@Nullable OnReceiveContentListener listener) {
if (listener != null) {
Preconditions.checkArgument(mimeTypes != null && mimeTypes.length > 0,
@@ -9106,6 +9108,7 @@
* @return The MIME types accepted by {@link #performReceiveContent} for this view (may
* include patterns such as "image/*").
*/
+ @SuppressLint("NullableCollection")
@Nullable
public String[] getOnReceiveContentMimeTypes() {
return mOnReceiveContentMimeTypes;
@@ -21367,6 +21370,10 @@
int height = mBottom - mTop;
int layerType = getLayerType();
+ // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+ // instead of being "stateful" like other RenderNode properties
+ renderNode.clearStretch();
+
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
@@ -22793,6 +22800,11 @@
final Rect bounds = drawable.getBounds();
final int width = bounds.width();
final int height = bounds.height();
+
+ // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+ // instead of being "stateful" like other RenderNode properties
+ renderNode.clearStretch();
+
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
// Reverse left/top translation done by drawable canvas, which will
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index f5aa97a..8b3fb2e 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
@@ -377,7 +378,8 @@
* <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
* will be ignored when used for Assist.
*/
- public void setOnReceiveContentMimeTypes(@Nullable String[] mimeTypes) {}
+ public void setOnReceiveContentMimeTypes(
+ @SuppressLint("NullableCollection") @Nullable String[] mimeTypes) {}
/**
* Sets the {@link android.text.InputType} bits of this node.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fa471fa..8319b74 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -83,6 +83,7 @@
import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -99,6 +100,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Build;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -2855,12 +2857,14 @@
/**
* The token of {@link android.app.WindowContext}. It is usually a
- * {@link android.app.WindowTokenClient} and is used for updating
- * {@link android.content.res.Resources} from {@link Configuration} propagated from the
- * server side.
+ * {@link android.app.WindowTokenClient} and is used for associating the params with an
+ * existing node in the WindowManager hierarchy and getting the corresponding
+ * {@link Configuration} and {@link android.content.res.Resources} values with updates
+ * propagated from the server side.
*
* @hide
*/
+ @Nullable
public IBinder mWindowContextToken = null;
/**
@@ -3547,6 +3551,37 @@
return userActivityTimeout;
}
+ /**
+ * Sets the {@link android.app.WindowContext} token.
+ *
+ * @see #getWindowContextToken()
+ *
+ * @hide
+ */
+ @TestApi
+ public final void setWindowContextToken(@NonNull IBinder token) {
+ mWindowContextToken = token;
+ }
+
+ /**
+ * Gets the {@link android.app.WindowContext} token.
+ *
+ * The token is usually a {@link android.app.WindowTokenClient} and is used for associating
+ * the params with an existing node in the WindowManager hierarchy and getting the
+ * corresponding {@link Configuration} and {@link android.content.res.Resources} values with
+ * updates propagated from the server side.
+ *
+ * @see android.app.WindowTokenClient
+ * @see Context#createWindowContext(Display, int, Bundle)
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public final IBinder getWindowContextToken() {
+ return mWindowContextToken;
+ }
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index a76d46d1..f3111bd 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -858,7 +858,7 @@
boolean reportFullscreenMode(boolean enabled);
/**
- * Have the editor perform spell checking around the current selection.
+ * Have the editor perform spell checking for the full content.
*
* <p>The editor can ignore this method call if it does not support spell checking.
*
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 0a1aea3..5980cb6 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -187,7 +187,8 @@
* @return The spell checker session of the spell checker.
*/
@Nullable
- public SpellCheckerSession newSpellCheckerSession(@Nullable Bundle bundle,
+ public SpellCheckerSession newSpellCheckerSession(
+ @SuppressLint("NullableCollection") @Nullable Bundle bundle,
@SuppressLint("UseIcu") @Nullable Locale locale,
@NonNull SpellCheckerSessionListener listener,
@SuppressLint("ListenerLast") boolean referToSpellCheckerLanguageSettings,
@@ -277,6 +278,7 @@
* @return The list of currently enabled spell checkers.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public List<SpellCheckerInfo> getEnabledSpellCheckersList() {
final SpellCheckerInfo[] enabledSpellCheckers = getEnabledSpellCheckers();
return enabledSpellCheckers != null ? Arrays.asList(enabledSpellCheckers) : null;
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index ffdb89d..98738ef 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -16,6 +16,8 @@
package android.widget;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -25,8 +27,10 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.text.format.DateUtils;
import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
import android.view.View;
import android.widget.RemoteViews.RemoteView;
@@ -42,25 +46,32 @@
* @attr ref android.R.styleable#AnalogClock_dial
* @attr ref android.R.styleable#AnalogClock_hand_hour
* @attr ref android.R.styleable#AnalogClock_hand_minute
+ * @attr ref android.R.styleable#AnalogClock_hand_second
* @deprecated This widget is no longer supported.
*/
@RemoteView
@Deprecated
public class AnalogClock extends View {
+ /** How often the clock should refresh to make the seconds hand advance at ~15 FPS. */
+ private static final long SECONDS_TICK_FREQUENCY_MS = 1000 / 15;
+
private Clock mClock;
@UnsupportedAppUsage
private Drawable mHourHand;
@UnsupportedAppUsage
private Drawable mMinuteHand;
+ @Nullable
+ private Drawable mSecondHand;
@UnsupportedAppUsage
private Drawable mDial;
private int mDialWidth;
private int mDialHeight;
- private boolean mAttached;
+ private boolean mVisible;
+ private float mSeconds;
private float mMinutes;
private float mHour;
private boolean mChanged;
@@ -101,18 +112,70 @@
mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
}
+ mSecondHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_second);
+
mClock = Clock.systemDefaultZone();
mDialWidth = mDial.getIntrinsicWidth();
mDialHeight = mDial.getIntrinsicHeight();
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
+ /** Sets the dial of the clock to the specified Icon. */
+ @RemotableViewMethod
+ public void setDial(@NonNull Icon icon) {
+ mDial = icon.loadDrawable(getContext());
+ mDialWidth = mDial.getIntrinsicWidth();
+ mDialHeight = mDial.getIntrinsicHeight();
- if (!mAttached) {
- mAttached = true;
+ mChanged = true;
+ invalidate();
+ }
+
+ /** Sets the hour hand of the clock to the specified Icon. */
+ @RemotableViewMethod
+ public void setHourHand(@NonNull Icon icon) {
+ mHourHand = icon.loadDrawable(getContext());
+
+ mChanged = true;
+ invalidate();
+ }
+
+ /** Sets the minute hand of the clock to the specified Icon. */
+ @RemotableViewMethod
+ public void setMinuteHand(@NonNull Icon icon) {
+ mMinuteHand = icon.loadDrawable(getContext());
+
+ mChanged = true;
+ invalidate();
+ }
+
+ /**
+ * Sets the second hand of the clock to the specified Icon, or hides the second hand if it is
+ * null.
+ */
+ @RemotableViewMethod
+ public void setSecondHand(@Nullable Icon icon) {
+ mSecondHand = icon == null ? null : icon.loadDrawable(getContext());
+ mSecondsTick.run();
+
+ mChanged = true;
+ invalidate();
+ }
+
+ @Override
+ public void onVisibilityAggregated(boolean isVisible) {
+ super.onVisibilityAggregated(isVisible);
+
+ if (isVisible) {
+ onVisible();
+ } else {
+ onInvisible();
+ }
+ }
+
+ private void onVisible() {
+ if (!mVisible) {
+ mVisible = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
@@ -128,6 +191,8 @@
// user not the one the context is for.
getContext().registerReceiverAsUser(mIntentReceiver,
android.os.Process.myUserHandle(), filter, null, getHandler());
+
+ mSecondsTick.run();
}
// NOTE: It's safe to do these after registering the receiver since the receiver always runs
@@ -140,12 +205,11 @@
onTimeChanged();
}
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mAttached) {
+ private void onInvisible() {
+ if (mVisible) {
getContext().unregisterReceiver(mIntentReceiver);
- mAttached = false;
+ removeCallbacks(mSecondsTick);
+ mVisible = false;
}
}
@@ -237,6 +301,20 @@
minuteHand.draw(canvas);
canvas.restore();
+ final Drawable secondHand = mSecondHand;
+ if (secondHand != null) {
+ canvas.save();
+ canvas.rotate(mSeconds / 60.0f * 360.0f, x, y);
+
+ if (changed) {
+ w = secondHand.getIntrinsicWidth();
+ h = secondHand.getIntrinsicHeight();
+ secondHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
+ }
+ secondHand.draw(canvas);
+ canvas.restore();
+ }
+
if (scaled) {
canvas.restore();
}
@@ -250,6 +328,7 @@
int minute = localDateTime.getMinute();
int second = localDateTime.getSecond();
+ mSeconds = second + localDateTime.getNano() / 1_000_000_000f;
mMinutes = minute + second / 60.0f;
mHour = hour + mMinutes / 60.0f;
mChanged = true;
@@ -271,6 +350,21 @@
}
};
+ private final Runnable mSecondsTick = new Runnable() {
+ @Override
+ public void run() {
+ if (!mVisible || mSecondHand == null) {
+ return;
+ }
+
+ onTimeChanged();
+
+ invalidate();
+
+ postDelayed(this, SECONDS_TICK_FREQUENCY_MS);
+ }
+ };
+
private void updateContentDescription(long timeMillis) {
final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
String contentDescription = DateUtils.formatDateTime(mContext, timeMillis, flags);
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 97d98fd..794b642 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -220,29 +220,26 @@
}
void onPerformSpellCheck() {
- final int selectionStart = mTextView.getSelectionStart();
- final int selectionEnd = mTextView.getSelectionEnd();
- final int selectionRangeStart;
- final int selectionRangeEnd;
- if (selectionStart < selectionEnd) {
- selectionRangeStart = selectionStart;
- selectionRangeEnd = selectionEnd;
- } else {
- selectionRangeStart = selectionEnd;
- selectionRangeEnd = selectionStart;
- }
- // Expand the range so that it (hopefully) includes the current sentence.
- final int start = Math.max(0, selectionRangeStart - MIN_SENTENCE_LENGTH);
- final int end = Math.min(mTextView.length(), selectionRangeEnd + MIN_SENTENCE_LENGTH);
+ // Triggers full content spell check.
+ final int start = 0;
+ final int end = mTextView.length();
if (DBG) {
Log.d(TAG, "performSpellCheckAroundSelection: " + start + ", " + end);
}
- spellCheck(start, end);
+ spellCheck(start, end, /* forceCheckWhenEditingWord= */ true);
}
public void spellCheck(int start, int end) {
+ spellCheck(start, end, /* forceCheckWhenEditingWord= */ false);
+ }
+
+ /**
+ * Requests to do spell check for text in the range (start, end).
+ */
+ public void spellCheck(int start, int end, boolean forceCheckWhenEditingWord) {
if (DBG) {
- Log.d(TAG, "Start spell-checking: " + start + ", " + end);
+ Log.d(TAG, "Start spell-checking: " + start + ", " + end + ", "
+ + forceCheckWhenEditingWord);
}
final Locale locale = mTextView.getSpellCheckerLocale();
final boolean isSessionActive = isSessionActive();
@@ -267,7 +264,7 @@
for (int i = 0; i < length; i++) {
final SpellParser spellParser = mSpellParsers[i];
if (spellParser.isFinished()) {
- spellParser.parse(start, end);
+ spellParser.parse(start, end, forceCheckWhenEditingWord);
return;
}
}
@@ -282,10 +279,14 @@
SpellParser spellParser = new SpellParser();
mSpellParsers[length] = spellParser;
- spellParser.parse(start, end);
+ spellParser.parse(start, end, forceCheckWhenEditingWord);
}
private void spellCheck() {
+ spellCheck(/* forceCheckWhenEditingWord= */ false);
+ }
+
+ private void spellCheck(boolean forceCheckWhenEditingWord) {
if (mSpellCheckerSession == null) return;
Editable editable = (Editable) mTextView.getText();
@@ -295,6 +296,12 @@
TextInfo[] textInfos = new TextInfo[mLength];
int textInfosCount = 0;
+ if (DBG) {
+ Log.d(TAG, "forceCheckWhenEditingWord=" + forceCheckWhenEditingWord
+ + ", mLength=" + mLength + ", cookie = " + mCookie
+ + ", sel start = " + selectionStart + ", sel end = " + selectionEnd);
+ }
+
for (int i = 0; i < mLength; i++) {
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) continue;
@@ -319,7 +326,7 @@
} else {
isEditing = selectionEnd < start || selectionStart > end;
}
- if (start >= 0 && end > start && isEditing) {
+ if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
spellCheckSpan.setSpellCheckInProgress(true);
final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
textInfos[textInfosCount++] = textInfo;
@@ -546,7 +553,11 @@
private class SpellParser {
private Object mRange = new Object();
- public void parse(int start, int end) {
+ // Forces to do spell checker even user is editing the word.
+ private boolean mForceCheckWhenEditingWord;
+
+ public void parse(int start, int end, boolean forceCheckWhenEditingWord) {
+ mForceCheckWhenEditingWord = forceCheckWhenEditingWord;
final int max = mTextView.length();
final int parseEnd;
if (end > max) {
@@ -567,6 +578,7 @@
public void stop() {
removeRangeSpan((Editable) mTextView.getText());
+ mForceCheckWhenEditingWord = false;
}
private void setRangeSpan(Editable editable, int start, int end) {
@@ -617,7 +629,7 @@
if (DBG) {
Log.i(TAG, "No more spell check.");
}
- removeRangeSpan(editable);
+ stop();
return;
}
@@ -649,7 +661,7 @@
if (DBG) {
Log.i(TAG, "Incorrect range span.");
}
- removeRangeSpan(editable);
+ stop();
return;
}
do {
@@ -778,7 +790,7 @@
removeRangeSpan(editable);
}
- spellCheck();
+ spellCheck(mForceCheckWhenEditingWord);
}
private <T> void removeSpansAt(Editable editable, int offset, T[] spans) {
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index f29eb39..cdb4762 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.os.IBinder;
@@ -151,6 +152,7 @@
/** Gets direct child tasks (ordered from top-to-bottom) */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@Nullable
+ @SuppressLint("NullableCollection")
public List<ActivityManager.RunningTaskInfo> getChildTasks(
@NonNull WindowContainerToken parent, @NonNull int[] activityTypes) {
try {
@@ -163,6 +165,7 @@
/** Gets all root tasks on a display (ordered from top-to-bottom) */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@Nullable
+ @SuppressLint("NullableCollection")
public List<ActivityManager.RunningTaskInfo> getRootTasks(
int displayId, @NonNull int[] activityTypes) {
try {
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 99692d0..7ade05c 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -5,3 +5,4 @@
per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS
per-file IVoice* = file:/core/java/android/service/voice/OWNERS
per-file *Hotword* = file:/core/java/android/service/voice/OWNERS
+per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 13358daf..ee98878 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -62,6 +62,11 @@
*/
public static final String ENABLE_NAS_FEEDBACK = "enable_nas_feedback";
+ /**
+ * Whether the Notification Assistant can label a notification not a conversation
+ */
+ public static final String ENABLE_NAS_NOT_CONVERSATION = "enable_nas_not_conversation";
+
// Flags related to screenshot intelligence
/**
@@ -420,6 +425,12 @@
public static final String PIP_STASHING = "pip_stashing";
/**
+ * (float) The threshold velocity to cause PiP to be stashed when flinging from one edge to the
+ * other.
+ */
+ public static final String PIP_STASH_MINIMUM_VELOCITY_THRESHOLD = "pip_velocity_threshold";
+
+ /**
* (float) Bottom height in DP for Back Gesture.
*/
public static final String BACK_GESTURE_BOTTOM_HEIGHT = "back_gesture_bottom_height";
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index b8c066d..ea2fb88 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.hardware.SensorManager;
-import android.net.ConnectivityManager;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
@@ -57,7 +56,7 @@
mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
- if (!isWifiOnlyDevice(mContext)) {
+ if (!BatteryStatsHelper.checkWifiOnly(mContext)) {
mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
}
mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile));
@@ -81,14 +80,6 @@
return mPowerCalculators;
}
- private static boolean isWifiOnlyDevice(Context context) {
- ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
- if (cm == null) {
- return false;
- }
- return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
- }
-
/**
* Returns a snapshot of battery attribution data.
*/
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 892c5a5..50bbfc5 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -91,11 +91,12 @@
/** Remove the IME surface. Requires passing the currently focused window. */
oneway void removeImeSurfaceFromWindow(in IBinder windowToken,
in IVoidResultCallback resultCallback);
- void startProtoDump(in byte[] protoDump, int source, String where);
+ oneway void startProtoDump(in byte[] protoDump, int source, String where,
+ in IVoidResultCallback resultCallback);
oneway void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
// Starts an ime trace.
- void startImeTrace();
+ oneway void startImeTrace(in IVoidResultCallback resultCallback);
// Stops an ime trace.
- void stopImeTrace();
+ oneway void stopImeTrace(in IVoidResultCallback resultCallback);
}
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 4eaa016..9cc7243 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -70,7 +70,8 @@
deviceInfo.isExternal(), deviceInfo.getSources(),
deviceInfo.getKeyboardType(), kcmObj.get(),
deviceInfo.hasVibrator(), hasMic,
- deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor()));
+ deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(),
+ deviceInfo.hasBattery()));
const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (const InputDeviceInfo::MotionRange& range: ranges) {
@@ -90,9 +91,10 @@
gInputDeviceClassInfo.clazz = FindClassOrDie(env, "android/view/InputDevice");
gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
- gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
- "(IIILjava/lang/String;IILjava/lang/"
- "String;ZIILandroid/view/KeyCharacterMap;ZZZZ)V");
+ gInputDeviceClassInfo.ctor =
+ GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
+ "(IIILjava/lang/String;IILjava/lang/"
+ "String;ZIILandroid/view/KeyCharacterMap;ZZZZZ)V");
gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
"addMotionRange", "(IIFFFFF)V");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4ef63ae..05fcaec 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -27,6 +27,7 @@
#include <android-base/chrono_utils.h>
#include <android/graphics/region.h>
#include <android/gui/BnScreenCaptureListener.h>
+#include <android/os/IInputConstants.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
#include <android_runtime/android_view_Surface.h>
@@ -110,10 +111,12 @@
static struct {
jfieldID pixelFormat;
jfieldID sourceCrop;
- jfieldID frameScale;
+ jfieldID frameScaleX;
+ jfieldID frameScaleY;
jfieldID captureSecureLayers;
jfieldID allowProtected;
jfieldID uid;
+ jfieldID grayscale;
} gCaptureArgsClassInfo;
static struct {
@@ -380,13 +383,17 @@
captureArgs.sourceCrop =
rectFromObj(env,
env->GetObjectField(captureArgsObject, gCaptureArgsClassInfo.sourceCrop));
- captureArgs.frameScale =
- env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScale);
+ captureArgs.frameScaleX =
+ env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX);
+ captureArgs.frameScaleY =
+ env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleY);
captureArgs.captureSecureLayers =
env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.captureSecureLayers);
captureArgs.allowProtected =
env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.allowProtected);
captureArgs.uid = env->GetLongField(captureArgsObject, gCaptureArgsClassInfo.uid);
+ captureArgs.grayscale =
+ env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.grayscale);
}
static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
@@ -1619,7 +1626,8 @@
jlong frameTimelineVsyncId) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->setFrameTimelineVsync(frameTimelineVsyncId);
+ transaction->setFrameTimelineInfo(
+ {frameTimelineVsyncId, android::os::IInputConstants::INVALID_INPUT_EVENT_ID});
}
class JankDataListenerWrapper : public JankDataListener {
@@ -2032,12 +2040,14 @@
gCaptureArgsClassInfo.pixelFormat = GetFieldIDOrDie(env, captureArgsClazz, "mPixelFormat", "I");
gCaptureArgsClassInfo.sourceCrop =
GetFieldIDOrDie(env, captureArgsClazz, "mSourceCrop", "Landroid/graphics/Rect;");
- gCaptureArgsClassInfo.frameScale = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScale", "F");
+ gCaptureArgsClassInfo.frameScaleX = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleX", "F");
+ gCaptureArgsClassInfo.frameScaleY = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleY", "F");
gCaptureArgsClassInfo.captureSecureLayers =
GetFieldIDOrDie(env, captureArgsClazz, "mCaptureSecureLayers", "Z");
gCaptureArgsClassInfo.allowProtected =
GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z");
gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J");
+ gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z");
jclass displayCaptureArgsClazz =
FindClassOrDie(env, "android/view/SurfaceControl$DisplayCaptureArgs");
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c76a5d3..c5a9559 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -70,7 +70,6 @@
#include <bionic/malloc.h>
#include <bionic/mte.h>
#include <cutils/fs.h>
-#include <cutils/memory.h>
#include <cutils/multiuser.h>
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h>
@@ -621,13 +620,6 @@
// Set the jemalloc decay time to 1.
mallopt(M_DECAY_TIME, 1);
-
- // Avoid potentially expensive memory mitigations, mostly meant for system
- // processes, in apps. These may cause app compat problems, use more memory,
- // or reduce performance. While it would be nice to have them for apps,
- // we will have to wait until they are proven out, have more efficient
- // hardware, and/or apply them only to new applications.
- process_disable_memory_mitigations();
}
static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
@@ -1785,6 +1777,14 @@
break;
}
mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level);
+
+ // Avoid heap zero initialization for applications without MTE. Zero init may
+ // cause app compat problems, use more memory, or reduce performance. While it
+ // would be nice to have them for apps, we will have to wait until they are
+ // proven out, have more efficient hardware, and/or apply them only to new
+ // applications.
+ mallopt(M_BIONIC_ZERO_INIT, 0);
+
// Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4acdb16..7c1e1b5f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1748,7 +1748,7 @@
@SystemApi
<p>Not for use by third-party applications. @hide -->
<permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM"
- android:protectionLevel="signature|setup" />
+ android:protectionLevel="signature|privileged" />
<!-- @SystemApi @hide Allows applications to toggle airplane mode.
<p>Not for use by third-party or privileged applications.
@@ -2708,13 +2708,13 @@
shown on top of all other apps.
Allows an application to use
- {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY}
+ {@link android.view.WindowManager.LayoutsParams#setSystemApplicationOverlay(boolean)}
to create overlays that will stay visible, even if another window is requesting overlays to
be hidden through {@link android.view.Window#setHideOverlayWindows(boolean)}.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
- android:protectionLevel="signature|wellbeing"/>
+ android:protectionLevel="signature|recents|wellbeing"/>
<!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
@hide
@@ -5168,6 +5168,11 @@
<permission android:name="android.permission.MANAGE_SEARCH_UI"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the smartspace service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SMARTSPACE"
+ android:protectionLevel="signature" />
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
@@ -5293,7 +5298,8 @@
android:protectionLevel="signature" />
<!-- Allows financial apps to read filtered sms messages.
- Protection level: signature|appop -->
+ Protection level: signature|appop
+ @deprecated The API that used this permission is no longer functional. -->
<permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"
android:protectionLevel="signature|appop" />
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 8746afa..55eaaf6 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Net Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-rugsteunoproepe"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nie aangestuur nie"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> sekondes"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 4daa9e3..3161226 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi ብቻ"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> ምትኬ ጥሪ"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>፡አልተላለፈም"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>፡<xliff:g id="DIALING_NUMBER">{1}</xliff:g> ከ<xliff:g id="TIME_DELAY">{2}</xliff:g> ሰከንዶች በኋላ"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 2f937e0..e91e2b7 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Yalnız Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Yedək Zəng"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönləndirilmədi"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> saniyə sonra"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index b3323b3..a490a6c 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> rezervni način za pozivanje"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunde/i"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 173f9b5..20342e0 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Обаждания през друга SIM карта от <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Не е пренасочено"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> след <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 1c8644e..989e2bb 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – pomoćno pozivanje"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđen"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> za <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index d369134..e9c55d7 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Només Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Trucades alternatives (<xliff:g id="SPN">%s</xliff:g>)"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no s\'ha desviat"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> després de <xliff:g id="TIME_DELAY">{2}</xliff:g> segons"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 7936adc..071bbfd 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Pouze Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Záložní volání"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index ba0ef7f..b760f5b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Kun Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Alternativ løsning til opkald leveret af <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ea56ee2..4f3c8a9 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Μόνο Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Δημιουργία αντιγράφων ασφαλείας κλήσεων"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Δεν προωθήθηκε"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> μετά από <xliff:g id="TIME_DELAY">{2}</xliff:g> δευτερόλεπτα"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 4ee4b8e..c5ff193 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Llamada de copia de seguridad de <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> después de <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 39f8ef4f..108c3db 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Llamadas de reserva de <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> transcurridos <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index faf1b74..70862d4 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Ainult WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – helistamise varuviis"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole suunatud"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi pärast"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 9258b30..6a0e454 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"فقط Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> تماس پشتیبان"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: هدایت نشده"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> پس از <xliff:g id="TIME_DELAY">{2}</xliff:g> ثانیه"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 034ee0b..8e5d9b4 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Vain Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Puheluiden varavaihtoehto"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ei siirretty"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunnin päästä"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 8418604..eadac9e 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Só por wifi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas de <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: non desviada"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> tras <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index f3cf7aa..5f0a8f8 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"केवल वाई-फ़ाई"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> बैक अप कॉलिंग"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> सेकंड के बाद"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index aac0650..e1ae1e2 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Rezervni način telefoniranja"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđeno"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 841553a..3c04bec 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Csak Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> másodlagos hívási lehetőség"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nincs átirányítva"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> másodperc után"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index cddcfe6..bdd2387 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Միայն Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> զանգելու պահեստային տարբերակ"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> վայրկյանից"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 8bd34c3..c84bc13 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Khusus Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Panggilan Cadangan <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak diteruskan"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> setelah <xliff:g id="TIME_DELAY">{2}</xliff:g> detik"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index f9536fe..84611a2 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi eingöngu"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Varasímtöl (<xliff:g id="SPN">%s</xliff:g>)"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ekki áframsent"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> eftir <xliff:g id="TIME_DELAY">{2}</xliff:g> sekúndur"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 409b378..f104d44 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi בלבד"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"אמצעי גיבוי להתקשרות באמצעות <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ללא העברה"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> כעבור <xliff:g id="TIME_DELAY">{2}</xliff:g> שניות"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 0696580..69822ac 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fiのみ"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> 通話のバックアップ"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g> (<xliff:g id="TIME_DELAY">{2}</xliff:g>秒後)"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ae1e6eb..6a7d563 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"მხოლოდ Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> დარეკვის სარეზერვო ხერხი"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: არ არის გადამისამართებული"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> წამის შემდეგ"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index fac878d..d1400b9 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi តែប៉ុណ្ណោះ"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"ការហៅទូរសព្ទបម្រុង <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> ៖ មិនបានបញ្ជូនបន្ត"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> បន្ទាប់ពី <xliff:g id="TIME_DELAY">{2}</xliff:g> វិនាទី"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index afeb1bd..32e3c4d 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ವೈ-ಫೈ ಮಾತ್ರ"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> ಬ್ಯಾಕಪ್ ಕರೆ ಮಾಡುವಿಕೆ"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗಿಲ್ಲ"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> ಸೆಕೆಂಡುಗಳ ನಂತರ <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index acc5a6a..3407431 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi에서만"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> 백업 전화"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: 착신전환 안됨"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g><xliff:g id="TIME_DELAY">{2}</xliff:g>초 후"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index d6ff927..9a84d0a 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi гана"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Кошумча чалуу ыкмасы"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Багытталган эмес"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> секунддан кийин"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 5c65d06..5f72e07 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tik „Wi-Fi“"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – atsarginis skambinimas"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neperadresuota"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 2840429..37e417f 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tikai Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>: zvanu rezerves iespēja"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nav pāradresēts"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pēc <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundes(-ēm)"</string>
diff --git a/core/res/res/values-mcc310-mnc950-si/strings.xml b/core/res/res/values-mcc310-mnc950-si/strings.xml
deleted file mode 100644
index 26fe4ac..0000000
--- a/core/res/res/values-mcc310-mnc950-si/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/* //device/apps/common/assets/res/any/strings.xml
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="615419724607901560">"SIM MM#2 ප්රතිපාදනය නොකරයි"</string>
- <string name="mmcc_illegal_ms" msgid="7801541624846497489">"SIM MM#3 ඉඩ නොදේ"</string>
- <string name="mmcc_illegal_me" msgid="7066936962628406316">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
-</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 3466bca..5666cc2 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Резервен начин на повикување преку <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е препратено"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> по <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 2c44adc..891ac47 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Зөвхөн Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Дуудлагыг нөөцлөх"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: дамжуулагдаагүй"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> секундын дараа"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 128675e..ec0aa5d 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi sahaja"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Panggilan Sandaran"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak dimajukan"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> selepas <xliff:g id="TIME_DELAY">{2}</xliff:g> saat"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 62d2d85..7b4a430fc 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ကြိုးမဲ့အင်တာနက် သာလျှင်"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> အရန် ခေါ်ဆိုမှု"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ထပ်ဆင့်မပို့နိုင်ပါ"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> နောက် <xliff:g id="TIME_DELAY">{2}</xliff:g> စက္ကန့်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index b20111e..4968184 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-reserve for anrop"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> etter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 9ee5e19..f73b382 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tylko Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Zapasowa metoda wykonywania połączeń <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundach"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 0663ec7..693af83 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Somente Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas da <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index ccb7a64..e00034a 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Apenas Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Fazer uma cópia de segurança das chamadas <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não reencaminhado"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 0663ec7..693af83 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Somente Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas da <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index e08cb57..5fd6f1a 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Numai Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Apelare de rezervă prin <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neredirecționată"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> după <xliff:g id="TIME_DELAY">{2}</xliff:g> secunde"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index f800195..d1cd374 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Только Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Резервный способ связи: <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не переадресовано"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> через <xliff:g id="TIME_DELAY">{2}</xliff:g> с."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 322c03c..d102183 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi පමණයි"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> උපස්ථ ඇමතුම"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ඉදිරියට නොයවන ලදි"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: තත්පර <xliff:g id="TIME_DELAY">{2}</xliff:g> ට පසුව <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 7172cab..ffa4b26 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Len Wi‑Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – záložné volanie"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepresmerované"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index af03b61..65037a0 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -149,8 +149,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> резервни начин за позивање"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Није прослеђено"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> након <xliff:g id="TIME_DELAY">{2}</xliff:g> секунде/и"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index b87e12b..ef7a102 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Endast Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> används som reserv för samtal"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Vidarebefordras inte"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 397a3b8..cc82f4e 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi pekee"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>Kupiga Simu Kupitia Mtandao Mbadala"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Haijatumiwa mwingine"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> baada ya sekunde <xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 3c24672..ca90171 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"வைஃபை மட்டும்"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> காப்புப் பிரதி அழைப்பு"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: பகிரப்படவில்லை"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> வினாடிகளுக்குப் பிறகு <xliff:g id="DIALING_NUMBER">{1}</xliff:g> ஐப் பகிர்"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 6012c0a2..5079f23 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi เท่านั้น"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"การสำรองข้อมูลการโทรจาก <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ไม่ได้โอนสาย"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> หลังผ่านไป <xliff:g id="TIME_DELAY">{2}</xliff:g> วินาที"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 9e677c3..81bfe52 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi lang"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup na Pagtawag"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Hindi naipasa"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pagkatapos ng <xliff:g id="TIME_DELAY">{2}</xliff:g> (na) segundo"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index fb23a29..7095bb8 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Yalnızca kablosuz"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Yedek Arama"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönlendirilmedi"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> saniye sonra <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index ecfcb1b..9864b4b 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -150,8 +150,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Лише Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>: резервний спосіб здійснення викликів"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не переслано"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> після <xliff:g id="TIME_DELAY">{2}</xliff:g> сек."</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 0b48dc1..6612cc9 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Faqat Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Zaxiraviy chaqiruv"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yo‘naltirilmadi"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> soniyadan so‘ng"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 438befd..8f8e0bc 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Chỉ Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Gọi điện dự phòng <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> sau <xliff:g id="TIME_DELAY">{2}</xliff:g> giây"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 20048f1..af5f846 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"仅限 WLAN"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>备用通话"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g>秒后<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 544cf1e..65586f7 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"只限 Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"「<xliff:g id="SPN">%s</xliff:g>」備用通話網路"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:尚未轉接"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> 於 <xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後轉接"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index d9b51e0..6facceb 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"只限 Wi-Fi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"「<xliff:g id="SPN">%s</xliff:g>」備用通話網路"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未轉接"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後 <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 029c569..3b22e94 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -148,8 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"I-Wi-Fi kuphela"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
- <skip />
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Ukushaya Ikholi Kwekhopi Yasenqolobaneni"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Akudlulisiwe"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> emuva kwamasekhondi angu-<xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 14f1e0e..07c3adf 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4069,6 +4069,7 @@
<attr name="dial" format="reference"/>
<attr name="hand_hour" format="reference"/>
<attr name="hand_minute" format="reference"/>
+ <attr name="hand_second" format="reference"/>
</declare-styleable>
<declare-styleable name="Button">
</declare-styleable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5e0cda6..01b8efa 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3800,6 +3800,15 @@
-->
<string name="config_defaultSearchUiService" translatable="false"></string>
+ <!-- The package name for the system's smartspace service.
+ This service returns smartspace results.
+
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ If no service with the specified name exists on the device, smartspace will be disabled.
+ Example: "com.android.intelligence/.SmartspaceService"
+-->
+ <string name="config_defaultSmartspaceService" translatable="false"></string>
+
<!-- The package name for the system's speech recognition service.
This service must be trusted, as it can be activated without explicit consent of the user.
Example: "com.android.speech/.RecognitionService"
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9c1c51c..b76ab3a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3059,6 +3059,7 @@
<public name="pathAdvancedPattern" />
<public name="sspAdvancedPattern" />
<public name="fontProviderSystemFontFamily" />
+ <public name="hand_second" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dfccdf4..815330f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3498,6 +3498,7 @@
<java-symbol type="string" name="config_defaultAppPredictionService" />
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="config_defaultSearchUiService" />
+ <java-symbol type="string" name="config_defaultSmartspaceService" />
<java-symbol type="string" name="config_defaultMusicRecognitionService" />
<java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="config_defaultRotationResolverService" />
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
index da6636b..6c28607 100644
--- a/core/tests/GameManagerTests/AndroidManifest.xml
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.graphics.gamemanagertests"
+ package="com.android.app.gamemanagertests"
android:sharedUserId="android.uid.system" >
<application>
@@ -24,7 +24,7 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.graphics.gamemanagertests"
+ android:targetPackage="com.android.app.gamemanagertests"
android:label="Game Manager Tests"/>
</manifest>
diff --git a/core/tests/GameManagerTests/AndroidTest.xml b/core/tests/GameManagerTests/AndroidTest.xml
index bfb9802..43729923 100644
--- a/core/tests/GameManagerTests/AndroidTest.xml
+++ b/core/tests/GameManagerTests/AndroidTest.xml
@@ -25,7 +25,7 @@
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.graphics.gamemanagertests" />
+ <option name="package" value="com.android.app.gamemanagertests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false" />
</test>
diff --git a/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
similarity index 95%
rename from core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java
rename to core/tests/GameManagerTests/src/android/app/GameManagerTests.java
index d861a89..8f50051 100644
--- a/core/tests/GameManagerTests/src/android/graphics/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package android.graphics;
+package android.app;
import static junit.framework.Assert.assertEquals;
-import android.graphics.GameManager.GameMode;
+import android.app.GameManager.GameMode;
import android.util.ArrayMap;
import android.util.Pair;
@@ -31,7 +31,7 @@
import org.junit.runner.RunWith;
/**
- * Unit tests for {@link GameManager}.
+ * Unit tests for {@link android.app.GameManager}.
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index f0b4af6..19458da 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -54,6 +54,32 @@
assertFalse(Signature.areEffectiveMatch(asArray(A, M), asArray(A, B)));
}
+ public void testHashCode_doesNotIncludeFlags() throws Exception {
+ // Some classes rely on the hash code not including the flags / capabilities for the signer
+ // to verify Set membership. This test verifies two signers with the same signature but
+ // different flags have the same hash code.
+ Signature signatureAWithAllCaps = new Signature(A.toCharsString());
+ // There are currently 5 capabilities that can be assigned to a previous signer, although
+ // for the purposes of this test all that matters is that the two flag values are distinct.
+ signatureAWithAllCaps.setFlags(31);
+ Signature signatureAWithNoCaps = new Signature(A.toCharsString());
+ signatureAWithNoCaps.setFlags(0);
+
+ assertEquals(signatureAWithAllCaps.hashCode(), signatureAWithNoCaps.hashCode());
+ }
+
+ public void testEquals_doesNotIncludeFlags() throws Exception {
+ // Similar to above some classes rely on equals only comparing the signature arrays
+ // for equality without including the flags. This test verifies two signers with the
+ // same signature but different flags are still considered equal.
+ Signature signatureAWithAllCaps = new Signature(A.toCharsString());
+ signatureAWithAllCaps.setFlags(31);
+ Signature signatureAWithNoCaps = new Signature(A.toCharsString());
+ signatureAWithNoCaps.setFlags(0);
+
+ assertEquals(signatureAWithAllCaps, signatureAWithNoCaps);
+ }
+
private static Signature[] asArray(Signature... s) {
return s;
}
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
index b319886..341ee37 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
@@ -149,7 +149,7 @@
0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */,
0 /* sources */, 0 /* keyboardType */, null /* keyCharacterMap */,
false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */,
- true /* hasSensor */);
+ true /* hasSensor */, false /* hasBattery */);
assertTrue(d.hasSensor());
return d;
}
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 6955ca8..564103e 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -117,6 +117,61 @@
}
@Test
+ public void testDurationMono() {
+ assertEquals(1, CombinedVibrationEffect.createSynced(
+ VibrationEffect.createOneShot(1, 1)).getDuration());
+ assertEquals(-1, CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration());
+ assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.createSynced(
+ VibrationEffect.createWaveform(
+ new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0)).getDuration());
+ }
+
+ @Test
+ public void testDurationStereo() {
+ assertEquals(6, CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.createOneShot(1, 1))
+ .addVibrator(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+ .combine()
+ .getDuration());
+ assertEquals(-1, CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+ .combine()
+ .getDuration());
+ assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
+ .combine()
+ .getDuration());
+ }
+
+ @Test
+ public void testDurationSequential() {
+ assertEquals(26, CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.createOneShot(10, 10), 10)
+ .addNext(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+ .combine()
+ .getDuration());
+ assertEquals(-1, CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+ .combine()
+ .getDuration());
+ assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(2,
+ VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
+ .combine()
+ .getDuration());
+ }
+
+ @Test
public void testSerializationMono() {
CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 301b491..b0ae9b9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -223,14 +223,6 @@
targetSdk="29">
<new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
</split-permission>
- <split-permission name="android.permission.RECORD_AUDIO"
- targetSdk="31">
- <new-permission name="android.permission.RECORD_BACKGROUND_AUDIO" />
- </split-permission>
- <split-permission name="android.permission.CAMERA"
- targetSdk="31">
- <new-permission name="android.permission.BACKGROUND_CAMERA" />
- </split-permission>
<!-- This is a list of all the libraries available for application
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index c1310a9..4a92cf1 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -693,6 +693,32 @@
throw new IllegalArgumentException("Unrecognized outline?");
}
+ /** @hide */
+ public boolean clearStretch() {
+ return nClearStretch(mNativeRenderNode);
+ }
+
+ /** @hide */
+ public boolean stretch(float left, float top, float right, float bottom,
+ float vecX, float vecY, float maxStretchAmount) {
+ if (1.0 < vecX || vecX < -1.0) {
+ throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX);
+ }
+ if (1.0 < vecY || vecY < -1.0) {
+ throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
+ }
+ if (top <= bottom || right <= left) {
+ throw new IllegalArgumentException(
+ "Stretch region must not be empty, got "
+ + new RectF(left, top, right, bottom).toString());
+ }
+ if (maxStretchAmount <= 0.0f) {
+ throw new IllegalArgumentException(
+ "The max stretch amount must be >0, got " + maxStretchAmount);
+ }
+ return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount);
+ }
+
/**
* Checks if the RenderNode has a shadow. That is, if the combination of {@link #getElevation()}
* and {@link #getTranslationZ()} is greater than zero, there is an {@link Outline} set with
@@ -1638,6 +1664,13 @@
private static native boolean nSetOutlineNone(long renderNode);
@CriticalNative
+ private static native boolean nClearStretch(long renderNode);
+
+ @CriticalNative
+ private static native boolean nStretch(long renderNode, float left, float top, float right,
+ float bottom, float vecX, float vecY, float maxStretch);
+
+ @CriticalNative
private static native boolean nHasShadow(long renderNode);
@CriticalNative
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index f41b608..ae9f8664 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -19,9 +19,9 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
-import com.android.org.bouncycastle.util.io.pem.PemObject;
-import com.android.org.bouncycastle.util.io.pem.PemReader;
-import com.android.org.bouncycastle.util.io.pem.PemWriter;
+import com.android.internal.org.bouncycastle.util.io.pem.PemObject;
+import com.android.internal.org.bouncycastle.util.io.pem.PemReader;
+import com.android.internal.org.bouncycastle.util.io.pem.PemWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 4a67135..e19d88c 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -45,8 +45,8 @@
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import java.io.ByteArrayInputStream;
import java.io.IOException;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 6ad8d2c..334b111 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -26,24 +26,24 @@
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
-import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.ASN1Integer;
-import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import com.android.org.bouncycastle.asn1.DERBitString;
-import com.android.org.bouncycastle.asn1.DERNull;
-import com.android.org.bouncycastle.asn1.DERSequence;
-import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import com.android.org.bouncycastle.asn1.x509.Certificate;
-import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import com.android.org.bouncycastle.asn1.x509.TBSCertificate;
-import com.android.org.bouncycastle.asn1.x509.Time;
-import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import com.android.org.bouncycastle.jce.X509Principal;
-import com.android.org.bouncycastle.jce.provider.X509CertificateObject;
-import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
+import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.ASN1Integer;
+import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import com.android.internal.org.bouncycastle.asn1.DERBitString;
+import com.android.internal.org.bouncycastle.asn1.DERNull;
+import com.android.internal.org.bouncycastle.asn1.DERSequence;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import com.android.internal.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import com.android.internal.org.bouncycastle.asn1.x509.Certificate;
+import com.android.internal.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import com.android.internal.org.bouncycastle.asn1.x509.TBSCertificate;
+import com.android.internal.org.bouncycastle.asn1.x509.Time;
+import com.android.internal.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import com.android.internal.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import com.android.internal.org.bouncycastle.jce.X509Principal;
+import com.android.internal.org.bouncycastle.jce.provider.X509CertificateObject;
+import com.android.internal.org.bouncycastle.x509.X509V3CertificateGenerator;
import libcore.util.EmptyArray;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 164bc86..75ac61a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -363,6 +363,11 @@
}
}
+ if (response.iSecurityLevel == null) {
+ // This seems to be a pure certificate entry, nothing to return here.
+ return null;
+ }
+
Integer keymasterAlgorithm = null;
// We just need one digest for the algorithm name
int keymasterDigest = -1;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 5e7f648..07169ce 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -490,7 +490,7 @@
int[] keymasterEncryptionPaddings =
KeyProperties.EncryptionPadding.allToKeymaster(
spec.getEncryptionPaddings());
- if (((spec.getPurposes() & KeyProperties.PURPOSE_DECRYPT) != 0)
+ if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
&& (spec.isRandomizedEncryptionRequired())) {
for (int keymasterPadding : keymasterEncryptionPaddings) {
if (!KeymasterUtils
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 1da72f8..04d1264 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -89,6 +89,7 @@
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
mAnimationController.removeAnimator(animator.getToken());
if (mAnimationController.isAnimatorsConsumed()) {
+ resetWindowsOffsetInternal(animator.getTransitionDirection());
finishOffset(animator.getDestinationOffset(),
animator.getTransitionDirection());
}
@@ -99,6 +100,7 @@
OneHandedAnimationController.OneHandedTransitionAnimator animator) {
mAnimationController.removeAnimator(animator.getToken());
if (mAnimationController.isAnimatorsConsumed()) {
+ resetWindowsOffsetInternal(animator.getTransitionDirection());
finishOffset(animator.getDestinationOffset(),
animator.getTransitionDirection());
}
@@ -205,6 +207,16 @@
applyTransaction(wct);
}
+ private void resetWindowsOffsetInternal(
+ @OneHandedAnimationController.TransitionDirection int td) {
+ if (td == TRANSITION_DIRECTION_TRIGGER) {
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ resetWindowsOffset(wct);
+ applyTransaction(wct);
+ }
+
private void resetWindowsOffset(WindowContainerTransaction wct) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 4b118f1..a57eee8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -212,8 +212,8 @@
}
@Nullable
- Size getEstimatedMenuSize() {
- return mPipMenuView == null ? null : mPipMenuView.getEstimatedMenuSize();
+ Size getEstimatedMinMenuSize() {
+ return mPipMenuView == null ? null : mPipMenuView.getEstimatedMinMenuSize();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 48942b6..2df3e2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -367,15 +367,17 @@
}
/**
- * @return estimated {@link Size} for which the width is based on number of actions and
- * height based on the height of expand button + top and bottom action bar.
+ * @return Estimated minimum {@link Size} to hold the actions.
+ * See also {@link #updateActionViews(Rect)}
*/
- Size getEstimatedMenuSize() {
- final int pipActionSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_action_size);
- final int width = mActions.size() * pipActionSize;
- final int height = pipActionSize * 2 + mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_expand_action_size);
+ Size getEstimatedMinMenuSize() {
+ final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
+ // the minimum width would be (2 * pipActionSize) since we have settings and dismiss button
+ // on the top action container.
+ final int width = Math.max(2, mActions.size()) * pipActionSize;
+ final int height = getResources().getDimensionPixelSize(R.dimen.pip_expand_action_size)
+ + getResources().getDimensionPixelSize(R.dimen.pip_action_padding)
+ + getResources().getDimensionPixelSize(R.dimen.pip_expand_container_edge_margin);
return new Size(width, height);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 4df7cef..fd4ea61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -27,7 +27,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
-import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Choreographer;
@@ -62,6 +61,7 @@
private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
+ private static final int UNSTASH_DURATION = 250;
private static final int LEAVE_PIP_DURATION = 300;
private static final int SHIFT_DURATION = 300;
@@ -482,6 +482,13 @@
}
/**
+ * Animates the PiP from stashed state into un-stashed, popping it out from the edge.
+ */
+ void animateToUnStashedBounds(Rect unstashedBounds) {
+ resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION);
+ }
+
+ /**
* Animates the PiP to offset it from the IME or shelf.
*/
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 128d13c..3cb3ae8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.pip.phone;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
@@ -30,7 +32,6 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.Handler;
import android.provider.DeviceConfig;
import android.util.Log;
import android.util.Size;
@@ -62,9 +63,8 @@
*/
public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
-
- private static final float STASH_MINIMUM_VELOCITY_X = 3000.f;
private static final float MINIMUM_SIZE_PERCENT = 0.4f;
+ private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
// Allow PIP to resize to a slightly bigger state upon touch
private final boolean mEnableResize;
@@ -87,6 +87,8 @@
*/
private boolean mEnableStash = true;
+ private float mStashVelocityThreshold;
+
// The reference inset bounds, used to determine the dismiss fraction
private final Rect mInsetBounds = new Rect();
private int mExpandedShortestEdgeSize;
@@ -176,9 +178,17 @@
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
- () -> mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
- mPipBoundsState.getBounds(), true /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle()),
+ () -> {
+ if (mPipBoundsState.isStashed()) {
+ animateToUnStashedState();
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ } else {
+ mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+ mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+ willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ },
menuController::hideMenu,
mainExecutor);
@@ -205,6 +215,19 @@
PIP_STASHING, /* defaultValue = */ true);
}
});
+ mStashVelocityThreshold = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mainExecutor,
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
+ mStashVelocityThreshold = properties.getFloat(
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ }
+ });
}
private void reloadResources() {
@@ -710,6 +733,17 @@
mSavedSnapFraction = -1f;
}
+ private void animateToUnStashedState() {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
+ final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
+ unStashedBounds.left = onLeftEdge ? mInsetBounds.left
+ : mInsetBounds.right - pipBounds.width();
+ unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
+ : mInsetBounds.right;
+ mMotionHelper.animateToUnStashedBounds(unStashedBounds);
+ }
+
/**
* @return the motion helper.
*/
@@ -773,7 +807,7 @@
}
if (touchState.startedDragging()) {
- mPipBoundsState.setStashed(PipBoundsState.STASH_TYPE_NONE);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
mSavedSnapFraction = -1f;
mPipDismissTargetHandler.showDismissTargetMaybe();
}
@@ -826,14 +860,8 @@
// Reset the touch state on up before the fling settles
mTouchState.reset();
- // If user flings the PIP window above the minimum velocity, stash PIP.
- // Only allow stashing to the edge if the user starts dragging the PIP from that
- // edge.
if (mEnableStash && !mPipBoundsState.isStashed()
- && ((vel.x > STASH_MINIMUM_VELOCITY_X
- && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
- || (vel.x < -STASH_MINIMUM_VELOCITY_X
- && mDownSavedFraction > 3f && mDownSavedFraction < 4f))) {
+ && shouldStash(vel, getPossiblyMotionBounds())) {
mMotionHelper.stashToEdge(vel.x, this::stashEndAction /* endAction */);
} else {
mMotionHelper.flingToSnapTarget(vel.x, vel.y,
@@ -847,24 +875,29 @@
&& mPipBoundsState.getBounds().height()
< mPipBoundsState.getMaxSize().y;
if (toExpand) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
animateToMaximizedState(null);
} else {
- animateToMinimizedState();
+ animateToUnexpandedState(getUserResizeBounds());
}
- mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
} else {
// Expand to fullscreen if this is a double tap
// the PiP should be frozen until the transition ends
setTouchEnabled(false);
mMotionHelper.expandLeavePip();
}
- } else if (mMenuState != MENU_STATE_FULL && !mPipBoundsState.isStashed()) {
+ } else if (mMenuState != MENU_STATE_FULL) {
if (!mTouchState.isWaitingForDoubleTap()) {
- // User has stalled long enough for this not to be a drag or a double tap, just
- // expand the menu
- mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
- true /* allowMenuTimeout */, willResizeMenu(),
- shouldShowResizeHandle());
+ if (mPipBoundsState.isStashed()) {
+ animateToUnStashedState();
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ } else {
+ // User has stalled long enough for this not to be a drag or a double tap,
+ // just expand the menu
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
} else {
// Next touch event _may_ be the second tap for the double-tap, schedule a
// fallback runnable to trigger the menu if no touch event occurs before the
@@ -895,6 +928,26 @@
mPipExclusionBoundsChangeListener.get().accept(new Rect());
}
}
+
+ private boolean shouldStash(PointF vel, Rect motionBounds) {
+ // If user flings the PIP window above the minimum velocity, stash PIP.
+ // Only allow stashing to the edge if the user starts dragging the PIP from the
+ // opposite edge.
+ final boolean stashFromFlingToEdge = ((vel.x < -mStashVelocityThreshold
+ && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
+ || (vel.x > mStashVelocityThreshold
+ && mDownSavedFraction > 3f && mDownSavedFraction < 4f));
+
+ // If User releases the PIP window while it's out of the display bounds, put
+ // PIP into stashed mode.
+ final int offset = motionBounds.width() / 2;
+ final boolean stashFromDroppingOnEdge =
+ (motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset
+ || motionBounds.left
+ < mPipBoundsState.getDisplayBounds().left - offset);
+
+ return stashFromFlingToEdge || stashFromDroppingOnEdge;
+ }
}
void setPipExclusionBoundsChangeListener(Consumer<Rect> pipExclusionBoundsChangeListener) {
@@ -932,14 +985,14 @@
if (!mEnableResize) {
return false;
}
- final Size estimatedMenuSize = mMenuController.getEstimatedMenuSize();
- if (estimatedMenuSize == null) {
+ final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
+ if (estimatedMinMenuSize == null) {
Log.wtf(TAG, "Failed to get estimated menu size");
return false;
}
final Rect currentBounds = mPipBoundsState.getBounds();
- return currentBounds.width() < estimatedMenuSize.getWidth()
- || currentBounds.height() < estimatedMenuSize.getHeight();
+ return currentBounds.width() < estimatedMinMenuSize.getWidth()
+ || currentBounds.height() < estimatedMinMenuSize.getHeight();
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index ccfdce6..24b0f30 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -18,10 +18,11 @@
import android.graphics.Region
import android.view.Surface
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.DOCKED_STACK_DIVIDER
@JvmOverloads
fun LayersAssertionBuilder.appPairsDividerIsVisible(
@@ -29,7 +30,7 @@
enabled: Boolean = bugId == 0
) {
end("appPairsDividerIsVisible", bugId, enabled) {
- this.isVisible(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ this.isVisible(APP_PAIR_SPLIT_DIVIDER)
}
}
@@ -39,7 +40,7 @@
enabled: Boolean = bugId == 0
) {
end("appPairsDividerIsInVisible", bugId, enabled) {
- this.notExists(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ this.notExists(APP_PAIR_SPLIT_DIVIDER)
}
}
@@ -107,7 +108,7 @@
enabled: Boolean = bugId == 0
) {
end("PrimaryAppBounds", bugId, enabled) {
- val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation))
}
}
@@ -120,7 +121,7 @@
enabled: Boolean = bugId == 0
) {
end("SecondaryAppBounds", bugId, enabled) {
- val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation))
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 3953c1c..89bbdb0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -135,12 +135,4 @@
throw RuntimeException(e)
}
}
-
- companion object {
- const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
- const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
- const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
- const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
- const val IMAGE_WALLPAPER = "ImageWallpaper"
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index 5cbfec6..257350b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -21,12 +21,12 @@
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.APP_PAIR_SPLIT_DIVIDER
import com.android.wm.shell.flicker.appPairsDividerIsVisible
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index f57a000..0b001f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -21,12 +21,12 @@
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.APP_PAIR_SPLIT_DIVIDER
import com.android.wm.shell.flicker.appPairsDividerIsInvisible
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
deleted file mode 100644
index dea5c30..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.canSplitScreen
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickstep
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
-import org.junit.Assert
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test SplitScreen launch.
- * To run this test: `atest WMShellFlickerTests:EnterLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- private val splitScreenSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- val testLaunchActivity = "launch_splitScreen_test_activity"
- withTestName {
- testLaunchActivity
- }
- setup {
- eachRun {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- }
- }
- teardown {
- eachRun {
- if (uiDevice.isInSplitScreen()) {
- uiDevice.exitSplitScreen()
- }
- splitScreenApp.exit()
- secondaryApp.exit()
- nonResizeableApp.exit()
- }
- }
- assertions {
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
- }
- }
-
- @Test
- fun testEnterSplitScreen_dockActivity() {
- val testTag = "testEnterSplitScreen_dockActivity"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- }
- assertions {
- layersTrace {
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- dockedStackDividerBecomesVisible()
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- LIVE_WALLPAPER_PACKAGE_NAME)
- )
- }
- windowManagerTrace {
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @Test
- fun testEnterSplitScreen_launchToSide() {
- val testTag = "testEnterSplitScreen_launchToSide"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- secondaryApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- splitScreenApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- dockedStackSecondaryBoundsIsVisible(
- rotation, secondaryApp.defaultWindowName, 169271943)
- dockedStackDividerBecomesVisible()
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
- }
- windowManagerTrace {
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- .isVisible(secondaryApp.defaultWindowName)
- }
- visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName))
- }
- }
- }
- }
-
- @FlakyTest(bugId = 173875043)
- @Test
- fun testNonResizeableNotDocked() {
- val testTag = "testNonResizeableNotDocked"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- nonResizeableApp.launchViaIntent()
- uiDevice.openQuickstep()
- if (uiDevice.canSplitScreen()) {
- Assert.fail("Non-resizeable app should not enter split screen")
- }
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName)
- )
- }
- windowManagerTrace {
- end("appWindowIsVisible") {
- isInvisible(nonResizeableApp.defaultWindowName)
- }
- visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName))
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
new file mode 100644
index 0000000..5374bd9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.WALLPAPER_TITLE
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open activity and dock to primary split screen
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenDockActivity`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenDockActivity(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testLegacySplitScreenDockActivity", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ }
+ assertions {
+ layersTrace {
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 169271943)
+ dockedStackDividerBecomesVisible()
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME,
+ WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+ splitScreenApp.defaultWindowName),
+ bugId = 178531736
+ )
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME,
+ WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+ splitScreenApp.defaultWindowName),
+ bugId = 178531736
+ )
+ end("appWindowIsVisible") {
+ isVisible(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
new file mode 100644
index 0000000..d750403
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open activity to primary split screen and dock secondary activity to side
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenLaunchToSide`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenLaunchToSide(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testLegacySplitScreenLaunchToSide", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ secondaryApp.reopenAppFromOverview()
+ }
+ assertions {
+ layersTrace {
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 169271943)
+ dockedStackSecondaryBoundsIsVisible(
+ configuration.startRotation,
+ secondaryApp.defaultWindowName, bugId = 169271943)
+ dockedStackDividerBecomesVisible()
+ // TODO(b/178447631) Remove Splash Screen from white list when flicker lib
+ // add a wait for splash screen be gone
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
+ splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
+ splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName)
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
new file mode 100644
index 0000000..e361923
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.WALLPAPER_TITLE
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.canSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickstep
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open non-resizable activity will auto exit split screen mode
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNonResizableNotDock`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 173875043)
+class EnterSplitScreenNonResizableNotDock(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testLegacySplitScreenNonResizeableActivityNotDock", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ device.openQuickstep()
+ if (device.canSplitScreen()) {
+ Assert.fail("Non-resizeable app should not enter split screen")
+ }
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsInvisible()
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME,
+ SPLASH_SCREEN_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(WALLPAPER_TITLE,
+ LAUNCHER_PACKAGE_NAME,
+ SPLASH_SCREEN_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName)
+ )
+ end("appWindowIsVisible") {
+ isInvisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
new file mode 100644
index 0000000..6aed83f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open resizeable activity split in primary, and drag divider to bottom exit split screen
+ * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottom`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitLegacySplitScreenFromBottom(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testExitLegacySplitScreenFromBottom", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ device.exitSplitScreenFromBottom()
+ }
+ assertions {
+ layersTrace {
+ layerBecomesInvisible(DOCKED_STACK_DIVIDER)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesInVisible(secondaryApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName)
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
deleted file mode 100644
index a3b8673..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerTestRunner
-import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottomTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitLegacySplitScreenFromBottomTest(
- testSpec: FlickerTestRunnerFactory.TestSpec
-) : FlickerTestRunner(testSpec) {
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
- // TODO(b/162923992) Use of multiple segments of flicker spec for testing
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)) {
- configuration ->
- withTestName {
- buildTestTag("exitSplitScreenFromBottom", configuration)
- }
- repeat { configuration.repetitions }
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview()
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen()
- device.waitForIdle()
- this.setRotation(configuration.endRotation)
- }
- }
- teardown {
- eachRun {
- if (device.isInSplitScreen()) {
- device.exitSplitScreen()
- }
- splitScreenApp.exit()
- }
- }
- transitions {
- device.exitSplitScreenFromBottom()
- }
- assertions {
- windowManagerTrace {
- all("isNotEmpty") { isNotEmpty() }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
deleted file mode 100644
index 701b0d0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.util.Rational
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.resizeSplitScreen
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test exit SplitScreen mode.
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- private val splitScreenSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- val testLaunchActivity = "launch_splitScreen_test_activity"
- withTestName {
- testLaunchActivity
- }
- setup {
- eachRun {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- secondaryApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- }
- }
- teardown {
- eachRun {
- splitScreenApp.exit()
- secondaryApp.exit()
- }
- }
- assertions {
- windowManagerTrace {
- visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
- layersTrace {
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME))
- }
- }
- }
-
- @Test
- fun testEnterSplitScreen_exitPrimarySplitScreenMode() {
- val testTag = "testEnterSplitScreen_exitPrimarySplitScreenMode"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- uiDevice.exitSplitScreen()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- layerBecomesInvisible(splitScreenApp.defaultWindowName)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsInvisible") {
- isInvisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @Test
- @FlakyTest(bugId = 172811376)
- fun testEnterSplitScreen_exitPrimary_showSecondaryAppFullScreen() {
- val testTag = "testEnterSplitScreen_exitPrimary_showSecondaryAppFullScreen"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.reopenAppFromOverview()
- uiDevice.resizeSplitScreen(startRatio)
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- private val startRatio = Rational(1, 3)
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
new file mode 100644
index 0000000..59f6aaf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dock activity to primary split screen, and open secondary to side, exit primary split
+ * and test secondary activity become full screen.
+ * To run this test: `atest WMShellFlickerTests:ExitPrimarySplitScreenShowSecondaryFullscreen`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitPrimarySplitScreenShowSecondaryFullscreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testExitPrimarySplitScreenShowSecondaryFullscreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ secondaryApp.reopenAppFromOverview()
+ // TODO(b/175687842) Can not find Split screen divider, use exit() instead
+ splitScreenApp.exit()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsInvisible(bugId = 175687842)
+ layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
similarity index 60%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 4dcbdff..03b6edf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -52,13 +52,13 @@
/**
* Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncherTest`
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncher`
*/
@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LegacySplitScreenToLauncherTest(
+class LegacySplitScreenToLauncher(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object {
@@ -67,68 +67,68 @@
fun getParams(): Collection<Array<Any>> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage
+ .launcherStrategy.supportedLauncherPackage
val testApp = SimpleAppHelper(instrumentation)
// b/161435597 causes the test not to work on 90 degrees
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
- withTestName {
- buildTestTag("splitScreenToLauncher", configuration)
+ withTestName {
+ buildTestTag("splitScreenToLauncher", configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
}
- repeat { configuration.repetitions }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview()
- }
- eachRun {
- testApp.launchViaIntent(wmHelper)
- this.setRotation(configuration.endRotation)
- device.launchSplitScreen()
- device.waitForIdle()
- }
+ eachRun {
+ testApp.launchViaIntent(wmHelper)
+ this.setRotation(configuration.endRotation)
+ device.launchSplitScreen()
+ device.waitForIdle()
}
- teardown {
- eachRun {
- testApp.exit()
- }
- test {
- if (device.isInSplitScreen()) {
- device.exitSplitScreen()
- }
- }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
}
- transitions {
- device.exitSplitScreen()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
-
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(configuration.endRotation)
- navBarLayerRotatesAndScales(configuration.endRotation)
- statusBarLayerRotatesScales(configuration.endRotation)
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName))
-
- // b/161435597 causes the test not to work on 90 degrees
- dockedStackDividerBecomesInvisible()
-
- layerBecomesInvisible(testApp.getPackage())
- }
-
- eventLog {
- focusDoesNotChange(bugId = 151179149)
+ test {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
}
}
}
+ transitions {
+ device.exitSplitScreen()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.endRotation)
+ navBarLayerRotatesAndScales(configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.endRotation)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(launcherPackageName))
+
+ // b/161435597 causes the test not to work on 90 degrees
+ dockedStackDividerBecomesInvisible()
+
+ layerBecomesInvisible(testApp.getPackage())
+ }
+
+ eventLog {
+ focusDoesNotChange(bugId = 151179149)
+ }
+ }
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
new file mode 100644
index 0000000..328ff88
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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/LICENSE2.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.wm.shell.flicker.legacysplitscreen
+
+import android.app.Instrumentation
+import android.os.Bundle
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.view.Surface
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.startRotation
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+
+abstract class LegacySplitScreenTransition(
+ protected val instrumentation: Instrumentation
+) {
+ internal val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
+ internal val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+ internal val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
+ internal val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
+ .launcherStrategy.supportedLauncherPackage
+ internal val LIVE_WALLPAPER_PACKAGE_NAME =
+ "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
+ internal val LETTERBOX_NAME = "Letterbox"
+ internal val TOAST_NAME = "Toast"
+ internal val SPLASH_SCREEN_NAME = "Splash Screen"
+
+ internal open val defaultTransitionSetup: FlickerBuilder.(Bundle) -> Unit
+ get() = { configuration ->
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
+ secondaryApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ splitScreenApp.exit()
+ secondaryApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ }
+
+ internal open val cleanSetup: FlickerBuilder.(Bundle) -> Unit
+ get() = { configuration ->
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ nonResizeableApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ }
+
+ internal open val customRotateSetup: FlickerBuilder.(Bundle) -> Unit
+ get() = { configuration ->
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
+ secondaryApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ }
+ }
+ teardown {
+ eachRun {
+ splitScreenApp.exit()
+ secondaryApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
new file mode 100644
index 0000000..42c7b7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non resizable activity in split screen mode will trigger exit split screen mode
+ * (Non resizable activity launch via recent overview)
+ * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableDismissInLegacySplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testNonResizableDismissInLegacySplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen()
+ nonResizeableApp.reopenAppFromOverview()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ assertions {
+ layersTrace {
+ layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
+ LETTERBOX_NAME, TOAST_NAME,
+ splitScreenApp.defaultWindowName,
+ nonResizeableApp.defaultWindowName),
+ bugId = 178447631
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
+ LETTERBOX_NAME, TOAST_NAME,
+ splitScreenApp.defaultWindowName,
+ nonResizeableApp.defaultWindowName)
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, cleanSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
deleted file mode 100644
index 6fca580..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableDismissInLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-
- @Test
- fun testNonResizableDismissInLegacySplitScreenTest() {
- val testTag = "testNonResizableDismissInLegacySplitScreenTest"
-
- runWithFlicker(transitionSetup) {
- withTestName { testTag }
- repeat { SplitScreenHelper.TEST_REPETITIONS }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen()
- nonResizeableApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
- }
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName, LETTER_BOX_NAME,
- TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME),
- bugId = 178447631
- )
- }
- windowManagerTrace {
- end("nonResizeableAppWindowIsVisible") {
- isVisible(nonResizeableApp.defaultWindowName)
- .isInvisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
new file mode 100644
index 0000000..5b8ec1e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non resizable activity in split screen mode will trigger exit split screen mode
+ * (Non resizable activity launch via intent)
+ * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableLaunchInLegacySplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testNonResizableLaunchInLegacySplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen()
+ nonResizeableApp.launchViaIntent(wmHelper)
+ wmHelper.waitForAppTransitionIdle()
+ }
+ assertions {
+ layersTrace {
+ layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(DOCKED_STACK_DIVIDER,
+ LAUNCHER_PACKAGE_NAME,
+ LETTERBOX_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName)
+ )
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(DOCKED_STACK_DIVIDER,
+ LAUNCHER_PACKAGE_NAME,
+ LETTERBOX_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName)
+ )
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, cleanSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
deleted file mode 100644
index deae41f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableLaunchInLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-
- @Test
- fun testNonResizableLaunchInLegacySplitScreenTest() {
- val testTag = "testNonResizableLaunchInLegacySplitScreenTest"
-
- runWithFlicker(transitionSetup) {
- withTestName { testTag }
- repeat { SplitScreenHelper.TEST_REPETITIONS }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen()
- nonResizeableApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
- }
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName, LETTER_BOX_NAME,
- TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME),
- bugId = 178447631
- )
- }
- windowManagerTrace {
- end("nonResizeableAppWindowIsVisible") {
- isVisible(nonResizeableApp.defaultWindowName)
- .isInvisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
new file mode 100644
index 0000000..c802ffe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppToLegacySplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val wmHelper = WindowManagerStateHelper()
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testOpenAppToLegacySplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ assertions {
+ windowManagerTrace {
+ visibleWindowsShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName),
+ bugId = 178447631)
+ appWindowBecomesVisible(splitScreenApp.getPackage())
+ }
+
+ layersTrace {
+ noUncoveredRegions(configuration.startRotation, enabled = false)
+ statusBarLayerIsAlwaysVisible()
+ appPairsDividerBecomesVisible()
+ layerBecomesVisible(splitScreenApp.getPackage())
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName),
+ bugId = 178447631)
+ }
+
+ eventLog {
+ focusChanges(splitScreenApp.`package`,
+ "recents_animation_input_consumer", "NexusLauncherActivity",
+ bugId = 151179149)
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, defaultTransitionSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
deleted file mode 100644
index 6200a69..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppToLegacySplitScreenTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- @Test
- fun OpenAppToLegacySplitScreenTest() {
- val testTag = "OpenAppToLegacySplitScreenTest"
- val helper = WindowManagerStateHelper()
- runWithFlicker(transitionSetup) {
- withTestName { testTag }
- repeat { SplitScreenHelper.TEST_REPETITIONS }
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.pressHome()
- this.setRotation(rotation)
- }
- }
- transitions {
- device.launchSplitScreen()
- helper.waitForAppTransitionIdle()
- }
- assertions {
- windowManagerTrace {
- visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- LETTER_BOX_NAME)
- )
- appWindowBecomesVisible(splitScreenApp.getPackage())
- }
-
- layersTrace {
- navBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation, enabled = false)
- statusBarLayerIsAlwaysVisible()
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- LETTER_BOX_NAME))
- appPairsDividerBecomesVisible()
- layerBecomesVisible(splitScreenApp.getPackage())
- }
-
- eventLog {
- focusChanges(splitScreenApp.`package`,
- "recents_animation_input_consumer", "NexusLauncherActivity",
- bugId = 151179149)
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- // TODO(b/161435597) causes the test not to work on 90 degrees
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
similarity index 99%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 95c1c16..54a37d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -58,7 +58,7 @@
/**
* Test split screen resizing window transitions.
- * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreenTest`
+ * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreen`
*
* Currently it runs only in 0 degrees because of b/156100803
*/
@@ -67,7 +67,7 @@
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 159096424)
-class ResizeLegacySplitScreenTest(
+class ResizeLegacySplitScreen(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
new file mode 100644
index 0000000..214269e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dock activity to primary split screen and rotate
+ * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppAndEnterSplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateOneLaunchedAppAndEnterSplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testRotateOneLaunchedAppAndEnterSplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ device.launchSplitScreen()
+ this.setRotation(configuration.startRotation)
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsVisible(bugId = 175687842)
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 175687842)
+ navBarLayerRotatesAndScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ statusBarLayerRotatesScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, customRotateSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
new file mode 100644
index 0000000..4290c92
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Rotate
+ * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppInSplitScreenMode`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateOneLaunchedAppInSplitScreenMode(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testRotateOneLaunchedAppInSplitScreenMode", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ this.setRotation(configuration.startRotation)
+ device.launchSplitScreen()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsVisible(bugId = 175687842)
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 175687842)
+ navBarLayerRotatesAndScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ statusBarLayerRotatesScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, customRotateSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt
deleted file mode 100644
index 07571c3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RotateOneLaunchedAppTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- private val splitScreenRotationSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- val testSetupRotation = "testSetupRotation"
- withTestName {
- testSetupRotation
- }
- setup {
- test {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- }
- }
- teardown {
- eachRun {
- if (uiDevice.isInSplitScreen()) {
- uiDevice.exitSplitScreen()
- }
- setRotation(Surface.ROTATION_0)
- splitScreenApp.exit()
- secondaryApp.exit()
- }
- }
- }
-
- @Test
- fun testRotateInSplitScreenMode() {
- val testTag = "testEnterSplitScreen_launchToSide"
- runWithFlicker(splitScreenRotationSetup) {
- withTestName { testTag }
- repeat {
- SplitScreenHelper.TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- setRotation(rotation)
- }
- assertions {
- layersTrace {
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
- dockedStackDividerIsVisible()
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @Test
- fun testRotateAndEnterSplitScreenMode() {
- val testTag = "testRotateAndEnterSplitScreenMode"
- runWithFlicker(splitScreenRotationSetup) {
- withTestName { testTag }
- repeat {
- SplitScreenHelper.TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.launchViaIntent()
- setRotation(rotation)
- uiDevice.launchSplitScreen()
- }
- assertions {
- layersTrace {
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
- dockedStackDividerIsVisible()
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_90, Surface.ROTATION_270)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
new file mode 100644
index 0000000..4095b9a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppAndEnterSplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateTwoLaunchedAppAndEnterSplitScreen(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testRotateTwoLaunchedAppAndEnterSplitScreen", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ this.setRotation(configuration.startRotation)
+ device.launchSplitScreen()
+ secondaryApp.reopenAppFromOverview()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsVisible(bugId = 175687842)
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, 175687842)
+ dockedStackSecondaryBoundsIsVisible(
+ configuration.startRotation,
+ secondaryApp.defaultWindowName, bugId = 175687842)
+ navBarLayerRotatesAndScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ statusBarLayerRotatesScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, customRotateSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
new file mode 100644
index 0000000..aebf606
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppInSplitScreenMode`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateTwoLaunchedAppInSplitScreenMode(
+ testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+ companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ withTestName {
+ buildTestTag("testRotateTwoLaunchedAppInSplitScreenMode", configuration)
+ }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ setup {
+ eachRun {
+ device.launchSplitScreen()
+ splitScreenApp.reopenAppFromOverview()
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ transitions {
+ this.setRotation(configuration.startRotation)
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsVisible(bugId = 175687842)
+ dockedStackPrimaryBoundsIsVisible(
+ configuration.startRotation,
+ splitScreenApp.defaultWindowName, bugId = 175687842)
+ dockedStackSecondaryBoundsIsVisible(
+ configuration.startRotation,
+ secondaryApp.defaultWindowName, bugId = 175687842)
+ navBarLayerRotatesAndScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ statusBarLayerRotatesScales(
+ configuration.startRotation,
+ configuration.endRotation, bugId = 169271943)
+ }
+ windowManagerTrace {
+ appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ }
+ }
+ return FlickerTestRunnerFactory.getInstance().buildTest(
+ instrumentation, customRotateSetup, testSpec,
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt
deleted file mode 100644
index d8014d3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RotateTwoLaunchedAppTest(
- rotationName: String,
- rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
- private val splitScreenRotationSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- val testSetupRotation = "testSetupRotation"
- withTestName {
- testSetupRotation
- }
- setup {
- test {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- }
- }
- teardown {
- eachRun {
- if (uiDevice.isInSplitScreen()) {
- uiDevice.exitSplitScreen()
- }
- setRotation(Surface.ROTATION_0)
- splitScreenApp.exit()
- secondaryApp.exit()
- }
- }
- }
-
- @Test
- fun testRotateInSplitScreenMode() {
- val testTag = "testRotateInSplitScreenMode"
- runWithFlicker(splitScreenRotationSetup) {
- withTestName { testTag }
- repeat {
- SplitScreenHelper.TEST_REPETITIONS
- }
- transitions {
- secondaryApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- splitScreenApp.reopenAppFromOverview()
- setRotation(rotation)
- }
- assertions {
- layersTrace {
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
- dockedStackDividerIsVisible()
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- dockedStackSecondaryBoundsIsVisible(
- rotation, secondaryApp.defaultWindowName, 169271943)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- .isVisible(secondaryApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @FlakyTest(bugId = 173875043)
- @Test
- fun testRotateAndEnterSplitScreenMode() {
- val testTag = "testRotateAndEnterSplitScreenMode"
- runWithFlicker(splitScreenRotationSetup) {
- withTestName { testTag }
- repeat {
- SplitScreenHelper.TEST_REPETITIONS
- }
- transitions {
- secondaryApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- setRotation(rotation)
- uiDevice.launchSplitScreen()
- splitScreenApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
- dockedStackDividerIsVisible()
- dockedStackPrimaryBoundsIsVisible(
- rotation, splitScreenApp.defaultWindowName, 169271943)
- dockedStackSecondaryBoundsIsVisible(
- rotation, secondaryApp.defaultWindowName, 169271943)
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- end("appWindowIsVisible") {
- isVisible(splitScreenApp.defaultWindowName)
- .isVisible(secondaryApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_90, Surface.ROTATION_270)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
deleted file mode 100644
index 01db4ed6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.NonRotationTestBase
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-
-abstract class SplitScreenTestBase(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
- protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
- protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
- protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage
- protected val LIVE_WALLPAPER_PACKAGE_NAME =
- "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
- protected val LETTER_BOX_NAME = "Letterbox"
- protected val TOAST_NAME = "Toast"
-
- protected val transitionSetup: FlickerBuilder
- get() = FlickerBuilder(instrumentation).apply {
- setup {
- eachRun {
- uiDevice.wakeUpAndGoToHomeScreen()
- uiDevice.openQuickStepAndClearRecentAppsFromOverview()
- }
- }
- teardown {
- eachRun {
- if (uiDevice.isInSplitScreen()) {
- uiDevice.exitSplitScreen()
- }
- splitScreenApp.exit()
- nonResizeableApp.exit()
- }
- }
- assertions {
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- }
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
- }
- }
-}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 615bf4d..ce1d96c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -435,6 +435,7 @@
"canvas/CanvasFrontend.cpp",
"canvas/CanvasOpBuffer.cpp",
"canvas/CanvasOpRasterizer.cpp",
+ "effects/StretchEffect.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
"pipeline/skia/RenderNodeDrawable.cpp",
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 96118aa..64b8b71 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -561,7 +561,7 @@
return;
}
c->concat(invertedMatrix);
- mLayerSurface->draw(c, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+ mLayerSurface->draw(c, deviceBounds.fLeft, deviceBounds.fTop);
} else {
c->drawDrawable(drawable.get());
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index aeb60e6..609706e 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -23,6 +23,7 @@
#include "Outline.h"
#include "Rect.h"
#include "RevealClip.h"
+#include "effects/StretchEffect.h"
#include "utils/MathUtils.h"
#include "utils/PaintUtils.h"
@@ -98,6 +99,10 @@
SkImageFilter* getImageFilter() const { return mImageFilter.get(); }
+ const StretchEffect& getStretchEffect() const { return mStretchEffect; }
+
+ StretchEffect& mutableStretchEffect() { return mStretchEffect; }
+
// Sets alpha, xfermode, and colorfilter from an SkPaint
// paint may be NULL, in which case defaults will be set
bool setFromPaint(const SkPaint* paint);
@@ -124,6 +129,7 @@
SkBlendMode mMode;
sk_sp<SkColorFilter> mColorFilter;
sk_sp<SkImageFilter> mImageFilter;
+ StretchEffect mStretchEffect;
};
/*
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
new file mode 100644
index 0000000..51cbc75
--- /dev/null
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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 "StretchEffect.h"
+
+namespace android::uirenderer {
+
+sk_sp<SkImageFilter> StretchEffect::getImageFilter() const {
+ // TODO: Implement & Cache
+ // Probably need to use mutable to achieve caching
+ return nullptr;
+}
+
+} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
new file mode 100644
index 0000000..7dfd639
--- /dev/null
+++ b/libs/hwui/effects/StretchEffect.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "utils/MathUtils.h"
+
+#include <SkPoint.h>
+#include <SkRect.h>
+#include <SkImageFilter.h>
+
+namespace android::uirenderer {
+
+// TODO: Inherit from base RenderEffect type?
+class StretchEffect {
+public:
+ enum class StretchInterpolator {
+ SmoothStep,
+ };
+
+ bool isEmpty() const {
+ return MathUtils::isZero(stretchDirection.x())
+ && MathUtils::isZero(stretchDirection.y());
+ }
+
+ void setEmpty() {
+ *this = StretchEffect{};
+ }
+
+ void mergeWith(const StretchEffect& other) {
+ if (other.isEmpty()) {
+ return;
+ }
+ if (isEmpty()) {
+ *this = other;
+ return;
+ }
+ stretchDirection += other.stretchDirection;
+ if (isEmpty()) {
+ return setEmpty();
+ }
+ stretchArea.join(other.stretchArea);
+ maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
+ }
+
+ sk_sp<SkImageFilter> getImageFilter() const;
+
+ SkRect stretchArea {0, 0, 0, 0};
+ SkVector stretchDirection {0, 0};
+ float maxStretchAmount = 0;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8b35d96..8023968 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -166,6 +166,31 @@
return true;
}
+static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ auto& stretch = renderNode->mutateStagingProperties()
+ .mutateLayerProperties().mutableStretchEffect();
+ if (stretch.isEmpty()) {
+ return false;
+ }
+ stretch.setEmpty();
+ renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+ return true;
+}
+
+static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
+ jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
+ StretchEffect{
+ .stretchArea = SkRect::MakeLTRB(left, top, right, bottom),
+ .stretchDirection = {.fX = vX, .fY = vY},
+ .maxStretchAmount = max
+ });
+ renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+ return true;
+}
+
static jboolean android_view_RenderNode_hasShadow(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
return renderNode->stagingProperties().hasShadow();
@@ -678,6 +703,8 @@
{ "nSetOutlinePath", "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath },
{ "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty },
{ "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone },
+ { "nClearStretch", "(J)Z", (void*) android_view_RenderNode_clearStretch },
+ { "nStretch", "(JFFFFFFF)Z", (void*) android_view_RenderNode_stretch },
{ "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow },
{ "nSetSpotShadowColor", "(JI)Z", (void*) android_view_RenderNode_setSpotShadowColor },
{ "nGetSpotShadowColor", "(J)I", (void*) android_view_RenderNode_getSpotShadowColor },
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index c6c9e9d..71f533c 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -194,7 +194,7 @@
canvas->concat(invertedMatrix);
const SkIRect deviceBounds = canvas->getDeviceClipBounds();
- tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+ tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop);
}
}
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 75815bb6..c010212 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -20,6 +20,8 @@
#include "SkiaDisplayList.h"
#include "utils/TraceUtils.h"
+#include <include/effects/SkImageFilters.h>
+
#include <optional>
namespace android {
@@ -171,11 +173,25 @@
SkPaint* paint) {
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
- properties.getImageFilter() != nullptr) {
+ properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) {
paint->setAlpha(properties.alpha() * alphaMultiplier);
paint->setBlendMode(properties.xferMode());
paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
- paint->setImageFilter(sk_ref_sp(properties.getImageFilter()));
+
+ sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
+ sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter();
+ sk_sp<SkImageFilter> filter;
+ if (imageFilter && stretchFilter) {
+ filter = SkImageFilters::Compose(
+ std::move(stretchFilter),
+ std::move(imageFilter)
+ );
+ } else if (stretchFilter) {
+ filter = std::move(stretchFilter);
+ } else {
+ filter = std::move(imageFilter);
+ }
+ paint->setImageFilter(std::move(filter));
return true;
}
return false;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index eacabfd..633f21c 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -490,9 +490,11 @@
if (mNativeSurface) {
// TODO(b/165985262): measure performance impact
- if (const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
- vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
- native_window_set_frame_timeline_vsync(mNativeSurface->getNativeWindow(), vsyncId);
+ const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+ if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
+ const auto inputEventId = mCurrentFrameInfo->get(FrameInfoIndex::NewestInputEvent);
+ native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), vsyncId,
+ inputEventId);
}
}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 3d188c0..65a0110 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -31,6 +31,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -1775,6 +1776,7 @@
*/
@Nullable
@SystemApi
+ @SuppressLint("NullableCollection")
public Collection<CorrelationVector> getCorrelationVectors() {
return mReadOnlyCorrelationVectors;
}
@@ -1785,7 +1787,9 @@
* @hide
*/
@TestApi
- public void setCorrelationVectors(@Nullable Collection<CorrelationVector> correlationVectors) {
+ public void setCorrelationVectors(
+ @SuppressLint("NullableCollection")
+ @Nullable Collection<CorrelationVector> correlationVectors) {
if (correlationVectors == null || correlationVectors.isEmpty()) {
resetCorrelationVectors();
} else {
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 96ec590..fdb044d 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -2233,6 +2233,7 @@
* @see #getGnssCapabilities()
*/
@Nullable
+ @SuppressLint("NullableCollection")
public List<GnssAntennaInfo> getGnssAntennaInfos() {
try {
return mService.getGnssAntennaInfos();
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 1306ea2..8f455cd 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -255,7 +255,9 @@
/**
* Implements optional custom commands.
*/
- public abstract void onSendExtraCommand(@NonNull String command, @Nullable Bundle extras);
+ public abstract void onSendExtraCommand(@NonNull String command,
+ @SuppressLint("NullableCollection")
+ @Nullable Bundle extras);
private final class Service extends ILocationProvider.Stub {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index bcc846b..bb1dbd4 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -739,6 +739,13 @@
if (mBundle != null) {
aa.mBundle = new Bundle(mBundle);
}
+
+ // Allow the FLAG_HW_HOTWORD only for AudioSource.VOICE_RECOGNITION
+ if (mSource != MediaRecorder.AudioSource.VOICE_RECOGNITION
+ && (mFlags & FLAG_HW_HOTWORD) == FLAG_HW_HOTWORD) {
+ aa.mFlags &= ~FLAG_HW_HOTWORD;
+ }
+
return aa;
}
@@ -852,6 +859,23 @@
}
/**
+ * @hide
+ * Request for capture in hotword mode.
+ *
+ * Requests an audio path optimized for Hotword detection use cases from
+ * the low power audio DSP. This is valid only for capture with
+ * audio source {@link MediaRecorder.AudioSource#VOICE_RECOGNITION}.
+ * There is no guarantee that this mode is available on the device.
+ * @return the same Builder instance.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
+ public @NonNull Builder setHotwordMode() {
+ mFlags |= FLAG_HW_HOTWORD;
+ return this;
+ }
+
+ /**
* Specifies whether the audio may or may not be captured by other apps or the system.
*
* The default is {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}.
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionManager.java b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
index 6bbcfd3..b183eaf 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionManager.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
@@ -90,7 +91,9 @@
* supplied bundle
*/
void onRecognitionSucceeded(@NonNull RecognitionRequest recognitionRequest,
- @NonNull MediaMetadata result, @Nullable Bundle extras);
+ @NonNull MediaMetadata result,
+ @SuppressLint("NullableCollection")
+ @Nullable Bundle extras);
/**
* Invoked when the search is not successful (possibly but not necessarily due to error).
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionService.java b/media/java/android/media/musicrecognition/MusicRecognitionService.java
index e2071b8..04b4c39b 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionService.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
@@ -53,7 +54,9 @@
* @param extras extra data to be supplied back to the caller. Note that all executable
* parameters and file descriptors would be removed from the supplied bundle
*/
- void onRecognitionSucceeded(@NonNull MediaMetadata result, @Nullable Bundle extras);
+ void onRecognitionSucceeded(@NonNull MediaMetadata result,
+ @SuppressLint("NullableCollection")
+ @Nullable Bundle extras);
/**
* Call this method if the search does not find a result on an error occurred.
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 4d835b2..8525e99 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
import android.hardware.tv.tuner.V1_0.Constants;
@@ -1017,6 +1018,7 @@
* failed.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public List<FrontendInfo> getAvailableFrontendInfos() {
FrontendInfo[] feInfoList = getFrontendInfoListInternal();
if (feInfoList == null) {
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index c064de2..08a8d89 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,7 +18,6 @@
#include <binder/Parcel.h>
#include <jni.h>
-#include <media/IMediaMetricsService.h>
#include <media/MediaMetricsItem.h>
#include <nativehelper/JNIHelp.h>
#include <variant>
@@ -151,12 +150,7 @@
return (jint)BAD_VALUE;
}
- sp<IMediaMetricsService> service = mediametrics::BaseItem::getService();
- if (service == nullptr) {
- ALOGW("Cannot retrieve mediametrics service");
- return (jint)NO_INIT;
- }
- return (jint)service->submitBuffer((char *)buffer, length);
+ return (jint)mediametrics::BaseItem::submitBuffer((char *)buffer, length);
}
// Helper function to convert a native PersistableBundle to a Java
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index d464587..3d633ea 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -27,14 +27,13 @@
],
shared_libs: [
- "libandroid_runtime",
"libhwui",
"liblog",
],
header_libs: [
- "libhwui_internal_headers",
"jni_headers",
+ "libhwui_internal_headers",
],
static_libs: ["libarect"],
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
new file mode 100644
index 0000000..9afa5d1
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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.net;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for all core connectivity services.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializer {
+ private ConnectivityFrameworkInitializer() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers all core
+ * connectivity services to {@link Context}, so that {@link Context#getSystemService} can
+ * return them.
+ *
+ * @throws IllegalStateException if this is called anywhere besides
+ * {@link SystemServiceRegistry}.
+ */
+ public static void registerServiceWrappers() {
+ // registerContextAwareService will throw if this is called outside of SystemServiceRegistry
+ // initialization.
+ SystemServiceRegistry.registerContextAwareService(
+ Context.CONNECTIVITY_SERVICE,
+ ConnectivityManager.class,
+ (context, serviceBinder) -> {
+ IConnectivityManager icm = IConnectivityManager.Stub.asInterface(serviceBinder);
+ return new ConnectivityManager(context, icm);
+ }
+ );
+
+ // TODO: move outside of the connectivity JAR
+ SystemServiceRegistry.registerContextAwareService(
+ Context.VPN_MANAGEMENT_SERVICE,
+ VpnManager.class,
+ (context) -> {
+ final ConnectivityManager cm = context.getSystemService(
+ ConnectivityManager.class);
+ return cm.createVpnManager();
+ }
+ );
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+ ConnectivityDiagnosticsManager.class,
+ (context) -> {
+ final ConnectivityManager cm = context.getSystemService(
+ ConnectivityManager.class);
+ return cm.createDiagnosticsManager();
+ }
+ );
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.TEST_NETWORK_SERVICE,
+ TestNetworkManager.class,
+ context -> {
+ final ConnectivityManager cm = context.getSystemService(
+ ConnectivityManager.class);
+ return cm.startOrGetTestNetworkManager();
+ }
+ );
+ }
+}
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index ac8f9c9..2a0a74e 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -4823,6 +4823,28 @@
}
}
+ /** @hide */
+ public TestNetworkManager startOrGetTestNetworkManager() {
+ final IBinder tnBinder;
+ try {
+ tnBinder = mService.startOrGetTestNetworkService();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder));
+ }
+
+ /** @hide */
+ public VpnManager createVpnManager() {
+ return new VpnManager(mContext, mService);
+ }
+
+ /** @hide */
+ public ConnectivityDiagnosticsManager createDiagnosticsManager() {
+ return new ConnectivityDiagnosticsManager(mContext, mService);
+ }
+
/**
* Simulates a Data Stall for the specified Network.
*
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 3843b9a..8bfa77a 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -34,9 +34,9 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
import com.android.internal.util.Preconditions;
+import com.android.net.module.util.CollectionUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -401,11 +401,18 @@
public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27;
/**
- * Indicates that this network is not managed by a Virtual Carrier Network (VCN).
- *
- * TODO(b/177299683): Add additional clarifying javadoc.
+ * Indicates that this network is not subsumed by a Virtual Carrier Network (VCN).
+ * <p>
+ * To provide an experience on a VCN similar to a single traditional carrier network, in
+ * some cases the system sets this bit is set by default in application's network requests,
+ * and may choose to remove it at its own discretion when matching the request to a network.
+ * <p>
+ * Applications that want to know about a Virtual Carrier Network's underlying networks,
+ * for example to use them for multipath purposes, should remove this bit from their network
+ * requests ; the system will not add it back once removed.
* @hide
*/
+ @SystemApi
public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
@@ -767,7 +774,7 @@
if (originalOwnerUid == creatorUid) {
setOwnerUid(creatorUid);
}
- if (ArrayUtils.contains(originalAdministratorUids, creatorUid)) {
+ if (CollectionUtils.contains(originalAdministratorUids, creatorUid)) {
setAdministratorUids(new int[] {creatorUid});
}
// There is no need to clear the UIDs, they have already been cleared by clearAll() above.
@@ -1873,7 +1880,7 @@
sb.append(" OwnerUid: ").append(mOwnerUid);
}
- if (!ArrayUtils.isEmpty(mAdministratorUids)) {
+ if (mAdministratorUids != null && mAdministratorUids.length != 0) {
sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids));
}
@@ -2506,7 +2513,7 @@
@NonNull
public NetworkCapabilities build() {
if (mCaps.getOwnerUid() != Process.INVALID_UID) {
- if (!ArrayUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) {
+ if (!CollectionUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) {
throw new IllegalStateException("The owner UID must be included in "
+ " administrator UIDs.");
}
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index c8f3bd3..8fc3181 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -57,6 +57,7 @@
static_libs: [
"net-utils-device-common",
"net-utils-framework-common",
+ "netd-client",
],
apex_available: [
"//apex_available:platform",
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
index 8a73fb5..2be00b9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
@@ -15,11 +15,17 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="4dp"
+ android:left="4dp"
+ android:right="4dp"
+ android:bottom="4dp">
- <size android:height="24dp" android:width="24dp" />
- <solid android:color="@color/thumb_off" />
- <stroke android:width="4dp" android:color="@android:color/transparent" />
+ <shape android:shape="oval" >
+ <size android:height="20dp" android:width="20dp" />
+ <solid android:color="@color/thumb_off" />
+ </shape>
-</shape>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
index 8a0af00..e85eb42 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
@@ -15,11 +15,17 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="4dp"
+ android:left="4dp"
+ android:right="4dp"
+ android:bottom="4dp">
- <size android:height="24dp" android:width="24dp" />
- <solid android:color="?android:attr/colorAccent" />
- <stroke android:width="4dp" android:color="@android:color/transparent" />
+ <shape android:shape="oval" >
+ <size android:height="20dp" android:width="20dp" />
+ <solid android:color="?android:attr/colorAccent" />
+ </shape>
-</shape>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
index 1be3a8e..b29f459 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
@@ -20,7 +20,7 @@
<item
android:width="13.33dp"
android:height="1.67dp"
- android:left="19dp"
+ android:left="21dp"
android:gravity="center"
android:drawable="@drawable/track_off_indicator" />
</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
index 3cc490f..c838654 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
@@ -17,17 +17,17 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="24dp"
- android:viewportWidth="48"
- android:viewportHeight="24">
+ android:width="52dp"
+ android:height="28dp"
+ android:viewportWidth="52"
+ android:viewportHeight="28">
<group>
<clip-path
- android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
+ android:pathData="M14 0H38C45.732 0 52 6.26801 52 14C52 21.732 45.732 28 38 28H14C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0Z" />
<path
- android:pathData="M0 0V24H48V0"
+ android:pathData="M0 0V28H52V0"
android:fillColor="@color/track_off" />
</group>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
index 2553891..cf24112 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
@@ -21,6 +21,6 @@
android:width="13.19dp"
android:height="10.06dp"
android:gravity="center"
- android:right="19dp"
+ android:right="21dp"
android:drawable="@drawable/track_on_indicator" />
</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
index 68ce19b..bb1a7ef 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
@@ -17,19 +17,20 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="24dp"
- android:viewportWidth="48"
- android:viewportHeight="24"
+ android:width="52dp"
+ android:height="28dp"
+ android:viewportWidth="52"
+ android:viewportHeight="28"
android:tint="@*android:color/switch_track_material">
<group>
<clip-path
- android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
+ android:pathData="M14 0H38C45.732 0 52 6.26801 52 14C52 21.732 45.732 28 38 28H14C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0Z" />
<path
- android:pathData="M0 0V24H48V0"
+ android:pathData="M0 0V28H52V0"
android:fillColor="@*android:color/white_disabled_material" />
+
</group>
</vector>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
index 7e3ce9d..52779bc 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
@@ -26,7 +26,7 @@
android:minHeight="@dimen/min_switch_bar_height"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:background="?android:attr/colorBackground"
+ android:background="?android:attr/selectableItemBackground"
android:paddingLeft="@dimen/switchbar_margin_start"
android:paddingRight="@dimen/switchbar_margin_end">
@@ -35,7 +35,7 @@
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
- android:paddingRight="54dp"
+ android:layout_marginRight="16dp"
android:layout_gravity="center_vertical"
android:maxLines="2"
android:ellipsize="end"
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
index 9dc0af3..147db77 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
@@ -17,7 +17,6 @@
<resources>
- <color name="title_text_color">@*android:color/primary_text_dark</color>
<color name="thumb_off">#BFFFFFFF</color>
<color name="track_off">@*android:color/material_grey_600</color>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
index 8194bdd..147db77 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
@@ -17,7 +17,6 @@
<resources>
- <color name="title_text_color">@*android:color/primary_text_light</color>
<color name="thumb_off">#BFFFFFFF</color>
<color name="track_off">@*android:color/material_grey_600</color>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index dd443de..b145c9b 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -18,13 +18,13 @@
<resources>
<!-- Size of layout margin left -->
- <dimen name="switchbar_margin_start">26dp</dimen>
+ <dimen name="switchbar_margin_start">22dp</dimen>
<!-- Size of layout margin right -->
- <dimen name="switchbar_margin_end">24dp</dimen>
+ <dimen name="switchbar_margin_end">16dp</dimen>
<!-- Minimum width of switch -->
- <dimen name="min_switch_width">48dp</dimen>
+ <dimen name="min_switch_width">52dp</dimen>
<!-- Minimum width of switch bar -->
<dimen name="min_switch_bar_height">72dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
index fbb896c..59b5899 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
@@ -19,7 +19,6 @@
<style name="MainSwitchText">
<item name="android:textSize">20sp</item>
- <item name="android:textColor">@color/title_text_color</item>
</style>
<style name="Settings.MainSwitch" parent="@android:style/Widget.Material.CompoundButton.Switch">
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 532c996..74b6578 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -136,10 +136,8 @@
* Show the MainSwitchBar
*/
public void show() {
- if (!isShowing()) {
- setVisibility(View.VISIBLE);
- mSwitch.setOnCheckedChangeListener(this);
- }
+ setVisibility(View.VISIBLE);
+ mSwitch.setOnCheckedChangeListener(this);
}
/**
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index dae0e70..274bf8d 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -17,9 +17,11 @@
package com.android.settingslib.widget;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
import android.util.AttributeSet;
-import android.widget.Switch;
+import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.TwoStatePreference;
@@ -38,30 +40,29 @@
private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
private MainSwitchBar mMainSwitchBar;
- private Switch mSwitch;
private String mTitle;
private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
public MainSwitchPreference(Context context) {
super(context);
- init();
+ init(context, null);
}
public MainSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- init();
+ init(context, attrs);
}
public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- init();
+ init(context, attrs);
}
public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- init();
+ init(context, attrs);
}
@Override
@@ -76,8 +77,21 @@
registerListenerToSwitchBar();
}
- private void init() {
+ private void init(Context context, AttributeSet attrs) {
setLayoutResource(R.layout.main_switch_layout);
+
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/,
+ 0 /*defStyleRes*/);
+ final CharSequence title = TypedArrayUtils.getText(a,
+ androidx.preference.R.styleable.Preference_title,
+ androidx.preference.R.styleable.Preference_android_title);
+ if (!TextUtils.isEmpty(title)) {
+ setTitle(title.toString());
+ }
+ a.recycle();
+ }
}
/**
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index f346a59..3331550 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -6,6 +6,7 @@
static_libs: [
"androidx.preference_preference",
+ "androidx-constraintlayout_constraintlayout",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
index 9dbd5fa..f45105d 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
+++ b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
@@ -17,6 +17,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
@@ -26,29 +27,40 @@
android:paddingTop="32dp"
android:paddingBottom="32dp">
- <RelativeLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/usage_summary"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_alignBaseline="@id/total_summary"
- android:singleLine="true"
+ app:layout_constraintWidth_percent="0.45"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBaseline_toBaselineOf="@id/total_summary"
android:ellipsize="marquee"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Display1"
- android:textSize="20sp"/>
+ android:textSize="14sp"
+ android:textAlignment="viewStart"/>
<TextView
android:id="@+id/total_summary"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:singleLine="true"
+ app:layout_constraintWidth_percent="0.45"
+ app:layout_constraintEnd_toStartOf="@id/custom_content"
android:ellipsize="marquee"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"/>
- </RelativeLayout>
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
+ android:textSize="14sp"
+ android:textAlignment="viewEnd"/>
+ <FrameLayout
+ android:id="@+id/custom_content"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/total_summary"
+ app:layout_constraintWidth_percent="0.1"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
<ProgressBar
android:id="@android:id/progress"
diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
index 950a8b4..af64a1d 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
+++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
@@ -22,6 +22,9 @@
import android.text.TextUtils;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -41,6 +44,7 @@
private CharSequence mUsageSummary;
private CharSequence mTotalSummary;
+ private ImageView mCustomImageView;
private int mPercent = -1;
/**
@@ -110,6 +114,15 @@
notifyChanged();
}
+ /** Set custom ImageView to the right side of total summary. */
+ public <T extends ImageView> void setCustomContent(T imageView) {
+ if (imageView == mCustomImageView) {
+ return;
+ }
+ mCustomImageView = imageView;
+ notifyChanged();
+ }
+
/**
* Binds the created View to the data for this preference.
*
@@ -141,6 +154,15 @@
progressBar.setIndeterminate(false);
progressBar.setProgress(mPercent);
}
+
+ final FrameLayout customLayout = (FrameLayout) holder.findViewById(R.id.custom_content);
+ if (mCustomImageView == null) {
+ customLayout.removeAllViews();
+ customLayout.setVisibility(View.GONE);
+ } else {
+ customLayout.addView(mCustomImageView);
+ customLayout.setVisibility(View.VISIBLE);
+ }
}
private CharSequence enlargeFontOfNumber(CharSequence summary) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 5580afe..78282fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -38,7 +39,9 @@
import com.android.settingslib.R;
import com.android.settingslib.Utils;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Track status of Wi-Fi for the Sys UI.
@@ -50,6 +53,7 @@
private final NetworkScoreManager mNetworkScoreManager;
private final ConnectivityManager mConnectivityManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Set<Integer> mNetworks = new HashSet<>();
private final WifiNetworkScoreCache.CacheListener mCacheListener =
new WifiNetworkScoreCache.CacheListener(mHandler) {
@Override
@@ -64,6 +68,20 @@
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(
+ Network network, NetworkCapabilities networkCapabilities,
+ LinkProperties linkProperties, boolean blocked) {
+ boolean isVcnOverWifi =
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && (Utils.tryGetWifiInfoForVcn(networkCapabilities) != null);
+ boolean isWifi =
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+ if (isVcnOverWifi || isWifi) {
+ mNetworks.add(network.getNetId());
+ }
+ }
+
// Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
// and onLinkPropertiesChanged.
@Override
@@ -84,9 +102,12 @@
@Override
public void onLost(Network network) {
- updateWifiInfo(null);
- updateStatusLabel();
- mCallback.run();
+ if (mNetworks.contains(network.getNetId())) {
+ mNetworks.remove(network.getNetId());
+ updateWifiInfo(null);
+ updateStatusLabel();
+ mCallback.run();
+ }
}
};
private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
index 85e2174..1a8477d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
@@ -18,11 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import android.text.SpannedString;
import android.text.style.RelativeSizeSpan;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -97,4 +101,30 @@
.findViewById(android.R.id.progress);
assertThat(progressBar.getProgress()).isEqualTo((int) (31.0f / 80 * 100));
}
+
+ @Test
+ public void setCustomContent_setNullImageView_noChild() {
+ mUsageProgressBarPreference.setCustomContent(null /* imageView */);
+
+ mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
+
+ final FrameLayout customContent =
+ (FrameLayout) mViewHolder.findViewById(R.id.custom_content);
+ assertThat(customContent.getChildCount()).isEqualTo(0);
+ assertThat(customContent.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void setCustomContent_setImageView_oneChild() {
+ final ImageView imageView = mock(ImageView.class);
+ mUsageProgressBarPreference.setCustomContent(imageView);
+
+ mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
+
+ final FrameLayout customContent =
+ (FrameLayout) mViewHolder.findViewById(R.id.custom_content);
+ assertThat(customContent.getChildCount()).isEqualTo(1);
+ assertThat(customContent.getChildAt(0)).isEqualTo(imageView);
+ assertThat(customContent.getVisibility()).isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index efd941e..438cec8 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -272,6 +272,7 @@
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
+ Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
Settings.Global.ENHANCED_4G_MODE_ENABLED,
Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
Settings.Global.ERROR_LOGCAT_PREFIX,
@@ -280,6 +281,7 @@
Settings.Global.EUICC_UNSUPPORTED_COUNTRIES,
Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
+ Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS,
Settings.Global.FANCY_IME_ANIMATIONS,
Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
Settings.Global.FORCED_APP_STANDBY_ENABLED,
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
index cf64136..5cb6f46 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
@@ -21,16 +21,16 @@
>
<shape android:shape="oval">
<size
- android:height="28dp"
- android:width="28dp"
+ android:height="@dimen/ongoing_appops_dialog_circle_size"
+ android:width="@dimen/ongoing_appops_dialog_circle_size"
/>
<solid android:color="@color/privacy_circle_camera" />
</shape>
</item>
<item android:id="@id/icon"
android:gravity="center"
- android:width="20dp"
- android:height="20dp"
+ android:width="@dimen/ongoing_appops_dialog_icon_size"
+ android:height="@dimen/ongoing_appops_dialog_icon_size"
android:drawable="@*android:drawable/perm_group_camera"
/>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
index 0a6a4a3..bff9b4b 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
@@ -21,16 +21,16 @@
>
<shape android:shape="oval">
<size
- android:height="28dp"
- android:width="28dp"
+ android:height="@dimen/ongoing_appops_dialog_circle_size"
+ android:width="@dimen/ongoing_appops_dialog_circle_size"
/>
<solid android:color="@color/privacy_circle_microphone_location" />
</shape>
</item>
<item android:id="@id/icon"
android:gravity="center"
- android:width="20dp"
- android:height="20dp"
- android:drawable="@*android:drawable/perm_group_microphone"
+ android:width="@dimen/ongoing_appops_dialog_icon_size"
+ android:height="@dimen/ongoing_appops_dialog_icon_size"
+ android:drawable="@*android:drawable/perm_group_location"
/>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
index 0a6a4a3..28466c8 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
@@ -21,16 +21,16 @@
>
<shape android:shape="oval">
<size
- android:height="28dp"
- android:width="28dp"
+ android:height="@dimen/ongoing_appops_dialog_circle_size"
+ android:width="@dimen/ongoing_appops_dialog_circle_size"
/>
<solid android:color="@color/privacy_circle_microphone_location" />
</shape>
</item>
<item android:id="@id/icon"
android:gravity="center"
- android:width="20dp"
- android:height="20dp"
+ android:width="@dimen/ongoing_appops_dialog_icon_size"
+ android:height="@dimen/ongoing_appops_dialog_icon_size"
android:drawable="@*android:drawable/perm_group_microphone"
/>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
index 71b414f..93664da 100644
--- a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
+++ b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
@@ -25,7 +25,7 @@
android:layout_marginBottom="@dimen/screenshot_offset_y"
android:scaleType="fitStart"
android:elevation="@dimen/screenshot_preview_elevation"
- android:visibility="gone"
+ android:visibility="invisible"
android:background="@drawable/screenshot_rounded_corners"
android:adjustViewBounds="true"
android:contentDescription="@string/screenshot_edit_label"
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index 5db247e..4d77a0d 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -22,7 +22,13 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/ongoing_appops_dialog_side_margins"
android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
+ android:layout_marginTop="8dp"
android:orientation="vertical"
- android:padding = "8dp"
+ android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
+ android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
+ android:paddingBottom="12dp"
+ android:paddingTop="8dp"
android:background="@drawable/privacy_dialog_bg"
-/>
\ No newline at end of file
+/>
+<!-- 12dp padding bottom so there's 20dp total under the icon -->
+<!-- 8dp padding top, as there's 4dp margin in each row -->
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml
index 882e968..91ffe22 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_item.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml
@@ -16,19 +16,22 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/privacy_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:orientation="horizontal"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="4dp"
+ android:importantForAccessibility="yes"
+ android:focusable="true"
>
+ <!-- 4dp marginTop makes 20dp minimum between icons -->
<ImageView
android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/ongoing_appops_dialog_circle_size"
+ android:layout_height="@dimen/ongoing_appops_dialog_circle_size"
android:layout_gravity="center_vertical"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
+ android:importantForAccessibility="no"
/>
<TextView
@@ -38,25 +41,21 @@
android:layout_width="0dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:textDirection="locale"
+ android:textAlignment="viewStart"
android:gravity="start | center_vertical"
- android:textAppearance="@style/TextAppearance.QS.TileLabel"
+ android:textAppearance="@style/TextAppearance.PrivacyDialog"
+ android:lineHeight="20sp"
/>
- <FrameLayout
- android:id="@+id/link"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ <ImageView
+ android:layout_height="24dp"
+ android:layout_width="24dp"
android:layout_gravity="center_vertical"
- android:background="?android:attr/selectableItemBackground"
- >
-
- <ImageView
- android:layout_height="24dp"
- android:layout_width="24dp"
- android:layout_gravity="center"
- android:src="@*android:drawable/ic_chevron_end"
- android:tint="?android:attr/textColorPrimary"
- />
- </FrameLayout>
+ android:src="@*android:drawable/ic_chevron_end"
+ android:tint="?android:attr/textColorPrimary"
+ />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e510930..1fac96b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1181,6 +1181,12 @@
<dimen name="ongoing_appops_dialog_side_margins">@dimen/notification_shade_content_margin_horizontal</dimen>
+ <dimen name="ongoing_appops_dialog_circle_size">32dp</dimen>
+
+ <dimen name="ongoing_appops_dialog_icon_size">20dp</dimen>
+
+ <dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
+
<!-- Size of the RAT type for CellularTile -->
<dimen name="celltile_rat_type_size">10sp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 8c6aba2..01e54ff 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -33,4 +33,7 @@
<bool name="flag_keyguard_layout">false</bool>
<bool name="flag_brightness_slider">false</bool>
+
+ <!-- People Tile flag -->
+ <bool name="flag_conversations">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4baa06a..eee0128 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1888,7 +1888,7 @@
<!-- Notification Inline controls: describes how the notification was adjusted [CHAR_LIMIT=NONE] -->
<string name="feedback_demoted">This notification was automatically <b>ranked lower</b> in your shade.</string>
<!-- Notification Inline controls: prompts the user for feedback [CHAR_LIMIT=NONE] -->
- <string name="feedback_prompt">Was this correct?</string>
+ <string name="feedback_prompt">Let the developer know your feedback. Was this correct?</string>
<!-- Notification Inline controls: responds to user provided feedback [CHAR_LIMIT=NONE] -->
<string name="feedback_response">Thanks for your feedback!</string>
<string name="feedback_ok">OK</string>
@@ -2556,8 +2556,8 @@
<!-- Text for privacy dialog, indicating that the application is the enterprise version [CHAR LIMIT=NONE] -->
<string name="ongoing_privacy_dialog_enterprise">(enterprise)</string>
- <!-- Text for privacy dialog, identifying the phonecall app [CHAR LIMIT=NONE]-->
- <string name="ongoing_privacy_dialog_phonecall">Phonecall</string>
+ <!-- Text for privacy dialog, identifying the phone call app [CHAR LIMIT=NONE]-->
+ <string name="ongoing_privacy_dialog_phonecall">Phone call</string>
<!-- Text for privacy dialog, indicating that an app is using an op on behalf of another [CHAR LIMIT=NONE] -->
<string name="ongoing_privacy_dialog_attribution_text">(through <xliff:g id="attribution" example="Special app">%s</xliff:g>)</string>
@@ -2771,13 +2771,6 @@
<!-- Controls menu, edit [CHAR_LIMIT=30] -->
<string name="controls_menu_edit">Edit controls</string>
- <!-- Device-specific path to the node for enabling/disabling the high-brightness mode -->
- <string name="udfps_hbm_sysfs_path" translatable="false"></string>
- <!-- Device-specific payload for enabling the high-brightness mode -->
- <string name="udfps_hbm_enable_command" translatable="false"></string>
- <!-- Device-specific payload for disabling the high-brightness mode -->
- <string name="udfps_hbm_disable_command" translatable="false"></string>
-
<!-- Title for the media output group dialog with media related devices [CHAR LIMIT=50] -->
<string name="media_output_dialog_add_output">Add outputs</string>
<!-- Title for the media output slice with group devices [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index db260ce..ad4e78e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -745,4 +745,9 @@
* Title: headline, medium 20sp
* Message: body, 16 sp -->
<style name="Theme.ControlsRequestDialog" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert"/>
+
+ <style name="TextAppearance.PrivacyDialog">
+ <item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 2a0715e..87f6b82 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -118,9 +118,9 @@
}
public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
- final RemoteAnimationTargetCompat[] appsCompat =
- new RemoteAnimationTargetCompat[apps != null ? apps.length : 0];
- for (int i = 0; i < apps.length; i++) {
+ final int length = apps != null ? apps.length : 0;
+ final RemoteAnimationTargetCompat[] appsCompat = new RemoteAnimationTargetCompat[length];
+ for (int i = 0; i < length; i++) {
appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
}
return appsCompat;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index cc59c39..30db341 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -395,7 +395,9 @@
}
private void beginJankInstrument(int cuj) {
- InteractionJankMonitor.getInstance().begin(mSecurityViewFlipper.getSecurityView(), cuj);
+ KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView();
+ if (securityView == null) return;
+ InteractionJankMonitor.getInstance().begin(securityView, cuj);
}
private void endJankInstrument(int cuj) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9908e67..d9a1eb6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1909,12 +1909,14 @@
}
/**
- * Whether to show the lock icon on lock screen and bouncer. This depends on the enrolled
- * biometrics to the device.
+ * Whether to show the lock icon on lock screen and bouncer.
*/
- public boolean shouldShowLockIcon() {
- return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
- && !isUdfpsEnrolled();
+ public boolean canShowLockIcon() {
+ if (mLockScreenMode == LOCK_SCREEN_MODE_LAYOUT_1) {
+ return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
+ && !isUdfpsEnrolled();
+ }
+ return true;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 036fcf3..78f7966 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -127,7 +127,7 @@
}
// If SHOW_PEOPLE_SPACE is true, enable People Space widget provider.
- // TODO(b/170396074): Remove this when we don't need a widget anymore.
+ // TODO(b/170396074): Migrate to new feature flag (go/silk-flags-howto)
try {
int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.SHOW_PEOPLE_SPACE, 1);
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 66c535f..3f79be8 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -24,7 +24,6 @@
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowInsets
import android.widget.ImageView
@@ -66,9 +65,8 @@
super.onCreate(savedInstanceState)
window?.apply {
attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
- setLayout(MATCH_PARENT, WRAP_CONTENT)
+ setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT)
setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
- setBackgroundDrawable(null)
}
setContentView(R.layout.privacy_dialog)
@@ -125,12 +123,12 @@
val finalText = element.attribution?.let {
TextUtils.concat(
firstLine,
- "\n",
+ " ",
context.getString(R.string.ongoing_privacy_dialog_attribution_text, it)
)
} ?: firstLine
newView.requireViewById<TextView>(R.id.text).text = finalText
- newView.requireViewById<View>(R.id.link).apply {
+ newView.apply {
tag = element.type.permGroupName
setOnClickListener(clickListener)
}
@@ -154,9 +152,7 @@
}
private val clickListener = View.OnClickListener { v ->
- if (v.id == R.id.link) {
- v.tag?.let { activityStarter(it as String) }
- }
+ v.tag?.let { activityStarter(it as String) }
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index 99e9bfc..8b6c04f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -227,21 +227,20 @@
/**
* Filters the list of elements to show.
*
- * * Return at most one element per [PrivacyType], sorted by the natural order of the
- * [PrivacyType].
- * * If there are no active usages for a type, return the most recent
- * * If there are multiple active usages for a type, return the most active recent.
+ * For each privacy type, it'll return all active elements. If there are no active elements,
+ * it'll return the most recent access
*/
private fun filterAndSelect(
list: List<PrivacyDialog.PrivacyElement>
): List<PrivacyDialog.PrivacyElement> {
- return list.groupBy { it.type }.toSortedMap().mapNotNull { entry ->
- if (entry.value.isEmpty()) {
- null
+ return list.groupBy { it.type }.toSortedMap().flatMap { (_, elements) ->
+ val actives = elements.filter { it.active }
+ if (actives.isNotEmpty()) {
+ actives.sortedByDescending { it.lastActiveTimestamp }
} else {
- val actives = entry.value.filter { it.active }
- val out = if (actives.isNotEmpty()) actives else entry.value
- out.maxByOrNull { it.lastActiveTimestamp }
+ elements.maxByOrNull { it.lastActiveTimestamp }?.let {
+ listOf(it)
+ } ?: emptyList()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index d6413ed..0a7eea4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -602,7 +602,7 @@
private void runScrollCapture(ScrollCaptureClient.Connection connection) {
cancelTimeout();
ScrollCaptureController controller = new ScrollCaptureController(mContext, connection,
- mMainExecutor, mBgExecutor, mImageExporter);
+ mMainExecutor, mBgExecutor, mImageExporter, mUiEventLogger);
controller.attach(mWindow);
controller.start(new TakeScreenshotService.RequestCallback() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index f1fb688..5cf0188 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -63,7 +63,15 @@
@UiEvent(doc = "screenshot swiped to dismiss")
SCREENSHOT_SWIPE_DISMISSED(656),
@UiEvent(doc = "screenshot reentered for new screenshot")
- SCREENSHOT_REENTERED(640);
+ SCREENSHOT_REENTERED(640),
+ @UiEvent(doc = "Long screenshot button was shown to the user")
+ SCREENSHOT_LONG_SCREENSHOT_IMPRESSION(687),
+ @UiEvent(doc = "User has requested a long screenshot")
+ SCREENSHOT_LONG_SCREENSHOT_REQUESTED(688),
+ @UiEvent(doc = "User has shared a long screenshot")
+ SCREENSHOT_LONG_SCREENSHOT_SHARE(689),
+ @UiEvent(doc = "User has sent a long screenshot to the editor")
+ SCREENSHOT_LONG_SCREENSHOT_EDIT(690);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index bf86b68..3bc5ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -192,14 +192,14 @@
if (DEBUG_SCROLL) {
Log.d(TAG, "Showing Scroll option");
}
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION);
mScrollChip.setVisibility(VISIBLE);
mScrollChip.setOnClickListener((v) -> {
if (DEBUG_INPUT) {
Log.d(TAG, "scroll chip tapped");
}
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED);
onClick.run();
- // TODO Logging, store event consumer to a field
- //onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 9be3566..176a2c7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -29,6 +29,7 @@
import android.view.Window;
import android.widget.ImageView;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -58,6 +59,7 @@
private final Executor mBgExecutor;
private final ImageExporter mImageExporter;
private final ImageTileSet mImageTileSet;
+ private final UiEventLogger mUiEventLogger;
private ZonedDateTime mCaptureTime;
private UUID mRequestId;
@@ -72,12 +74,13 @@
private Runnable mPendingAction;
public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor,
- Executor bgExecutor, ImageExporter exporter) {
+ Executor bgExecutor, ImageExporter exporter, UiEventLogger uiEventLogger) {
mContext = context;
mConnection = connection;
mUiExecutor = uiExecutor;
mBgExecutor = bgExecutor;
mImageExporter = exporter;
+ mUiEventLogger = uiEventLogger;
mImageTileSet = new ImageTileSet();
}
@@ -136,10 +139,12 @@
disableButtons();
finish();
} else if (id == R.id.edit) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT);
v.setPressed(true);
disableButtons();
edit();
} else if (id == R.id.share) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
v.setPressed(true);
disableButtons();
share();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index c54c459..e7b60c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -66,4 +66,8 @@
public boolean useNewBrightnessSlider() {
return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
}
+
+ public boolean isPeopleTileEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_conversations);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index 1ec043c..e4ae560 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -23,8 +23,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.phone.LockIcon;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
@@ -41,7 +39,6 @@
private final Context mContext;
private final InjectionInflationController mInjectionInflationController;
- private final LockscreenLockIconController mLockIconController;
private final NotificationShelfComponent.Builder mNotificationShelfComponentBuilder;
private NotificationShadeWindowView mNotificationShadeWindowView;
@@ -51,11 +48,9 @@
@Inject
public SuperStatusBarViewFactory(Context context,
InjectionInflationController injectionInflationController,
- NotificationShelfComponent.Builder notificationShelfComponentBuilder,
- LockscreenLockIconController lockIconController) {
+ NotificationShelfComponent.Builder notificationShelfComponentBuilder) {
mContext = context;
mInjectionInflationController = injectionInflationController;
- mLockIconController = lockIconController;
mNotificationShelfComponentBuilder = notificationShelfComponentBuilder;
}
@@ -77,10 +72,6 @@
throw new IllegalStateException(
"R.layout.super_notification_shade could not be properly inflated");
}
- LockIcon lockIcon = mNotificationShadeWindowView.findViewById(R.id.lock_icon);
- if (lockIcon != null) {
- mLockIconController.attach(lockIcon);
- }
return mNotificationShadeWindowView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 4fde118..db49e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -21,7 +21,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -50,7 +49,6 @@
private final ShadeListBuilder mListBuilder;
private final NotifCoordinators mNotifPluggableCoordinators;
private final NotifInflaterImpl mNotifInflater;
- private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
private final DumpManager mDumpManager;
private final ShadeViewManagerFactory mShadeViewManagerFactory;
private final FeatureFlags mFeatureFlags;
@@ -64,7 +62,6 @@
ShadeListBuilder listBuilder,
NotifCoordinators notifCoordinators,
NotifInflaterImpl notifInflater,
- PeopleSpaceWidgetManager peopleSpaceWidgetManager,
DumpManager dumpManager,
ShadeViewManagerFactory shadeViewManagerFactory,
FeatureFlags featureFlags) {
@@ -75,7 +72,6 @@
mNotifPluggableCoordinators = notifCoordinators;
mDumpManager = dumpManager;
mNotifInflater = notifInflater;
- mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mShadeViewManagerFactory = shadeViewManagerFactory;
mFeatureFlags = featureFlags;
}
@@ -103,7 +99,6 @@
mListBuilder.attach(mNotifCollection);
mNotifCollection.attach(mGroupCoalescer);
mGroupCoalescer.attach(notificationService);
- mPeopleSpaceWidgetManager.attach(notificationService);
Log.d(TAG, "Notif pipeline initialized");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 54ce4ed..0ad6507 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -18,6 +18,7 @@
import android.service.notification.StatusBarNotification
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.NotificationListener
@@ -73,7 +74,8 @@
private val headsUpController: HeadsUpController,
private val headsUpViewBinder: HeadsUpViewBinder,
private val clickerBuilder: NotificationClicker.Builder,
- private val animatedImageNotificationManager: AnimatedImageNotificationManager
+ private val animatedImageNotificationManager: AnimatedImageNotificationManager,
+ private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager
) : NotificationsController {
override fun initialize(
@@ -126,6 +128,10 @@
entryManager.attach(notificationListener)
}
+
+ if (featureFlags.isPeopleTileEnabled) {
+ peopleSpaceWidgetManager.attach(notificationListener)
+ }
}
override fun dump(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index e1eaf3c..f289b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -71,7 +71,7 @@
"persist.sysui.wake_performs_auth", true);
private boolean mDozingRequested;
private boolean mPulsing;
- private WakefulnessLifecycle mWakefulnessLifecycle;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
private final SysuiStatusBarStateController mStatusBarStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final HeadsUpManagerPhone mHeadsUpManagerPhone;
@@ -86,9 +86,8 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
- private final LockscreenLockIconController mLockscreenLockIconController;
private final AuthController mAuthController;
- private NotificationIconAreaController mNotificationIconAreaController;
+ private final NotificationIconAreaController mNotificationIconAreaController;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private NotificationPanelViewController mNotificationPanel;
private View mAmbientIndicationContainer;
@@ -109,7 +108,6 @@
PulseExpansionHandler pulseExpansionHandler,
NotificationShadeWindowController notificationShadeWindowController,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
- LockscreenLockIconController lockscreenLockIconController,
AuthController authController,
NotificationIconAreaController notificationIconAreaController) {
super();
@@ -129,7 +127,6 @@
mPulseExpansionHandler = pulseExpansionHandler;
mNotificationShadeWindowController = notificationShadeWindowController;
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
- mLockscreenLockIconController = lockscreenLockIconController;
mAuthController = authController;
mNotificationIconAreaController = notificationIconAreaController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index eceac32..4b70de9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -37,12 +37,10 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -52,18 +50,20 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ViewController;
import java.util.Optional;
import javax.inject.Inject;
-/** Controls the {@link LockIcon} in the lockscreen. */
-@SysUISingleton
-public class LockscreenLockIconController {
+/** Controls the {@link LockIcon} on the lockscreen. */
+@StatusBarComponent.StatusBarScope
+public class LockscreenLockIconController extends ViewController<LockIcon> {
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -79,7 +79,6 @@
private final KeyguardStateController mKeyguardStateController;
private final Resources mResources;
private final HeadsUpManagerPhone mHeadsUpManagerPhone;
- private final KeyguardSecurityModel mKeyguardSecurityModel;
private boolean mKeyguardShowing;
private boolean mKeyguardJustShown;
private boolean mBlockUpdates;
@@ -92,50 +91,310 @@
private boolean mBouncerShowingScrimmed;
private boolean mFingerprintUnlock;
private int mStatusBarState = StatusBarState.SHADE;
- private LockIcon mLockIcon;
+ private int mLastState;
+ private boolean mDozing;
- private View.OnAttachStateChangeListener mOnAttachStateChangeListener =
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- mStatusBarStateController.addCallback(mSBStateListener);
- mConfigurationController.addCallback(mConfigurationListener);
- mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
- mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
- mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+ @Inject
+ public LockscreenLockIconController(
+ @Nullable LockIcon view,
+ LockscreenGestureLogger lockscreenGestureLogger,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockPatternUtils lockPatternUtils,
+ ShadeController shadeController,
+ AccessibilityController accessibilityController,
+ KeyguardIndicationController keyguardIndicationController,
+ StatusBarStateController statusBarStateController,
+ ConfigurationController configurationController,
+ NotificationWakeUpCoordinator notificationWakeUpCoordinator,
+ KeyguardBypassController keyguardBypassController,
+ @Nullable DockManager dockManager,
+ KeyguardStateController keyguardStateController,
+ @Main Resources resources,
+ HeadsUpManagerPhone headsUpManagerPhone) {
+ super(view);
+ mLockscreenGestureLogger = lockscreenGestureLogger;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
+ mShadeController = shadeController;
+ mAccessibilityController = accessibilityController;
+ mKeyguardIndicationController = keyguardIndicationController;
+ mStatusBarStateController = statusBarStateController;
+ mConfigurationController = configurationController;
+ mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
+ mKeyguardBypassController = keyguardBypassController;
+ mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
+ mKeyguardStateController = keyguardStateController;
+ mResources = resources;
+ mHeadsUpManagerPhone = headsUpManagerPhone;
- mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
+ if (view == null) {
+ return;
+ }
- mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
- mConfigurationListener.onThemeChanged();
+ mKeyguardIndicationController.setLockIconController(this);
+ }
- updateColor();
+ @Override
+ protected void onInit() {
+ if (mView == null) {
+ return;
+ }
+ mView.setOnClickListener(this::handleClick);
+ mView.setOnLongClickListener(this::handleLongClick);
+ mView.setAccessibilityDelegate(mAccessibilityDelegate);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ setStatusBarState(mStatusBarStateController.getState());
+ mDozing = mStatusBarStateController.isDozing();
+ mStatusBarStateController.addCallback(mSBStateListener);
+ mConfigurationController.addCallback(mConfigurationListener);
+ mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
+ mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+ mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+
+ mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
+
+ mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
+ mConfigurationListener.onThemeChanged();
+
+ updateColor();
+ update();
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mStatusBarStateController.removeCallback(mSBStateListener);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
+ mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+ mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
+
+ mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
+ }
+
+ /**
+ * Called whenever the scrims become opaque, transparent or semi-transparent.
+ */
+ public void onScrimVisibilityChanged(Integer scrimsVisible) {
+ if (mWakeAndUnlockRunning
+ && scrimsVisible == ScrimController.TRANSPARENT) {
+ mWakeAndUnlockRunning = false;
update();
}
+ }
- @Override
- public void onViewDetachedFromWindow(View v) {
- mStatusBarStateController.removeCallback(mSBStateListener);
- mConfigurationController.removeCallback(mConfigurationListener);
- mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
- mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
- mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
-
- mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
+ /**
+ * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
+ * icon on top of the black front scrim.
+ * We also want to halt padlock the animation when we're in face bypass mode or dismissing the
+ * keyguard with fingerprint.
+ * @param wakeAndUnlock are we wake and unlocking
+ * @param isUnlock are we currently unlocking
+ */
+ public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
+ BiometricSourceType type) {
+ if (wakeAndUnlock) {
+ mWakeAndUnlockRunning = true;
}
- };
+ mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
+ if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
+ && canBlockUpdates()) {
+ // We don't want the icon to change while we are unlocking
+ mBlockUpdates = true;
+ }
+ update();
+ }
+
+ /**
+ * When we're launching an affordance, like double pressing power to open camera.
+ */
+ public void onShowingLaunchAffordanceChanged(Boolean showing) {
+ mShowingLaunchAffordance = showing;
+ update();
+ }
+
+ /** Sets whether the bouncer is showing. */
+ public void setBouncerShowingScrimmed(boolean showing, boolean scrimmed) {
+ mBouncerShowingScrimmed = scrimmed;
+ update();
+ }
+
+ /**
+ * Sets how hidden the bouncer is, where 0f is fully visible and 1f is fully hidden
+ * See {@link KeyguardBouncer#EXPANSION_VISIBLE} and {@link KeyguardBouncer#EXPANSION_HIDDEN}.
+ */
+ public void setBouncerHideAmount(float hideAmount) {
+ mBouncerHiddenAmount = hideAmount;
+ updateColor();
+ }
+
+ private void updateColor() {
+ if (mView == null) {
+ return;
+ }
+ int iconColor = -1;
+ if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_VISIBLE) {
+ TypedArray typedArray = mView.getContext().getTheme().obtainStyledAttributes(
+ null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
+ iconColor = typedArray.getColor(0, Color.WHITE);
+ typedArray.recycle();
+ } else if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_HIDDEN) {
+ iconColor = Utils.getColorAttrDefaultColor(
+ mView.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
+ } else {
+ // bouncer is transitioning
+ TypedArray typedArray = mView.getContext().getTheme().obtainStyledAttributes(
+ null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
+ int bouncerIconColor = typedArray.getColor(0, Color.WHITE);
+ typedArray.recycle();
+ int keyguardIconColor = Utils.getColorAttrDefaultColor(
+ mView.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
+ iconColor = (int) new ArgbEvaluator().evaluate(
+ mBouncerHiddenAmount, bouncerIconColor, keyguardIconColor);
+ }
+ mView.updateColor(iconColor);
+ }
+
+ /**
+ * Animate padlock opening when bouncer challenge is solved.
+ */
+ public void onBouncerPreHideAnimation() {
+ update();
+ }
+
+ /**
+ * If we're currently presenting an authentication error message.
+ */
+ public void setTransientBiometricsError(boolean transientBiometricsError) {
+ mTransientBiometricsError = transientBiometricsError;
+ update();
+ }
+
+ private boolean handleLongClick(View view) {
+ mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
+ 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+ mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
+ mKeyguardIndicationController.showTransientIndication(
+ R.string.keyguard_indication_trust_disabled);
+ mKeyguardUpdateMonitor.onLockIconPressed();
+ mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+
+ return true;
+ }
+
+
+ private void handleClick(View view) {
+ if (!mAccessibilityController.isAccessibilityEnabled()) {
+ return;
+ }
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ }
+
+ private void update() {
+ update(false /* force */);
+ }
+
+ private void update(boolean force) {
+ if (mView == null) {
+ return;
+ }
+ int state = getState();
+ boolean shouldUpdate = mLastState != state || force;
+ if (mBlockUpdates && canBlockUpdates()) {
+ shouldUpdate = false;
+ }
+ if (shouldUpdate && mView.getVisibility() != GONE) {
+ mView.update(state, mDozing, mKeyguardJustShown);
+ }
+ mLastState = state;
+ mKeyguardJustShown = false;
+ updateIconVisibility();
+ updateClickability();
+ }
+
+ private int getState() {
+ if ((mKeyguardStateController.canDismissLockScreen()
+ || !mKeyguardStateController.isShowing()
+ || mKeyguardStateController.isKeyguardGoingAway()
+ || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
+ return STATE_LOCK_OPEN;
+ } else if (mTransientBiometricsError) {
+ return STATE_BIOMETRICS_ERROR;
+ } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
+ return STATE_SCANNING_FACE;
+ } else {
+ return STATE_LOCKED;
+ }
+ }
+
+ private boolean canBlockUpdates() {
+ return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
+ }
+
+ /** Set the StatusBarState. */
+ private void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ updateIconVisibility();
+ }
+
+ /**
+ * Update the icon visibility
+ * @return true if the visibility changed
+ */
+ private boolean updateIconVisibility() {
+ if (mView == null) {
+ return false;
+ }
+ if (!mKeyguardUpdateMonitor.canShowLockIcon()) {
+ boolean changed = mView.getVisibility() != GONE;
+ mView.setVisibility(GONE);
+ return changed;
+ }
+
+ boolean onAodOrDocked = mDozing || mDocked;
+ boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
+ boolean fingerprintOrBypass = mFingerprintUnlock
+ || mKeyguardBypassController.getBypassEnabled();
+ if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
+ if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
+ || mHeadsUpManagerPhone.hasPinnedHeadsUp()
+ || mStatusBarState == StatusBarState.KEYGUARD
+ || mStatusBarState == StatusBarState.SHADE)
+ && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
+ invisible = true;
+ }
+ }
+ return mView.updateIconVisibility(!invisible);
+ }
+
+ private void updateClickability() {
+ if (mView == null) {
+ return;
+ }
+ boolean canLock = mKeyguardStateController.isMethodSecure()
+ && mKeyguardStateController.canDismissLockScreen();
+ boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
+ mView.setClickable(clickToUnlock);
+ mView.setLongClickable(canLock && !clickToUnlock);
+ mView.setFocusable(mAccessibilityController.isAccessibilityEnabled());
+ }
private final StatusBarStateController.StateListener mSBStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozingChanged(boolean isDozing) {
- setDozing(isDozing);
+ if (mDozing != isDozing) {
+ mDozing = isDozing;
+ update();
+ }
}
@Override
public void onDozeAmountChanged(float linear, float eased) {
- if (mLockIcon != null) {
- mLockIcon.setDozeAmount(eased);
+ if (mView != null) {
+ mView.setDozeAmount(eased);
}
}
@@ -160,29 +419,21 @@
@Override
public void onDensityOrFontScaleChanged() {
- if (mLockIcon == null) {
- return;
- }
-
- ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams();
+ ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp == null) {
return;
}
- lp.width = mLockIcon.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
- lp.height = mLockIcon.getResources().getDimensionPixelSize(
+ lp.width = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
+ lp.height = mView.getResources().getDimensionPixelSize(
R.dimen.keyguard_lock_height);
- mLockIcon.setLayoutParams(lp);
+ mView.setLayoutParams(lp);
update(true /* force */);
}
@Override
public void onLocaleListChanged() {
- if (mLockIcon == null) {
- return;
- }
-
- mLockIcon.setContentDescription(
- mLockIcon.getResources().getText(R.string.accessibility_unlock_button));
+ mView.setContentDescription(
+ mView.getResources().getText(R.string.accessibility_unlock_button));
update(true /* force */);
}
@@ -299,9 +550,9 @@
if (fingerprintRunning && unlockingAllowed) {
AccessibilityNodeInfo.AccessibilityAction unlock =
new AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLICK,
- mResources.getString(
- R.string.accessibility_unlock_without_fingerprint));
+ AccessibilityNodeInfo.ACTION_CLICK,
+ mResources.getString(
+ R.string.accessibility_unlock_without_fingerprint));
info.addAction(unlock);
info.setHintText(mResources.getString(
R.string.accessibility_waiting_for_fingerprint));
@@ -313,277 +564,4 @@
}
}
};
- private int mLastState;
-
- @Inject
- public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- LockPatternUtils lockPatternUtils,
- ShadeController shadeController,
- AccessibilityController accessibilityController,
- KeyguardIndicationController keyguardIndicationController,
- StatusBarStateController statusBarStateController,
- ConfigurationController configurationController,
- NotificationWakeUpCoordinator notificationWakeUpCoordinator,
- KeyguardBypassController keyguardBypassController,
- @Nullable DockManager dockManager,
- KeyguardStateController keyguardStateController,
- @Main Resources resources,
- HeadsUpManagerPhone headsUpManagerPhone,
- KeyguardSecurityModel keyguardSecurityModel) {
- mLockscreenGestureLogger = lockscreenGestureLogger;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mLockPatternUtils = lockPatternUtils;
- mShadeController = shadeController;
- mAccessibilityController = accessibilityController;
- mKeyguardIndicationController = keyguardIndicationController;
- mStatusBarStateController = statusBarStateController;
- mConfigurationController = configurationController;
- mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
- mKeyguardBypassController = keyguardBypassController;
- mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
- mKeyguardStateController = keyguardStateController;
- mResources = resources;
- mHeadsUpManagerPhone = headsUpManagerPhone;
- mKeyguardSecurityModel = keyguardSecurityModel;
-
- mKeyguardIndicationController.setLockIconController(this);
- }
-
- /**
- * Associate the controller with a {@link LockIcon}
- *
- * TODO: change to an init method and inject the view.
- */
- public void attach(LockIcon lockIcon) {
- mLockIcon = lockIcon;
-
- mLockIcon.setOnClickListener(this::handleClick);
- mLockIcon.setOnLongClickListener(this::handleLongClick);
- mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
-
- if (mLockIcon.isAttachedToWindow()) {
- mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
- }
- mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- setStatusBarState(mStatusBarStateController.getState());
- }
-
- public LockIcon getView() {
- return mLockIcon;
- }
-
- /**
- * Called whenever the scrims become opaque, transparent or semi-transparent.
- */
- public void onScrimVisibilityChanged(Integer scrimsVisible) {
- if (mWakeAndUnlockRunning
- && scrimsVisible == ScrimController.TRANSPARENT) {
- mWakeAndUnlockRunning = false;
- update();
- }
- }
-
- /**
- * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
- * icon on top of the black front scrim.
- * We also want to halt padlock the animation when we're in face bypass mode or dismissing the
- * keyguard with fingerprint.
- * @param wakeAndUnlock are we wake and unlocking
- * @param isUnlock are we currently unlocking
- */
- public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
- BiometricSourceType type) {
- if (wakeAndUnlock) {
- mWakeAndUnlockRunning = true;
- }
- mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
- if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
- && canBlockUpdates()) {
- // We don't want the icon to change while we are unlocking
- mBlockUpdates = true;
- }
- update();
- }
-
- /**
- * When we're launching an affordance, like double pressing power to open camera.
- */
- public void onShowingLaunchAffordanceChanged(Boolean showing) {
- mShowingLaunchAffordance = showing;
- update();
- }
-
- /** Sets whether the bouncer is showing. */
- public void setBouncerShowingScrimmed(boolean showing, boolean scrimmed) {
- mBouncerShowingScrimmed = scrimmed;
- update();
- }
-
- /**
- * Sets how hidden the bouncer is, where 0f is fully visible and 1f is fully hidden
- * See {@link KeyguardBouncer#EXPANSION_VISIBLE} and {@link KeyguardBouncer#EXPANSION_HIDDEN}.
- */
- public void setBouncerHideAmount(float hideAmount) {
- mBouncerHiddenAmount = hideAmount;
- updateColor();
- }
-
- private void updateColor() {
- if (mLockIcon == null) {
- return;
- }
-
- int iconColor = -1;
- if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_VISIBLE) {
- TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
- null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
- iconColor = typedArray.getColor(0, Color.WHITE);
- typedArray.recycle();
- } else if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_HIDDEN) {
- iconColor = Utils.getColorAttrDefaultColor(
- mLockIcon.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
- } else {
- // bouncer is transitioning
- TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
- null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
- int bouncerIconColor = typedArray.getColor(0, Color.WHITE);
- typedArray.recycle();
- int keyguardIconColor = Utils.getColorAttrDefaultColor(
- mLockIcon.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
- iconColor = (int) new ArgbEvaluator().evaluate(
- mBouncerHiddenAmount, bouncerIconColor, keyguardIconColor);
- }
- mLockIcon.updateColor(iconColor);
- }
-
- /**
- * Animate padlock opening when bouncer challenge is solved.
- */
- public void onBouncerPreHideAnimation() {
- update();
- }
-
- /**
- * If we're currently presenting an authentication error message.
- */
- public void setTransientBiometricsError(boolean transientBiometricsError) {
- mTransientBiometricsError = transientBiometricsError;
- update();
- }
-
- private boolean handleLongClick(View view) {
- mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
- 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
- mKeyguardIndicationController.showTransientIndication(
- R.string.keyguard_indication_trust_disabled);
- mKeyguardUpdateMonitor.onLockIconPressed();
- mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
-
- return true;
- }
-
-
- private void handleClick(View view) {
- if (!mAccessibilityController.isAccessibilityEnabled()) {
- return;
- }
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
- }
-
- private void update() {
- update(false /* force */);
- }
-
- private void update(boolean force) {
- int state = getState();
- boolean shouldUpdate = mLastState != state || force;
- if (mBlockUpdates && canBlockUpdates()) {
- shouldUpdate = false;
- }
- if (shouldUpdate && mLockIcon != null && mLockIcon.getVisibility() != GONE) {
- mLockIcon.update(state,
- mStatusBarStateController.isDozing(), mKeyguardJustShown);
- }
- mLastState = state;
- mKeyguardJustShown = false;
- updateIconVisibility();
- updateClickability();
- }
-
- private int getState() {
- if ((mKeyguardStateController.canDismissLockScreen()
- || !mKeyguardStateController.isShowing()
- || mKeyguardStateController.isKeyguardGoingAway()
- || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
- return STATE_LOCK_OPEN;
- } else if (mTransientBiometricsError) {
- return STATE_BIOMETRICS_ERROR;
- } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
- return STATE_SCANNING_FACE;
- } else {
- return STATE_LOCKED;
- }
- }
-
- private boolean canBlockUpdates() {
- return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
- }
-
- private void setDozing(boolean isDozing) {
- update();
- }
-
- /** Set the StatusBarState. */
- private void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
- updateIconVisibility();
- }
-
- /**
- * Update the icon visibility
- * @return true if the visibility changed
- */
- private boolean updateIconVisibility() {
- if (mLockIcon == null) {
- return false;
- }
-
- if (!mKeyguardUpdateMonitor.shouldShowLockIcon()) {
- boolean changed = mLockIcon.getVisibility() != GONE;
- mLockIcon.setVisibility(GONE);
- return changed;
- }
-
- boolean onAodOrDocked = mStatusBarStateController.isDozing() || mDocked;
- boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
- boolean fingerprintOrBypass = mFingerprintUnlock
- || mKeyguardBypassController.getBypassEnabled();
- if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
- if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
- || mHeadsUpManagerPhone.hasPinnedHeadsUp()
- || mStatusBarState == StatusBarState.KEYGUARD
- || mStatusBarState == StatusBarState.SHADE)
- && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
- invisible = true;
- }
- }
- return mLockIcon.updateIconVisibility(!invisible);
- }
-
- private void updateClickability() {
- if (mAccessibilityController == null) {
- return;
- }
- boolean canLock = mKeyguardStateController.isMethodSecure()
- && mKeyguardStateController.canDismissLockScreen();
- boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
- if (mLockIcon != null) {
- mLockIcon.setClickable(clickToUnlock);
- mLockIcon.setLongClickable(canLock && !clickToUnlock);
- mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
- }
- }
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index b24d0e7..a5284f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -910,7 +910,7 @@
clockPreferredY, hasCustomClock(),
hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
bypassEnabled, getUnlockedStackScrollerPadding(),
- mUpdateMonitor.shouldShowLockIcon(),
+ mUpdateMonitor.canShowLockIcon(),
getQsExpansionFraction(),
mDisplayCutoutTopInset);
mClockPositionAlgorithm.run(mClockPositionResult);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 8365139..4a3d8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -368,7 +368,6 @@
protected NotificationShadeWindowController mNotificationShadeWindowController;
protected StatusBarWindowController mStatusBarWindowController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final LockscreenLockIconController mLockscreenLockIconController;
@VisibleForTesting
DozeServiceHost mDozeServiceHost;
private boolean mWakeUpComingFromTouch;
@@ -415,6 +414,7 @@
// expanded notifications
// the sliding/resizing panel within the notification window
protected NotificationPanelViewController mNotificationPanelViewController;
+ protected LockscreenLockIconController mLockscreenLockIconController;
// settings
private QSPanelController mQSPanelController;
@@ -725,7 +725,6 @@
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
- LockscreenLockIconController lockscreenLockIconController,
DozeParameters dozeParameters,
ScrimController scrimController,
@Nullable KeyguardLiftController keyguardLiftController,
@@ -807,7 +806,6 @@
mAssistManagerLazy = assistManagerLazy;
mConfigurationController = configurationController;
mNotificationShadeWindowController = notificationShadeWindowController;
- mLockscreenLockIconController = lockscreenLockIconController;
mDozeServiceHost = dozeServiceHost;
mPowerManager = powerManager;
mDozeParameters = dozeParameters;
@@ -1173,9 +1171,7 @@
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
- if (mNotificationShadeWindowView != null) {
- mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
- }
+ mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
});
mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
@@ -1208,9 +1204,6 @@
createUserSwitcher();
}
- mNotificationPanelViewController.setLaunchAffordanceListener(
- mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
-
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
@@ -1490,6 +1483,11 @@
mStatusBarWindowController = statusBarComponent.getStatusBarWindowController();
mPhoneStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
+ mLockscreenLockIconController = statusBarComponent.getLockscreenLockIconController();
+ mLockscreenLockIconController.init();
+
+ mNotificationPanelViewController.setLaunchAffordanceListener(
+ mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
}
protected void startKeyguard() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 5f90077..f6165f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -316,11 +316,23 @@
mIconController.removeAllIconsForSlot(mSlotMobile);
mMobileStates.clear();
+ List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>();
+ noCallingStates.addAll(mNoCallingStates);
mNoCallingStates.clear();
final int n = subs.size();
for (int i = 0; i < n; i++) {
mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
- mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+ boolean isNewSub = true;
+ for (NoCallingIconState state : noCallingStates) {
+ if (state.subId == subs.get(i).getSubscriptionId()) {
+ mNoCallingStates.add(state);
+ isNewSub = false;
+ break;
+ }
+ }
+ if (isNewSub) {
+ mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index 802da3e..ecd9613 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -18,6 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import com.android.systemui.statusbar.phone.LockscreenLockIconController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
@@ -73,4 +74,9 @@
@StatusBarScope
NotificationPanelViewController getNotificationPanelViewController();
+ /**
+ * Creates a LockscreenLockIconController.
+ */
+ @StatusBarScope
+ LockscreenLockIconController getLockscreenLockIconController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 26e1959..9e9533d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -77,7 +77,6 @@
import com.android.systemui.statusbar.phone.KeyguardLiftController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightsOutNotifController;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
@@ -167,7 +166,6 @@
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
- LockscreenLockIconController lockscreenLockIconController,
DozeParameters dozeParameters,
ScrimController scrimController,
@Nullable KeyguardLiftController keyguardLiftController,
@@ -248,7 +246,6 @@
assistManagerLazy,
configurationController,
notificationShadeWindowController,
- lockscreenLockIconController,
dozeParameters,
scrimController,
keyguardLiftController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 37d8c9a..781abe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -16,6 +16,10 @@
package com.android.systemui.statusbar.phone.dagger;
+import android.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
@@ -32,4 +36,12 @@
return notificationShadeWindowView.getNotificationPanelView();
}
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ @Nullable
+ public static LockIcon getLockIcon(
+ NotificationShadeWindowView notificationShadeWindowView) {
+ return notificationShadeWindowView.findViewById(R.id.lock_icon);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 2546813..f86b465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -295,8 +295,9 @@
)
controller.showDialog(context)
exhaustExecutors()
- assertThat(dialogProvider.list).hasSize(1)
+ assertThat(dialogProvider.list).hasSize(2)
assertThat(dialogProvider.list?.get(0)?.lastActiveTimestamp).isEqualTo(1L)
+ assertThat(dialogProvider.list?.get(1)?.lastActiveTimestamp).isEqualTo(0L)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
index 9762fff..eb5dd4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
@@ -72,7 +72,7 @@
)
dialog = PrivacyDialog(context, list, starter)
dialog.show()
- dialog.requireViewById<View>(R.id.link).callOnClick()
+ dialog.requireViewById<View>(R.id.privacy_item).callOnClick()
verify(starter).invoke(PrivacyType.TYPE_MICROPHONE.permGroupName)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 53ff957..fe2f5f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -146,7 +146,8 @@
mAssistantFeedbackController);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically demoted to Silent by the system. "
- + "Was this correct?", prompt.getText().toString());
+ + "Let the developer know your feedback. Was this correct?",
+ prompt.getText().toString());
}
@Test
@@ -157,7 +158,8 @@
mAssistantFeedbackController);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically ranked higher in your shade. "
- + "Was this correct?", prompt.getText().toString());
+ + "Let the developer know your feedback. Was this correct?",
+ prompt.getText().toString());
}
@Test
@@ -168,7 +170,7 @@
mAssistantFeedbackController);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically promoted to Default by the system. "
- + "Was this correct?",
+ + "Let the developer know your feedback. Was this correct?",
prompt.getText().toString());
}
@@ -180,7 +182,8 @@
mAssistantFeedbackController);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically ranked lower in your shade. "
- + "Was this correct?", prompt.getText().toString());
+ + "Let the developer know your feedback. Was this correct?",
+ prompt.getText().toString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 23c0930..bdde822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -88,7 +88,6 @@
@Mock private NotificationPanelViewController mNotificationPanel;
@Mock private View mAmbientIndicationContainer;
@Mock private BiometricUnlockController mBiometricUnlockController;
- @Mock private LockscreenLockIconController mLockscreenLockIconController;
@Mock private AuthController mAuthController;
@Before
@@ -100,7 +99,7 @@
mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mPulseExpansionHandler,
mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
- mLockscreenLockIconController, mAuthController, mNotificationIconAreaController);
+ mAuthController, mNotificationIconAreaController);
mDozeServiceHost.initialize(
mStatusBar,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
index 95a3505..60af16a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
@@ -18,7 +18,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,12 +29,12 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -45,6 +44,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,7 +64,7 @@
@Mock
private KeyguardIndicationController mKeyguardIndicationController;
@Mock
- private LockIcon mLockIcon; // TODO: make this not a mock once inject is removed.
+ private LockIcon mLockIcon;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -81,34 +81,36 @@
private Resources mResources;
@Mock
private HeadsUpManagerPhone mHeadsUpManagerPhone;
- @Mock
- private KeyguardSecurityModel mKeyguardSecurityModel;
private LockscreenLockIconController mLockIconController;
+
+ @Captor ArgumentCaptor<OnAttachStateChangeListener> mOnAttachStateChangeCaptor;
+ @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+
private OnAttachStateChangeListener mOnAttachStateChangeListener;
+ private StatusBarStateController.StateListener mStatusBarStateListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mKeyguardUpdateMonitor.shouldShowLockIcon()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.canShowLockIcon()).thenReturn(true);
when(mLockIcon.getContext()).thenReturn(mContext);
- mLockIconController = new LockscreenLockIconController(
+ mLockIconController = new LockscreenLockIconController(mLockIcon,
mLockscreenGestureLogger, mKeyguardUpdateMonitor, mLockPatternUtils,
mShadeController, mAccessibilityController, mKeyguardIndicationController,
mStatusBarStateController, mConfigurationController, mNotificationWakeUpCoordinator,
mKeyguardBypassController, mDockManager, mKeyguardStateController, mResources,
- mHeadsUpManagerPhone, mKeyguardSecurityModel);
+ mHeadsUpManagerPhone);
- ArgumentCaptor<OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
- ArgumentCaptor.forClass(OnAttachStateChangeListener.class);
+ when(mLockIcon.isAttachedToWindow()).thenReturn(true);
+ mLockIconController.init();
- doNothing().when(mLockIcon)
- .addOnAttachStateChangeListener(
- onAttachStateChangeListenerArgumentCaptor.capture());
- mLockIconController.attach(mLockIcon);
-
- mOnAttachStateChangeListener = onAttachStateChangeListenerArgumentCaptor.getValue();
+ verify(mLockIcon).addOnAttachStateChangeListener(
+ mOnAttachStateChangeCaptor.capture());
+ mOnAttachStateChangeListener = mOnAttachStateChangeCaptor.getValue();
+ verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+ mStatusBarStateListener = mStateListenerCaptor.getValue();
}
@Test
@@ -133,23 +135,17 @@
@Test
public void testVisibility_Dozing() {
- ArgumentCaptor<StatusBarStateController.StateListener> sBStateListenerCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-
- mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
- verify(mStatusBarStateController).addCallback(sBStateListenerCaptor.capture());
-
when(mStatusBarStateController.isDozing()).thenReturn(true);
- sBStateListenerCaptor.getValue().onDozingChanged(true);
+ mStatusBarStateListener.onDozingChanged(true);
verify(mLockIcon).updateIconVisibility(false);
}
@Test
public void testVisibility_doNotShowLockIcon() {
- when(mKeyguardUpdateMonitor.shouldShowLockIcon()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.canShowLockIcon()).thenReturn(false);
+ mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
- mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
verify(mLockIcon).setVisibility(View.GONE);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 2c781ba..cae488a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -386,7 +386,6 @@
() -> mAssistManager,
configurationController,
mNotificationShadeWindowController,
- mLockscreenLockIconController,
mDozeParameters,
mScrimController,
mKeyguardLiftController,
@@ -436,6 +435,7 @@
// initialized automatically.
mStatusBar.mNotificationShadeWindowView = mNotificationShadeWindowView;
mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
+ mStatusBar.mLockscreenLockIconController = mLockscreenLockIconController;
mStatusBar.mDozeScrimController = mDozeScrimController;
mStatusBar.mPresenter = mNotificationPresenter;
mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index da1f5d3..f8b6383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -37,6 +37,7 @@
import android.app.Instrumentation;
import android.content.Intent;
import android.net.ConnectivityManager;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
@@ -343,6 +344,8 @@
setConnectivityCommon(networkType, validated, isConnected);
if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
if (isConnected) {
+ mNetworkCallback.onAvailable(mock(Network.class),
+ new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
mNetworkCallback.onCapabilitiesChanged(
mock(Network.class), new NetworkCapabilities(mNetCapabilities));
} else {
@@ -357,6 +360,8 @@
setConnectivityCommon(networkType, validated, isConnected);
if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
if (isConnected) {
+ mNetworkCallback.onAvailable(mock(Network.class),
+ new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
mNetworkCallback.onCapabilitiesChanged(
mock(Network.class), new NetworkCapabilities(mNetCapabilities));
} else {
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index ae024ff..5dd271c 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -260,6 +260,10 @@
// Package: android
NOTE_ADB_WIFI_ACTIVE = 62;
+ // Notify the user a carrier suggestion is available to get IMSI exemption.
+ // Package: android
+ NOTE_CARRIER_SUGGESTION_AVAILABLE = 63;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/Android.bp b/services/Android.bp
index 1970b7d..61591c2 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -28,6 +28,7 @@
":services.profcollect-sources",
":services.restrictions-sources",
":services.searchui-sources",
+ ":services.smartspace-sources",
":services.speech-sources",
":services.startop.iorap-sources",
":services.systemcaptions-sources",
@@ -77,6 +78,7 @@
"services.profcollect",
"services.restrictions",
"services.searchui",
+ "services.smartspace",
"services.speech",
"services.startop",
"services.systemcaptions",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4f0efb4..c091dfa 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -216,8 +216,6 @@
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.utils.PriorityDump;
-import com.google.android.collect.Lists;
-
import libcore.io.IoUtils;
import java.io.FileDescriptor;
@@ -1577,7 +1575,7 @@
@Override
public NetworkInfo[] getAllNetworkInfo() {
enforceAccessPermission();
- final ArrayList<NetworkInfo> result = Lists.newArrayList();
+ final ArrayList<NetworkInfo> result = new ArrayList<>();
for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
networkType++) {
NetworkInfo info = getNetworkInfo(networkType);
@@ -1851,7 +1849,7 @@
// This contains IMSI details, so make sure the caller is privileged.
NetworkStack.checkNetworkStackPermission(mContext);
- final ArrayList<NetworkState> result = Lists.newArrayList();
+ final ArrayList<NetworkState> result = new ArrayList<>();
for (Network network : getAllNetworks()) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai != null) {
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 8b506ba..41903fc 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -372,10 +372,12 @@
* even from a previous boot.
*/
public void unregisterHealthObserver(PackageHealthObserver observer) {
- synchronized (mLock) {
- mAllObservers.remove(observer.getName());
- }
- syncState("unregistering observer: " + observer.getName());
+ mLongTaskHandler.post(() -> {
+ synchronized (mLock) {
+ mAllObservers.remove(observer.getName());
+ }
+ syncState("unregistering observer: " + observer.getName());
+ });
}
/**
@@ -982,7 +984,11 @@
if (!DeviceConfig.NAMESPACE_ROLLBACK.equals(properties.getNamespace())) {
return;
}
- updateConfigs();
+ try {
+ updateConfigs();
+ } catch (Exception ignore) {
+ Slog.w(TAG, "Failed to reload device config changes");
+ }
});
}
@@ -990,7 +996,8 @@
* Health check is enabled or disabled after reading the flags
* from DeviceConfig.
*/
- private void updateConfigs() {
+ @VisibleForTesting
+ void updateConfigs() {
synchronized (mLock) {
mTriggerFailureCount = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ROLLBACK,
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index a08d066..e96fd39 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -32,6 +32,7 @@
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
+import android.net.NetworkStack;
import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.net.TestNetworkInterface;
@@ -48,6 +49,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.NetdUtils;
import java.io.UncheckedIOException;
import java.net.Inet4Address;
@@ -317,10 +319,10 @@
}
try {
- // This requires NETWORK_STACK privileges.
final long token = Binder.clearCallingIdentity();
try {
- mNMS.setInterfaceUp(iface);
+ NetworkStack.checkNetworkStackPermission(mContext);
+ NetdUtils.setInterfaceUp(mNetd, iface);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 8562b0d..6a72010 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -27,10 +27,12 @@
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.wifi.WifiInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -291,6 +293,12 @@
@NonNull VcnConfig config) {
return new Vcn(vcnContext, subscriptionGroup, config);
}
+
+ /** Gets the subId indicated by the given {@link WifiInfo}. */
+ public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) {
+ // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
}
/** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -582,8 +590,36 @@
"Must have permission NETWORK_FACTORY or be the SystemServer to get underlying"
+ " Network policies");
- // TODO(b/175914059): implement policy generation once VcnManagementService is able to
- // determine policies
+ // Defensive copy in case this call is in-process and the given NetworkCapabilities mutates
+ networkCapabilities = new NetworkCapabilities(networkCapabilities);
+
+ int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && networkCapabilities.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
+ TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ (TelephonyNetworkSpecifier) networkCapabilities.getNetworkSpecifier();
+ subId = telephonyNetworkSpecifier.getSubscriptionId();
+ } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+ && networkCapabilities.getTransportInfo() instanceof WifiInfo) {
+ WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
+ subId = mDeps.getSubIdForWifiInfo(wifiInfo);
+ }
+
+ boolean isVcnManagedNetwork = false;
+ if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ synchronized (mLock) {
+ ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);
+
+ // TODO(b/178140910): only mark the Network as VCN-managed if not in safe mode
+ if (mVcns.containsKey(subGroup)) {
+ isVcnManagedNetwork = true;
+ }
+ }
+ }
+ if (isVcnManagedNetwork) {
+ networkCapabilities.removeCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ }
return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
}
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index eca1dfa..6a816af 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -22,12 +22,20 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.vibrator.IVibrator;
+import android.os.BatteryStats;
+import android.os.Binder;
import android.os.CombinedVibrationEffect;
+import android.os.ExternalVibration;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IExternalVibratorService;
import android.os.IVibratorManagerService;
+import android.os.IVibratorStateListener;
import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.Trace;
@@ -37,12 +45,17 @@
import android.os.VibratorInfo;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.DumpUtils;
+import com.android.server.vibrator.InputDeviceDelegate;
import com.android.server.vibrator.Vibration;
import com.android.server.vibrator.VibrationScaler;
import com.android.server.vibrator.VibrationSettings;
+import com.android.server.vibrator.VibrationThread;
import com.android.server.vibrator.VibratorController;
import libcore.util.NativeAllocationRegistry;
@@ -50,7 +63,11 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -83,18 +100,32 @@
}
}
+ // Used to generate globally unique vibration ids.
+ private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
+
private final Object mLock = new Object();
private final Context mContext;
+ private final PowerManager.WakeLock mWakeLock;
+ private final IBatteryStats mBatteryStatsService;
private final Handler mHandler;
private final AppOpsManager mAppOps;
private final NativeWrapper mNativeWrapper;
+ private final VibratorManagerRecords mVibratorManagerRecords;
private final int[] mVibratorIds;
private final SparseArray<VibratorController> mVibrators;
+ private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
+ @GuardedBy("mLock")
+ private VibrationThread mCurrentVibration;
+ @GuardedBy("mLock")
+ private VibrationThread mNextVibration;
+ @GuardedBy("mLock")
+ private ExternalVibrationHolder mCurrentExternalVibration;
private VibrationSettings mVibrationSettings;
private VibrationScaler mVibrationScaler;
+ private InputDeviceDelegate mInputDeviceDelegate;
static native long nativeInit();
@@ -109,8 +140,19 @@
mNativeWrapper = injector.getNativeWrapper();
mNativeWrapper.init();
+ int dumpLimit = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_previousVibrationsDumpLimit);
+ mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+
+ mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+
mAppOps = mContext.getSystemService(AppOpsManager.class);
+ PowerManager pm = context.getSystemService(PowerManager.class);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
+ mWakeLock.setReferenceCounted(true);
+
int[] vibratorIds = mNativeWrapper.getVibratorIds();
if (vibratorIds == null) {
mVibratorIds = new int[0];
@@ -134,6 +176,7 @@
try {
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+ mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
mVibrationSettings.addListener(this::updateServiceState);
@@ -145,6 +188,11 @@
}
@Override // Binder call
+ public int[] getVibratorIds() {
+ return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+ }
+
+ @Override // Binder call
@Nullable
public VibratorInfo getVibratorInfo(int vibratorId) {
VibratorController controller = mVibrators.get(vibratorId);
@@ -152,8 +200,37 @@
}
@Override // Binder call
- public int[] getVibratorIds() {
- return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+ public boolean isVibrating(int vibratorId) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+ "isVibrating");
+ VibratorController controller = mVibrators.get(vibratorId);
+ return controller != null && controller.isVibrating();
+ }
+
+ @Override // Binder call
+ public boolean registerVibratorStateListener(int vibratorId, IVibratorStateListener listener) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+ "registerVibratorStateListener");
+ VibratorController controller = mVibrators.get(vibratorId);
+ if (controller == null) {
+ return false;
+ }
+ return controller.registerVibratorStateListener(listener);
+ }
+
+ @Override // Binder call
+ public boolean unregisterVibratorStateListener(int vibratorId,
+ IVibratorStateListener listener) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+ "unregisterVibratorStateListener");
+ VibratorController controller = mVibrators.get(vibratorId);
+ if (controller == null) {
+ return false;
+ }
+ return controller.unregisterVibratorStateListener(listener);
}
@Override // Binder call
@@ -161,9 +238,10 @@
@Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attrs) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
try {
- if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
- throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
- }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.VIBRATE_ALWAYS_ON,
+ "setAlwaysOnEffect");
+
if (effect == null) {
synchronized (mLock) {
mAlwaysOnEffects.delete(alwaysOnId);
@@ -200,12 +278,113 @@
@Override // Binder call
public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
- throw new UnsupportedOperationException("Not implemented");
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
+ try {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
+
+ if (token == null) {
+ Slog.e(TAG, "token must not be null");
+ return;
+ }
+ enforceUpdateAppOpsStatsPermission(uid);
+ if (!isEffectValid(effect)) {
+ return;
+ }
+ effect = fixupVibrationEffect(effect);
+ attrs = fixupVibrationAttributes(attrs);
+ Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
+ uid, opPkg, reason);
+
+ synchronized (mLock) {
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
+ if (ignoreStatus != null) {
+ endVibrationLocked(vib, ignoreStatus);
+ return;
+ }
+
+ VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
+ mBatteryStatsService, mVibrationCallbacks);
+
+ ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread);
+ if (ignoreStatus != null) {
+ endVibrationLocked(vib, ignoreStatus);
+ return;
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mCurrentVibration != null) {
+ mCurrentVibration.cancel();
+ }
+ Vibration.Status status = startVibrationLocked(vibThread);
+ if (status != Vibration.Status.RUNNING) {
+ endVibrationLocked(vib, status);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
}
@Override // Binder call
public void cancelVibrate(IBinder token) {
- throw new UnsupportedOperationException("Not implemented");
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.VIBRATE,
+ "cancelVibrate");
+
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Canceling vibration.");
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mNextVibration = null;
+ if (mCurrentVibration != null
+ && mCurrentVibration.getVibration().token == token) {
+ mCurrentVibration.cancel();
+ }
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.end(Vibration.Status.CANCELLED);
+ mVibratorManagerRecords.record(mCurrentExternalVibration);
+ mCurrentExternalVibration.externalVibration.mute();
+ mCurrentExternalVibration = null;
+ // TODO(b/167946816): set external control to false
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ final long ident = Binder.clearCallingIdentity();
+
+ boolean isDumpProto = false;
+ for (String arg : args) {
+ if (arg.equals("--proto")) {
+ isDumpProto = true;
+ }
+ }
+ try {
+ if (isDumpProto) {
+ mVibratorManagerRecords.dumpProto(fd);
+ } else {
+ mVibratorManagerRecords.dumpText(pw);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override
@@ -214,11 +393,24 @@
new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver);
}
- private void updateServiceState() {
+ @VisibleForTesting
+ void updateServiceState() {
synchronized (mLock) {
+ boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
+ mVibrationSettings.shouldVibrateInputDevices());
+
for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
updateAlwaysOnLocked(mAlwaysOnEffects.valueAt(i));
}
+
+ if (mCurrentVibration == null) {
+ return;
+ }
+
+ if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
+ mCurrentVibration.getVibration().attrs.getUsage())) {
+ mCurrentVibration.cancel();
+ }
}
}
@@ -230,9 +422,9 @@
if (vibrator == null) {
continue;
}
- Vibration.Status ignoredStatus = shouldIgnoreVibrationLocked(
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
vib.uid, vib.opPkg, vib.attrs);
- if (ignoredStatus == null) {
+ if (ignoreStatus == null) {
effect = mVibrationScaler.scale(effect, vib.attrs.getUsage());
} else {
// Vibration should not run, use null effect to remove registered effect.
@@ -242,6 +434,135 @@
}
}
+ @GuardedBy("mLock")
+ private Vibration.Status startVibrationLocked(VibrationThread vibThread) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
+ try {
+ Vibration vib = vibThread.getVibration();
+ vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
+
+ boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
+ vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
+
+ if (inputDevicesAvailable) {
+ return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
+ }
+
+ if (mCurrentVibration == null) {
+ return startVibrationThreadLocked(vibThread);
+ }
+
+ mNextVibration = vibThread;
+ return Vibration.Status.RUNNING;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private Vibration.Status startVibrationThreadLocked(VibrationThread vibThread) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
+ try {
+ Vibration vib = vibThread.getVibration();
+ int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
+ switch (mode) {
+ case AppOpsManager.MODE_ALLOWED:
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ mCurrentVibration = vibThread;
+ mCurrentVibration.start();
+ return Vibration.Status.RUNNING;
+ case AppOpsManager.MODE_ERRORED:
+ Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
+ return Vibration.Status.IGNORED_ERROR_APP_OPS;
+ default:
+ return Vibration.Status.IGNORED_APP_OPS;
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void endVibrationLocked(Vibration vib, Vibration.Status status) {
+ vib.end(status);
+ mVibratorManagerRecords.record(vib);
+ }
+
+ @GuardedBy("mLock")
+ private void reportFinishedVibrationLocked(Vibration.Status status) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ try {
+ Vibration vib = mCurrentVibration.getVibration();
+ mCurrentVibration = null;
+ endVibrationLocked(vib, status);
+ finishAppOpModeLocked(vib.uid, vib.opPkg);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private void onVibrationComplete(int vibratorId, long vibrationId) {
+ synchronized (mLock) {
+ if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ + " complete, notifying thread");
+ }
+ mCurrentVibration.vibratorComplete(vibratorId);
+ }
+ }
+ }
+
+ /**
+ * Check if given vibration should be ignored in favour of one of the vibrations currently
+ * running on the same vibrators.
+ *
+ * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) {
+ if (vibThread.getVibration().isRepeating()) {
+ // Repeating vibrations always take precedence.
+ return null;
+ }
+ if (mCurrentVibration != null && mCurrentVibration.getVibration().isRepeating()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration in favor of previous alarm vibration");
+ }
+ return Vibration.Status.IGNORED_FOR_ALARM;
+ }
+ return null;
+ }
+
+ /**
+ * Check if given vibration should be ignored by this service.
+ *
+ * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+ * @see #shouldIgnoreVibrationLocked(int, String, VibrationAttributes)
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private Vibration.Status shouldIgnoreVibrationLocked(Vibration vib) {
+ // If something has external control of the vibrator, assume that it's more important.
+ if (mCurrentExternalVibration != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+ }
+ return Vibration.Status.IGNORED_FOR_EXTERNAL;
+ }
+
+ if (!mVibrationSettings.shouldVibrateForUid(vib.uid, vib.attrs.getUsage())) {
+ Slog.e(TAG, "Ignoring incoming vibration as process with"
+ + " uid= " + vib.uid + " is background,"
+ + " attrs= " + vib.attrs);
+ return Vibration.Status.IGNORED_BACKGROUND;
+ }
+
+ return shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
+ }
+
/**
* Check if a vibration with given {@code uid}, {@code opPkg} and {@code attrs} should be
* ignored by this service.
@@ -271,7 +592,7 @@
return Vibration.Status.IGNORED_RINGTONE;
}
- int mode = getAppOpMode(uid, opPkg, attrs);
+ int mode = checkAppOpModeLocked(uid, opPkg, attrs);
if (mode != AppOpsManager.MODE_ALLOWED) {
if (mode == AppOpsManager.MODE_ERRORED) {
// We might be getting calls from within system_server, so we don't actually
@@ -290,21 +611,49 @@
* Check which mode should be set for a vibration with given {@code uid}, {@code opPkg} and
* {@code attrs}. This will return one of the AppOpsManager.MODE_*.
*/
- private int getAppOpMode(int uid, String opPkg, VibrationAttributes attrs) {
+ @GuardedBy("mLock")
+ private int checkAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
attrs.getAudioUsage(), uid, opPkg);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg);
- }
- if (mode == AppOpsManager.MODE_IGNORED
- && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+ int fixedMode = fixupAppOpModeLocked(mode, attrs);
+ if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) {
// If we're just ignoring the vibration op then this is set by DND and we should ignore
// if we're asked to bypass. AppOps won't be able to record this operation, so make
// sure we at least note it in the logs for debugging.
Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
- mode = AppOpsManager.MODE_ALLOWED;
}
- return mode;
+ return fixedMode;
+ }
+
+ /** Start an operation in {@link AppOpsManager}, if allowed. */
+ @GuardedBy("mLock")
+ private int startAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
+ return fixupAppOpModeLocked(
+ mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg), attrs);
+ }
+
+ /**
+ * Finish a previously started operation in {@link AppOpsManager}. This will be a noop if no
+ * operation with same uid was previously started.
+ */
+ @GuardedBy("mLock")
+ private void finishAppOpModeLocked(int uid, String opPkg) {
+ mAppOps.finishOp(AppOpsManager.OP_VIBRATE, uid, opPkg);
+ }
+
+ /**
+ * Enforces {@link android.Manifest.permission#UPDATE_APP_OPS_STATS} to incoming UID if it's
+ * different from the calling UID.
+ */
+ private void enforceUpdateAppOpsStatsPermission(int uid) {
+ if (uid == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
}
/**
@@ -330,6 +679,49 @@
}
/**
+ * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
+ * VibrationSettings#getFallbackEffect}.
+ */
+ private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) {
+ if (effect instanceof CombinedVibrationEffect.Mono) {
+ return CombinedVibrationEffect.createSynced(
+ fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect()));
+ } else if (effect instanceof CombinedVibrationEffect.Stereo) {
+ CombinedVibrationEffect.SyncedCombination combination =
+ CombinedVibrationEffect.startSynced();
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibrationEffect.Stereo) effect).getEffects();
+ for (int i = 0; i < effects.size(); i++) {
+ combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i)));
+ }
+ return combination.combine();
+ } else if (effect instanceof CombinedVibrationEffect.Sequential) {
+ CombinedVibrationEffect.SequentialCombination combination =
+ CombinedVibrationEffect.startSequential();
+ List<CombinedVibrationEffect> effects =
+ ((CombinedVibrationEffect.Sequential) effect).getEffects();
+ for (CombinedVibrationEffect e : effects) {
+ combination.addNext(fixupVibrationEffect(e));
+ }
+ return combination.combine();
+ }
+ return effect;
+ }
+
+ private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
+ if (effect instanceof VibrationEffect.Prebaked
+ && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
+ VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
+ if (fallback != null) {
+ return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
+ fallback);
+ }
+ }
+ return effect;
+ }
+
+ /**
* Return new {@link VibrationAttributes} that only applies flags that this user has permissions
* to use.
*/
@@ -388,6 +780,19 @@
}
}
+ /**
+ * Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to
+ * allow bypassing {@link AppOpsManager} checks.
+ */
+ @GuardedBy("mLock")
+ private int fixupAppOpModeLocked(int mode, VibrationAttributes attrs) {
+ if (mode == AppOpsManager.MODE_IGNORED
+ && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ return mode;
+ }
+
private boolean hasPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
@@ -428,6 +833,42 @@
}
/**
+ * Implementation of {@link VibrationThread.VibrationCallbacks} that controls synced vibrations
+ * and reports them when finished.
+ */
+ private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
+
+ @Override
+ public void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds) {
+ // TODO(b/167946816): call IVibratorManager to prepare
+ }
+
+ @Override
+ public void triggerSyncedVibration(long vibrationId) {
+ // TODO(b/167946816): call IVibratorManager to trigger
+ }
+
+ @Override
+ public void onVibrationEnded(long vibrationId, Vibration.Status status) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration " + vibrationId + " thread finished with status " + status);
+ }
+ synchronized (mLock) {
+ if (mCurrentVibration != null
+ && mCurrentVibration.getVibration().id == vibrationId) {
+ reportFinishedVibrationLocked(status);
+
+ if (mNextVibration != null) {
+ VibrationThread vibThread = mNextVibration;
+ mNextVibration = null;
+ startVibrationThreadLocked(vibThread);
+ }
+ }
+ }
+ }
+ }
+
+ /**
* Implementation of {@link VibratorController.OnVibrationCompleteListener} with a weak
* reference to this service.
*/
@@ -443,7 +884,7 @@
public void onComplete(int vibratorId, long vibrationId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
- // TODO(b/159207608): finish vibration if all vibrators finished for this vibration
+ service.onVibrationComplete(vibratorId, vibrationId);
}
}
}
@@ -469,6 +910,41 @@
}
}
+ /** Holder for a {@link ExternalVibration}. */
+ private final class ExternalVibrationHolder {
+
+ public final ExternalVibration externalVibration;
+ public int scale;
+
+ private final long mStartTimeDebug;
+ private long mEndTimeDebug;
+ private Vibration.Status mStatus;
+
+ private ExternalVibrationHolder(ExternalVibration externalVibration) {
+ this.externalVibration = externalVibration;
+ this.scale = IExternalVibratorService.SCALE_NONE;
+ mStartTimeDebug = System.currentTimeMillis();
+ mStatus = Vibration.Status.RUNNING;
+ }
+
+ public void end(Vibration.Status status) {
+ if (mStatus != Vibration.Status.RUNNING) {
+ // Vibration already ended, keep first ending status set and ignore this one.
+ return;
+ }
+ mStatus = status;
+ mEndTimeDebug = System.currentTimeMillis();
+ }
+
+ public Vibration.DebugInfo getDebugInfo() {
+ return new Vibration.DebugInfo(
+ mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
+ scale, externalVibration.getVibrationAttributes(),
+ externalVibration.getUid(), externalVibration.getPackage(),
+ /* reason= */ null, mStatus);
+ }
+ }
+
/** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
@VisibleForTesting
public static class NativeWrapper {
@@ -494,8 +970,158 @@
}
}
+ /** Keep records of vibrations played and provide debug information for this service. */
+ private final class VibratorManagerRecords {
+ @GuardedBy("mLock")
+ private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations =
+ new SparseArray<>();
+ @GuardedBy("mLock")
+ private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations =
+ new LinkedList<>();
+ private final int mPreviousVibrationsLimit;
+
+ private VibratorManagerRecords(int limit) {
+ mPreviousVibrationsLimit = limit;
+ }
+
+ @GuardedBy("mLock")
+ void record(Vibration vib) {
+ int usage = vib.attrs.getUsage();
+ if (!mPreviousVibrations.contains(usage)) {
+ mPreviousVibrations.put(usage, new LinkedList<>());
+ }
+ record(mPreviousVibrations.get(usage), vib.getDebugInfo());
+ }
+
+ @GuardedBy("mLock")
+ void record(ExternalVibrationHolder vib) {
+ record(mPreviousExternalVibrations, vib.getDebugInfo());
+ }
+
+ @GuardedBy("mLock")
+ void record(LinkedList<Vibration.DebugInfo> records, Vibration.DebugInfo info) {
+ if (records.size() > mPreviousVibrationsLimit) {
+ records.removeFirst();
+ }
+ records.addLast(info);
+ }
+
+ void dumpText(PrintWriter pw) {
+ pw.println("Vibrator Manager Service:");
+ synchronized (mLock) {
+ pw.println(" mVibratorControllers:");
+ for (int i = 0; i < mVibrators.size(); i++) {
+ pw.println(" " + mVibrators.valueAt(i));
+ }
+ pw.println();
+ pw.println(" mCurrentVibration:");
+ pw.println(" " + mCurrentVibration == null
+ ? null : mCurrentVibration.getVibration().getDebugInfo());
+ pw.println(" mNextVibration:");
+ pw.println(" " + mNextVibration == null
+ ? null : mNextVibration.getVibration().getDebugInfo());
+ pw.println(" mCurrentExternalVibration:");
+ pw.println(" " + mCurrentExternalVibration == null
+ ? null : mCurrentExternalVibration.getDebugInfo());
+ pw.println();
+ pw.println(" mVibrationSettings=" + mVibrationSettings);
+ for (int i = 0; i < mPreviousVibrations.size(); i++) {
+ pw.println();
+ pw.print(" Previous vibrations for usage ");
+ pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
+ pw.println(":");
+ for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+ pw.println(" " + info);
+ }
+ }
+
+ pw.println(" Previous external vibrations:");
+ for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+ pw.println(" " + info);
+ }
+ }
+ }
+
+ synchronized void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ synchronized (mLock) {
+ mVibrationSettings.dumpProto(proto);
+ if (mCurrentVibration != null) {
+ mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+ VibratorServiceDumpProto.CURRENT_VIBRATION);
+ }
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+ VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+ }
+
+ boolean isVibrating = false;
+ boolean isUnderExternalControl = false;
+ for (int i = 0; i < mVibrators.size(); i++) {
+ isVibrating |= mVibrators.valueAt(i).isVibrating();
+ isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
+ }
+ proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating);
+ proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+ isUnderExternalControl);
+
+ for (Vibration.DebugInfo info : mPreviousVibrations.get(
+ VibrationAttributes.USAGE_RINGTONE)) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
+ }
+
+ for (Vibration.DebugInfo info : mPreviousVibrations.get(
+ VibrationAttributes.USAGE_NOTIFICATION)) {
+ info.dumpProto(proto,
+ VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
+ }
+
+ for (Vibration.DebugInfo info : mPreviousVibrations.get(
+ VibrationAttributes.USAGE_ALARM)) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
+ }
+
+ for (Vibration.DebugInfo info : mPreviousVibrations.get(
+ VibrationAttributes.USAGE_UNKNOWN)) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+ }
+
+ for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ }
+ }
+ proto.flush();
+ }
+ }
+
/** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
private final class VibratorManagerShellCommand extends ShellCommand {
+ public static final String SHELL_PACKAGE_NAME = "com.android.shell";
+
+ private final class CommonOptions {
+ public boolean force = false;
+ public String description = "Shell command";
+
+ CommonOptions() {
+ String nextArg;
+ while ((nextArg = peekNextArg()) != null) {
+ switch (nextArg) {
+ case "-f":
+ getNextArgRequired(); // consume the -f argument;
+ force = true;
+ break;
+ case "-d":
+ getNextArgRequired(); // consume the -d argument;
+ description = getNextArgRequired();
+ break;
+ default:
+ // Not a common option, finish reading.
+ return;
+ }
+ }
+ }
+ }
private final IBinder mToken;
@@ -505,10 +1131,27 @@
@Override
public int onCommand(String cmd) {
- if ("list".equals(cmd)) {
- return runListVibrators();
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onCommand " + cmd);
+ try {
+ if ("list".equals(cmd)) {
+ return runListVibrators();
+ }
+ if ("synced".equals(cmd)) {
+ return runMono();
+ }
+ if ("combined".equals(cmd)) {
+ return runStereo();
+ }
+ if ("sequential".equals(cmd)) {
+ return runSequential();
+ }
+ if ("cancel".equals(cmd)) {
+ return runCancel();
+ }
+ return handleDefaultCommands(cmd);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
- return handleDefaultCommands(cmd);
}
private int runListVibrators() {
@@ -525,6 +1168,157 @@
}
}
+ private int runMono() {
+ CommonOptions commonOptions = new CommonOptions();
+ VibrationEffect effect = nextEffect();
+ if (effect == null) {
+ return 0;
+ }
+
+ CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+ vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combinedEffect, attrs,
+ commonOptions.description, mToken);
+ return 0;
+ }
+
+ private int runStereo() {
+ CommonOptions commonOptions = new CommonOptions();
+ CombinedVibrationEffect.SyncedCombination combination =
+ CombinedVibrationEffect.startSynced();
+ while ("-v".equals(getNextOption())) {
+ int vibratorId = Integer.parseInt(getNextArgRequired());
+ VibrationEffect effect = nextEffect();
+ if (effect != null) {
+ combination.addVibrator(vibratorId, effect);
+ }
+ }
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+ vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
+ commonOptions.description, mToken);
+ return 0;
+ }
+
+ private int runSequential() {
+ CommonOptions commonOptions = new CommonOptions();
+
+ CombinedVibrationEffect.SequentialCombination combination =
+ CombinedVibrationEffect.startSequential();
+ while ("-v".equals(getNextOption())) {
+ int vibratorId = Integer.parseInt(getNextArgRequired());
+ int delay = 0;
+ if ("-w".equals(getNextOption())) {
+ delay = Integer.parseInt(getNextArgRequired());
+ }
+ VibrationEffect effect = nextEffect();
+ if (effect != null) {
+ combination.addNext(vibratorId, effect, delay);
+ }
+ }
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+ vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
+ commonOptions.description, mToken);
+ return 0;
+ }
+
+ private int runCancel() {
+ cancelVibrate(mToken);
+ return 0;
+ }
+
+ @Nullable
+ private VibrationEffect nextEffect() {
+ String effectType = getNextArgRequired();
+ if ("oneshot".equals(effectType)) {
+ return nextOneShot();
+ }
+ if ("waveform".equals(effectType)) {
+ return nextWaveform();
+ }
+ if ("prebaked".equals(effectType)) {
+ return nextPrebaked();
+ }
+ if ("composed".equals(effectType)) {
+ return nextComposed();
+ }
+ return null;
+ }
+
+ private VibrationEffect nextOneShot() {
+ boolean hasAmplitude = "-a".equals(getNextOption());
+ long duration = Long.parseLong(getNextArgRequired());
+ int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired())
+ : VibrationEffect.DEFAULT_AMPLITUDE;
+ return VibrationEffect.createOneShot(duration, amplitude);
+ }
+
+ private VibrationEffect nextWaveform() {
+ boolean hasAmplitudes = false;
+ int repeat = -1;
+
+ String nextOption = getNextOption();
+ while (nextOption != null) {
+ if ("-a".equals(nextOption)) {
+ hasAmplitudes = true;
+ } else if ("-r".equals(nextOption)) {
+ repeat = Integer.parseInt(getNextArgRequired());
+ }
+ nextOption = getNextOption();
+ }
+ List<Long> durations = new ArrayList<>();
+ List<Integer> amplitudes = new ArrayList<>();
+
+ String nextArg;
+ while ((nextArg = peekNextArg()) != null && !"-v".equals(nextArg)) {
+ durations.add(Long.parseLong(getNextArgRequired()));
+ if (hasAmplitudes) {
+ amplitudes.add(Integer.parseInt(getNextArgRequired()));
+ }
+ }
+
+ long[] durationArray = durations.stream().mapToLong(Long::longValue).toArray();
+ if (!hasAmplitudes) {
+ return VibrationEffect.createWaveform(durationArray, repeat);
+ }
+
+ int[] amplitudeArray = amplitudes.stream().mapToInt(Integer::intValue).toArray();
+ return VibrationEffect.createWaveform(durationArray, amplitudeArray, repeat);
+ }
+
+ private VibrationEffect nextPrebaked() {
+ boolean shouldFallback = "-b".equals(getNextOption());
+ int effectId = Integer.parseInt(getNextArgRequired());
+ return VibrationEffect.get(effectId, shouldFallback);
+ }
+
+ private VibrationEffect nextComposed() {
+ VibrationEffect.Composition composition = VibrationEffect.startComposition();
+ String nextArg;
+ while ((nextArg = peekNextArg()) != null) {
+ int delay = 0;
+ if ("-w".equals(nextArg)) {
+ getNextArgRequired(); // consume the -w option
+ delay = Integer.parseInt(getNextArgRequired());
+ } else if ("-v".equals(nextArg)) {
+ // Starting next vibrator, this composed effect if finished.
+ break;
+ }
+ int primitiveId = Integer.parseInt(getNextArgRequired());
+ composition.addPrimitive(primitiveId, /* scale= */ 1f, delay);
+ }
+ return composition.compose();
+ }
+
+ private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
+ final int flags =
+ commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0;
+ return new VibrationAttributes.Builder()
+ .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
+ // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
+ .setUsage(VibrationAttributes.USAGE_TOUCH)
+ .build();
+ }
+
@Override
public void onHelp() {
try (PrintWriter pw = getOutPrintWriter();) {
@@ -535,6 +1329,49 @@
pw.println(" list");
pw.println(" Prints the id of device vibrators. This does not include any ");
pw.println(" connected input device.");
+ pw.println(" synced [options] <effect>");
+ pw.println(" Vibrates effect on all vibrators in sync.");
+ pw.println(" combined [options] (-v <vibrator-id> <effect>)...");
+ pw.println(" Vibrates different effects on each vibrator in sync.");
+ pw.println(" sequential [options] (-v <vibrator-id> [-w <delay>] <effect>)...");
+ pw.println(" Vibrates different effects on each vibrator in sequence.");
+ pw.println(" cancel");
+ pw.println(" Cancels any active vibration");
+ pw.println("");
+ pw.println("Effect commands:");
+ pw.println(" oneshot [-a] <duration> [<amplitude>]");
+ pw.println(" Vibrates for duration milliseconds; ignored when device is on ");
+ pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting ");
+ pw.println(" will be used to scale amplitude.");
+ pw.println(" If -a is provided, the command accepts a second argument for ");
+ pw.println(" amplitude, in a scale of 1-255.");
+ pw.println(" waveform [-r <index>] [-a] (<duration> [<amplitude>])...");
+ pw.println(" Vibrates for durations and amplitudes in list; ignored when ");
+ pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength ");
+ pw.println(" user setting will be used to scale amplitude.");
+ pw.println(" If -r is provided, the waveform loops back to the specified");
+ pw.println(" index (e.g. 0 loops from the beginning)");
+ pw.println(" If -a is provided, the command accepts duration-amplitude pairs;");
+ pw.println(" otherwise, it accepts durations only and alternates off/on");
+ pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255.");
+ pw.println(" prebaked [-b] <effect-id>");
+ pw.println(" Vibrates with prebaked effect; ignored when device is on DND ");
+ pw.println(" (Do Not Disturb) mode; touch feedback strength user setting ");
+ pw.println(" will be used to scale amplitude.");
+ pw.println(" If -b is provided, the prebaked fallback effect will be played if");
+ pw.println(" the device doesn't support the given effect-id.");
+ pw.println(" composed [-w <delay>] <primitive-id>...");
+ pw.println(" Vibrates with a composed effect; ignored when device is on DND ");
+ pw.println(" (Do Not Disturb) mode; touch feedback strength user setting ");
+ pw.println(" will be used to scale primitive intensities.");
+ pw.println(" If -w is provided, the next primitive will be played after the ");
+ pw.println(" specified wait time in milliseconds.");
+ pw.println("");
+ pw.println("Common Options:");
+ pw.println(" -f");
+ pw.println(" Force. Ignore Do Not Disturb setting.");
+ pw.println(" -d <description>");
+ pw.println(" Add description to the vibration.");
pw.println("");
}
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 97e313e..026eb63 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -772,18 +772,7 @@
proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
mVibratorController.isUnderExternalControl());
- proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH));
- proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
- proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION));
- proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
- proto.write(VibratorServiceDumpProto.RING_INTENSITY,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
- proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+ mVibrationSettings.dumpProto(proto);
for (Vibration.DebugInfo info : mPreviousRingVibrations) {
info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index d63a6c3..5b50431 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -111,7 +111,6 @@
};
public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
- "android.hardware.audio@2.0::IDevicesFactory",
"android.hardware.audio@4.0::IDevicesFactory",
"android.hardware.audio@5.0::IDevicesFactory",
"android.hardware.audio@6.0::IDevicesFactory",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0c8172d..6216fc0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2815,12 +2815,24 @@
r = smap.mServicesByIntent.get(filter);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by intent: " + r);
}
- if (r != null && (r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
- && !callingPackage.equals(r.packageName)) {
- // If an external service is running within its own package, other packages
- // should not bind to that instance.
- r = null;
- if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Whoops, can't use existing external service");
+ if (r != null) {
+ // Compared to resolveService below, the ServiceRecord here is retrieved from
+ // ServiceMap so the package visibility doesn't apply to it. We need to filter it.
+ if (mAm.getPackageManagerInternal().filterAppAccess(r.packageName, callingUid,
+ userId)) {
+ Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId
+ + ": not found");
+ return null;
+ }
+ if ((r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
+ && !callingPackage.equals(r.packageName)) {
+ // If an external service is running within its own package, other packages
+ // should not bind to that instance.
+ r = null;
+ if (DEBUG_SERVICE) {
+ Slog.v(TAG_SERVICE, "Whoops, can't use existing external service");
+ }
+ }
}
if (r == null) {
try {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b153cfd..170c34d 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -43,6 +43,7 @@
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.power.MeasuredEnergyArray;
import com.android.internal.power.MeasuredEnergyStats;
@@ -96,8 +97,6 @@
return t;
});
- private final Context mContext;
-
@GuardedBy("mStats")
private final BatteryStatsImpl mStats;
@@ -168,15 +167,39 @@
@GuardedBy("this")
private long mLastCollectionTimeStamp;
+ final Injector mInjector;
+
+ @VisibleForTesting
+ public static class Injector {
+ private final Context mContext;
+
+ Injector(Context context) {
+ mContext = context;
+ }
+
+ public <T> T getSystemService(Class<T> serviceClass) {
+ return mContext.getSystemService(serviceClass);
+ }
+
+ public <T> T getLocalService(Class<T> serviceClass) {
+ return LocalServices.getService(serviceClass);
+ }
+ }
+
BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
- mContext = context;
+ this(new Injector(context), stats);
+ }
+
+ @VisibleForTesting
+ BatteryExternalStatsWorker(Injector injector, BatteryStatsImpl stats) {
+ mInjector = injector;
mStats = stats;
}
public void systemServicesReady() {
- final WifiManager wm = mContext.getSystemService(WifiManager.class);
- final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
- final PowerStatsInternal psi = LocalServices.getService(PowerStatsInternal.class);
+ final WifiManager wm = mInjector.getSystemService(WifiManager.class);
+ final TelephonyManager tm = mInjector.getSystemService(TelephonyManager.class);
+ final PowerStatsInternal psi = mInjector.getLocalService(PowerStatsInternal.class);
synchronized (mWorkerLock) {
mWifiManager = wm;
mTelephony = tm;
@@ -747,7 +770,8 @@
* EnergyConsumerResult}[]
*/
@GuardedBy("mWorkerLock")
- private @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
+ @VisibleForTesting
+ public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
final EnergyConsumerResult[] results;
try {
results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
@@ -761,28 +785,41 @@
final int[] subsystems = new int[size];
final long[] energyUJ = new long[size];
+ int count = 0;
for (int i = 0; i < size; i++) {
final EnergyConsumerResult consumer = results[i];
final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
- subsystems[i] = subsystem;
- energyUJ[i] = consumer.energyUWs;
+ subsystems[count] = subsystem;
+ energyUJ[count] = consumer.energyUWs;
+ count++;
}
+ final int arraySize = count;
return new MeasuredEnergyArray() {
@Override
public int getSubsystem(int index) {
+ if (index >= size()) {
+ throw new IllegalArgumentException(
+ "Out of bounds subsystem index! index : " + index + ", size : "
+ + size());
+ }
return subsystems[index];
}
@Override
public long getEnergy(int index) {
+ if (index >= size()) {
+ throw new IllegalArgumentException(
+ "Out of bounds subsystem index! index : " + index + ", size : "
+ + size());
+ }
return energyUJ[index];
}
@Override
public int size() {
- return size;
+ return arraySize;
}
};
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a768532..bbf927b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4447,7 +4447,7 @@
}
app.getPkgList().forEachPackage(packageName -> {
- if (updateFrameworkRes && packagesToUpdate.contains(packageName)) {
+ if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
try {
final ApplicationInfo ai = AppGlobals.getPackageManager()
.getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index cf309f4..9fd2bd7 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -623,6 +623,8 @@
DebugUtils.printSizeValue(pw, mLastCachedSwapPss * 1024);
pw.print(" lastRss=");
DebugUtils.printSizeValue(pw, mLastRss * 1024);
+ pw.println();
+ pw.print(prefix);
pw.print(" trimMemoryLevel=");
pw.println(mTrimMemoryLevel);
pw.println();
diff --git a/services/core/java/com/android/server/graphics/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
similarity index 97%
rename from services/core/java/com/android/server/graphics/GameManagerService.java
rename to services/core/java/com/android/server/app/GameManagerService.java
index 876f02f..3acad49 100644
--- a/services/core/java/com/android/server/graphics/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.server.graphics;
+package com.android.server.app;
import android.annotation.NonNull;
+import android.app.GameManager;
+import android.app.GameManager.GameMode;
+import android.app.IGameManagerService;
import android.content.Context;
-import android.graphics.GameManager;
-import android.graphics.GameManager.GameMode;
-import android.graphics.IGameManagerService;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
diff --git a/services/core/java/com/android/server/graphics/Settings.java b/services/core/java/com/android/server/app/Settings.java
similarity index 98%
rename from services/core/java/com/android/server/graphics/Settings.java
rename to services/core/java/com/android/server/app/Settings.java
index bbd84d0..ab367fb 100644
--- a/services/core/java/com/android/server/graphics/Settings.java
+++ b/services/core/java/com/android/server/app/Settings.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.server.graphics;
+package com.android.server.app;
-import android.graphics.GameManager;
+import android.app.GameManager;
import android.os.FileUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index b282484..1a4f20c7 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -57,6 +57,7 @@
import com.android.server.ConnectivityService;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -1025,6 +1026,8 @@
+ (networkAgentConfig.acceptUnvalidated ? " acceptUnvalidated" : "")
+ (networkAgentConfig.acceptPartialConnectivity ? " acceptPartialConnectivity" : "")
+ (clatd.isStarted() ? " clat{" + clatd + "} " : "")
+ + (declaredUnderlyingNetworks != null
+ ? " underlying{" + Arrays.toString(declaredUnderlyingNetworks) + "}" : "")
+ " lp{" + linkProperties + "}"
+ " nc{" + networkCapabilities + "}"
+ "}";
diff --git a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java
new file mode 100644
index 0000000..b082b25
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A class to detect font-related native crash.
+ *
+ * <p>If a fs-verity protected file is accessed through mmap and corrupted file block is detected,
+ * SIGBUG signal is generated and the process will crash. To find corrupted files and remove them,
+ * we use a marker file to detect crash.
+ * <ol>
+ * <li>Create a marker file before reading fs-verity protected font files.
+ * <li>Delete the marker file after reading font files successfully.
+ * <li>If the marker file is found in the next process startup, it means that the process
+ * crashed before. We will delete font files to prevent crash loop.
+ * </ol>
+ *
+ * <p>Example usage:
+ * <pre>
+ * FontCrashDetector detector = new FontCrashDetector(new File("/path/to/marker_file"));
+ * if (detector.hasCrashed()) {
+ * // Do cleanup
+ * }
+ * try (FontCrashDetector.MonitoredBlock b = detector.start()) {
+ * // Read files
+ * }
+ * </pre>
+ *
+ * <p>This class DOES NOT detect Java exceptions. If a Java exception is thrown while monitoring
+ * crash, the marker file will be deleted. Creating and deleting marker files are not lightweight.
+ * Please use this class sparingly with caution.
+ */
+/* package */ final class FontCrashDetector {
+
+ private static final String TAG = "FontCrashDetector";
+
+ @NonNull
+ private final File mMarkerFile;
+
+ /* package */ FontCrashDetector(@NonNull File markerFile) {
+ mMarkerFile = markerFile;
+ }
+
+ /* package */ boolean hasCrashed() {
+ return mMarkerFile.exists();
+ }
+
+ /* package */ void clear() {
+ if (!mMarkerFile.delete()) {
+ Slog.e(TAG, "Could not delete marker file: " + mMarkerFile);
+ }
+ }
+
+ /** Starts crash monitoring. */
+ /* package */ MonitoredBlock start() {
+ try {
+ mMarkerFile.createNewFile();
+ } catch (IOException e) {
+ Slog.e(TAG, "Could not create marker file: " + mMarkerFile, e);
+ }
+ return new MonitoredBlock();
+ }
+
+ /** A helper class to monitor crash with try-with-resources syntax. */
+ /* package */ class MonitoredBlock implements AutoCloseable {
+ /** Ends crash monitoring. */
+ @Override
+ public void close() {
+ clear();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 7461405..8e5215b 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -64,6 +64,7 @@
private static final String TAG = "FontManagerService";
private static final String FONT_FILES_DIR = "/data/fonts/files";
+ private static final String CRASH_MARKER_FILE = "/data/fonts/config/crash.txt";
@Override
public FontConfig getFontConfig() throws RemoteException {
@@ -179,6 +180,13 @@
}
}
+ @NonNull
+ private final Context mContext;
+
+ @GuardedBy("FontManagerService.this")
+ @NonNull
+ private final FontCrashDetector mFontCrashDetector;
+
@Nullable
private final UpdatableFontDir mUpdatableFontDir;
@@ -188,7 +196,9 @@
private FontManagerService(Context context) {
mContext = context;
+ mFontCrashDetector = new FontCrashDetector(new File(CRASH_MARKER_FILE));
mUpdatableFontDir = createUpdatableFontDir();
+ initialize();
}
@Nullable
@@ -201,20 +211,35 @@
new OtfFontFileParser(), new FsverityUtilImpl());
}
-
- @NonNull
- private final Context mContext;
+ private void initialize() {
+ synchronized (FontManagerService.this) {
+ if (mUpdatableFontDir == null) {
+ mSerializedFontMap = buildNewSerializedFontMap();
+ return;
+ }
+ if (mFontCrashDetector.hasCrashed()) {
+ Slog.i(TAG, "Crash detected. Clearing font updates.");
+ try {
+ mUpdatableFontDir.clearUpdates();
+ } catch (SystemFontException e) {
+ Slog.e(TAG, "Failed to clear updates.", e);
+ }
+ mFontCrashDetector.clear();
+ }
+ try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+ mUpdatableFontDir.loadFontFileMap();
+ mSerializedFontMap = buildNewSerializedFontMap();
+ }
+ }
+ }
@NonNull
public Context getContext() {
return mContext;
}
- @NonNull /* package */ SharedMemory getCurrentFontMap() {
+ @Nullable /* package */ SharedMemory getCurrentFontMap() {
synchronized (FontManagerService.this) {
- if (mSerializedFontMap == null) {
- mSerializedFontMap = buildNewSerializedFontMap();
- }
return mSerializedFontMap;
}
}
@@ -234,9 +259,10 @@
FontManager.RESULT_ERROR_VERSION_MISMATCH,
"The base config version is older than current.");
}
- mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
- // Create updated font map in the next getSerializedSystemFontMap() call.
- mSerializedFontMap = null;
+ try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+ mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
+ mSerializedFontMap = buildNewSerializedFontMap();
+ }
}
}
@@ -246,7 +272,12 @@
FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
"The font updater is disabled.");
}
- mUpdatableFontDir.clearUpdates();
+ synchronized (FontManagerService.this) {
+ try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+ mUpdatableFontDir.clearUpdates();
+ mSerializedFontMap = buildNewSerializedFontMap();
+ }
+ }
}
/* package */ Map<String, File> getFontFileMap() {
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index b0bc65b..0cb7045 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -142,11 +142,9 @@
mFsverityUtil = fsverityUtil;
mConfigFile = configFile;
mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
- loadFontFileMap();
}
- private void loadFontFileMap() {
- // TODO: SIGBUS crash protection
+ /* package */ void loadFontFileMap() {
synchronized (UpdatableFontDir.this) {
boolean success = false;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f6e08fb..2e4200c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -289,6 +289,8 @@
private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
private static native boolean nativeIsVibrating(long ptr, int deviceId);
private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
+ private static native int nativeGetBatteryCapacity(long ptr, int deviceId);
+ private static native int nativeGetBatteryStatus(long ptr, int deviceId);
private static native void nativeReloadKeyboardLayouts(long ptr);
private static native void nativeReloadDeviceAliases(long ptr);
private static native String nativeDump(long ptr);
@@ -2008,6 +2010,18 @@
// Binder call
@Override
+ public int getBatteryStatus(int deviceId) {
+ return nativeGetBatteryStatus(mPtr, deviceId);
+ }
+
+ // Binder call
+ @Override
+ public int getBatteryCapacity(int deviceId) {
+ return nativeGetBatteryCapacity(mPtr, deviceId);
+ }
+
+ // Binder call
+ @Override
public void setPointerIconType(int iconId) {
nativeSetPointerIconType(mPtr, iconId);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6308ace..e5b5350 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4131,48 +4131,52 @@
@BinderThread
@Override
@GuardedBy("mMethodMap")
- public void startProtoDump(byte[] protoDump, int source, String where) {
- if (protoDump == null && source != IME_TRACING_FROM_IMMS) {
- // Dump not triggered from IMMS, but no proto information provided.
- return;
- }
- ImeTracing tracingInstance = ImeTracing.getInstance();
- if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
- return;
- }
-
- ProtoOutputStream proto = new ProtoOutputStream();
- switch (source) {
- case ImeTracing.IME_TRACING_FROM_CLIENT:
- final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
- proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodClientsTraceProto.WHERE, where);
- proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
- proto.end(client_token);
- break;
- case ImeTracing.IME_TRACING_FROM_IMS:
- final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
- proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodServiceTraceProto.WHERE, where);
- proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
- proto.end(service_token);
- break;
- case IME_TRACING_FROM_IMMS:
- final long managerservice_token =
- proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
- proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
- SystemClock.elapsedRealtimeNanos());
- proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
- dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
- proto.end(managerservice_token);
- break;
- default:
- // Dump triggered by a source not recognised.
+ public void startProtoDump(byte[] protoDump, int source, String where,
+ IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> {
+ if (protoDump == null && source != IME_TRACING_FROM_IMMS) {
+ // Dump not triggered from IMMS, but no proto information provided.
return;
- }
- tracingInstance.addToBuffer(proto, source);
+ }
+ ImeTracing tracingInstance = ImeTracing.getInstance();
+ if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
+ return;
+ }
+
+ ProtoOutputStream proto = new ProtoOutputStream();
+ switch (source) {
+ case ImeTracing.IME_TRACING_FROM_CLIENT:
+ final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
+ proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
+ SystemClock.elapsedRealtimeNanos());
+ proto.write(InputMethodClientsTraceProto.WHERE, where);
+ proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
+ proto.end(client_token);
+ break;
+ case ImeTracing.IME_TRACING_FROM_IMS:
+ final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
+ proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
+ SystemClock.elapsedRealtimeNanos());
+ proto.write(InputMethodServiceTraceProto.WHERE, where);
+ proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
+ proto.end(service_token);
+ break;
+ case IME_TRACING_FROM_IMMS:
+ final long managerservice_token =
+ proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
+ proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
+ SystemClock.elapsedRealtimeNanos());
+ proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
+ dumpDebug(proto,
+ InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+ proto.end(managerservice_token);
+ break;
+ default:
+ // Dump triggered by a source not recognised.
+ return;
+ }
+ tracingInstance.addToBuffer(proto, source);
+ });
}
@BinderThread
@@ -4183,40 +4187,44 @@
@BinderThread
@Override
- public void startImeTrace() {
- ImeTracing.getInstance().startTrace(null /* printwriter */);
- ArrayMap<IBinder, ClientState> clients;
- synchronized (mMethodMap) {
- clients = new ArrayMap<>(mClients);
- }
- for (ClientState state : clients.values()) {
- if (state != null) {
- try {
- state.client.setImeTraceEnabled(true /* enabled */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error while trying to enable ime trace on client window", e);
+ public void startImeTrace(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> {
+ ImeTracing.getInstance().startTrace(null /* printwriter */);
+ ArrayMap<IBinder, ClientState> clients;
+ synchronized (mMethodMap) {
+ clients = new ArrayMap<>(mClients);
+ }
+ for (ClientState state : clients.values()) {
+ if (state != null) {
+ try {
+ state.client.setImeTraceEnabled(true /* enabled */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error while trying to enable ime trace on client window", e);
+ }
}
}
- }
+ });
}
@BinderThread
@Override
- public void stopImeTrace() {
- ImeTracing.getInstance().stopTrace(null /* printwriter */);
- ArrayMap<IBinder, ClientState> clients;
- synchronized (mMethodMap) {
- clients = new ArrayMap<>(mClients);
- }
- for (ClientState state : clients.values()) {
- if (state != null) {
- try {
- state.client.setImeTraceEnabled(false /* enabled */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error while trying to disable ime trace on client window", e);
+ public void stopImeTrace(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> {
+ ImeTracing.getInstance().stopTrace(null /* printwriter */);
+ ArrayMap<IBinder, ClientState> clients;
+ synchronized (mMethodMap) {
+ clients = new ArrayMap<>(mClients);
+ }
+ for (ClientState state : clients.values()) {
+ if (state != null) {
+ try {
+ state.client.setImeTraceEnabled(false /* enabled */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error while trying to disable ime trace on client window", e);
+ }
}
}
- }
+ });
}
@GuardedBy("mMethodMap")
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 7f9c766..6fec906 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1868,7 +1868,9 @@
@BinderThread
@Override
- public void startProtoDump(byte[] clientProtoDump, int source, String where) {
+ public void startProtoDump(byte[] clientProtoDump, int source, String where,
+ IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> { });
}
@BinderThread
@@ -1879,12 +1881,14 @@
@BinderThread
@Override
- public void startImeTrace() {
+ public void startImeTrace(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> { });
}
@BinderThread
@Override
- public void stopImeTrace() {
+ public void stopImeTrace(IVoidResultCallback resultCallback) {
+ CallbackUtils.onResult(resultCallback, () -> { });
}
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index dc1a26a..785e674 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -16,6 +16,7 @@
package com.android.server.location.contexthub;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -320,6 +321,10 @@
@Override
public void onNanoAppDisabled(long nanoAppId) {
}
+
+ @Override
+ public void onClientAuthorizationChanged(long nanoAppId, int authorization) {
+ }
};
}
@@ -697,6 +702,7 @@
*
* @param contextHubId the ID of the hub this client is attached to
* @param clientCallback the client interface to register with the service
+ * @param attributionTag an optional attribution tag within the given package
* @return the generated client interface, null if registration was unsuccessful
* @throws IllegalArgumentException if contextHubId is not a valid ID
* @throws IllegalStateException if max number of clients have already registered
@@ -704,7 +710,8 @@
*/
@Override
public IContextHubClient createClient(
- int contextHubId, IContextHubClientCallback clientCallback) throws RemoteException {
+ int contextHubId, IContextHubClientCallback clientCallback,
+ @Nullable String attributionTag) throws RemoteException {
checkPermissions();
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -723,13 +730,15 @@
* @param contextHubId the ID of the hub this client is attached to
* @param pendingIntent the PendingIntent associated with this client
* @param nanoAppId the ID of the nanoapp PendingIntent events will be sent for
+ * @param attributionTag an optional attribution tag within the given package
* @return the generated client interface
* @throws IllegalArgumentException if hubInfo does not represent a valid hub
* @throws IllegalStateException if there were too many registered clients at the service
*/
@Override
public IContextHubClient createPendingIntentClient(
- int contextHubId, PendingIntent pendingIntent, long nanoAppId) throws RemoteException {
+ int contextHubId, PendingIntent pendingIntent, long nanoAppId,
+ @Nullable String attributionTag) throws RemoteException {
checkPermissions();
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e32c00f..6843733 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -377,7 +377,8 @@
static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
Adjustment.KEY_CONTEXTUAL_ACTIONS,
- Adjustment.KEY_TEXT_REPLIES};
+ Adjustment.KEY_TEXT_REPLIES,
+ Adjustment.KEY_NOT_CONVERSATION};
static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
RoleManager.ROLE_DIALER,
@@ -2313,6 +2314,13 @@
} else if ("false".equals(value)) {
mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
}
+ } else if (SystemUiDeviceConfigFlags.ENABLE_NAS_NOT_CONVERSATION.equals(name)) {
+ String value = properties.getString(name, null);
+ if ("true".equals(value)) {
+ mAssistants.allowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
+ } else if ("false".equals(value)) {
+ mAssistants.disallowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
+ }
}
}
};
@@ -9302,21 +9310,30 @@
Slog.v(TAG, "onNotificationEnqueuedLocked() called with: r = [" + r + "]");
}
final StatusBarNotification sbn = r.getSbn();
- notifyAssistantLocked(
- sbn,
- r.getNotificationType(),
- true /* sameUserOnly */,
- (assistant, sbnHolder) -> {
- try {
- if (debug) {
- Slog.v(TAG,
- "calling onNotificationEnqueuedWithChannel " + sbnHolder);
- }
- assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel());
- } catch (RemoteException ex) {
- Slog.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+
+ for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+ boolean sbnVisible = isVisibleToListener(
+ sbn, r.getNotificationType(), info)
+ && info.isSameUser(r.getUserId());
+ if (sbnVisible) {
+ TrimCache trimCache = new TrimCache(sbn);
+ final INotificationListener assistant = (INotificationListener) info.service;
+ final StatusBarNotification sbnToPost = trimCache.ForListener(info);
+ final StatusBarNotificationHolder sbnHolder =
+ new StatusBarNotificationHolder(sbnToPost);
+ try {
+ if (debug) {
+ Slog.v(TAG,
+ "calling onNotificationEnqueuedWithChannel " + sbnHolder);
}
- });
+ final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+ assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel(),
+ update);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+ }
+ }
+ }
}
@GuardedBy("mNotificationLock")
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index f59934f..d7bc3bb 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -523,27 +523,39 @@
if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
long timeStartMs = System.currentTimeMillis();
for (final String handle: mPendingLookups) {
+ final String cacheKey = getCacheKey(mContext.getUserId(), handle);
LookupResult lookupResult = null;
- final Uri uri = Uri.parse(handle);
- if ("tel".equals(uri.getScheme())) {
- if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
- lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
- } else if ("mailto".equals(uri.getScheme())) {
- if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
- lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
- } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
- if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
- lookupResult = searchContacts(mContext, uri);
- } else {
- lookupResult = new LookupResult(); // invalid person for the cache
- if (!"name".equals(uri.getScheme())) {
- Slog.w(TAG, "unsupported URI " + handle);
+ boolean cacheHit = false;
+ synchronized (mPeopleCache) {
+ lookupResult = mPeopleCache.get(cacheKey);
+ if (lookupResult != null && !lookupResult.isExpired()) {
+ // The name wasn't already added to the cache, no need to retry
+ cacheHit = true;
+ }
+ }
+ if (!cacheHit) {
+ final Uri uri = Uri.parse(handle);
+ if ("tel".equals(uri.getScheme())) {
+ if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
+ lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
+ } else if ("mailto".equals(uri.getScheme())) {
+ if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
+ lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
+ } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+ if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
+ lookupResult = searchContacts(mContext, uri);
+ } else {
+ lookupResult = new LookupResult(); // invalid person for the cache
+ if (!"name".equals(uri.getScheme())) {
+ Slog.w(TAG, "unsupported URI " + handle);
+ }
}
}
if (lookupResult != null) {
- synchronized (mPeopleCache) {
- final String cacheKey = getCacheKey(mContext.getUserId(), handle);
- mPeopleCache.put(cacheKey, lookupResult);
+ if (!cacheHit) {
+ synchronized (mPeopleCache) {
+ mPeopleCache.put(cacheKey, lookupResult);
+ }
}
if (DEBUG) {
Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
@@ -580,4 +592,3 @@
}
}
}
-
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4ef892d..f68113d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18819,9 +18819,11 @@
final VersionInfo versionInfo = request.versionInfos.get(installPackageName);
final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
+ final boolean isRollback = installArgs != null
+ && installArgs.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
final boolean compatMatch = verifySignatures(signatureCheckPs,
disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
- compareRecover);
+ compareRecover, isRollback);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
removeAppKeySetData = true;
@@ -19791,6 +19793,7 @@
final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
final boolean virtualPreload =
((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
+ final boolean isRollback = args.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
@ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (args.move != null) {
// moving a complete application; perform an initial scan on the new install location
@@ -19971,7 +19974,8 @@
parsedPackage);
// We don't care about disabledPkgSetting on install for now.
final boolean compatMatch = verifySignatures(signatureCheckPs, null,
- parsedPackage.getSigningDetails(), compareCompat, compareRecover);
+ parsedPackage.getSigningDetails(), compareCompat, compareRecover,
+ isRollback);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
synchronized (mLock) {
@@ -20248,15 +20252,23 @@
+ pkgName11);
}
} else {
+ SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails();
+ SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails();
// default to original signature matching
- if (!parsedPackage.getSigningDetails().checkCapability(
- oldPackage.getSigningDetails(),
+ if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails,
SigningDetails.CertCapabilities.INSTALLED_DATA)
- && !oldPackage.getSigningDetails().checkCapability(
- parsedPackage.getSigningDetails(),
+ && !oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails,
SigningDetails.CertCapabilities.ROLLBACK)) {
- throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
- "New package has a different signature: " + pkgName11);
+ // Allow the update to proceed if this is a rollback and the parsed
+ // package's current signing key is the current signer or in the lineage
+ // of the old package; this allows a rollback to a previously installed
+ // version after an app's signing key has been rotated without requiring
+ // the rollback capability on the previous signing key.
+ if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
+ parsedPkgSigningDetails)) {
+ throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+ "New package has a different signature: " + pkgName11);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index ee94b85..8015063 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -624,7 +624,7 @@
*/
public static boolean verifySignatures(PackageSetting pkgSetting,
PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
- boolean compareCompat, boolean compareRecover)
+ boolean compareCompat, boolean compareRecover, boolean isRollback)
throws PackageManagerException {
final String packageName = pkgSetting.name;
boolean compatMatch = false;
@@ -658,6 +658,13 @@
match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
}
+ if (!match && isRollback) {
+ // Since a rollback can only be initiated for an APK previously installed on the
+ // device allow rolling back to a previous signing key even if the rollback
+ // capability has not been granted.
+ match = pkgSetting.signatures.mSigningDetails.hasAncestorOrSelf(parsedSignatures);
+ }
+
if (!match) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + packageName +
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index e12991a..9560f59 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -563,6 +563,7 @@
params.setRequestDowngrade(true);
params.setRequiredInstalledVersionCode(
pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
+ params.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
if (isStaged()) {
params.setStaged();
}
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index 6427ae2..fd12c2d2 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -18,16 +18,28 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilities.NetCapability;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.os.ParcelUuid;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Tracks a set of Networks underpinning a VcnGatewayConnection.
@@ -38,53 +50,385 @@
*
* @hide
*/
-public class UnderlyingNetworkTracker extends Handler {
+public class UnderlyingNetworkTracker {
@NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();
@NonNull private final VcnContext mVcnContext;
@NonNull private final ParcelUuid mSubscriptionGroup;
@NonNull private final UnderlyingNetworkTrackerCallback mCb;
@NonNull private final Dependencies mDeps;
+ @NonNull private final Handler mHandler;
+ @NonNull private final ConnectivityManager mConnectivityManager;
+ @NonNull private final SubscriptionManager mSubscriptionManager;
+
+ @NonNull private final SparseArray<NetworkCallback> mCellBringupCallbacks = new SparseArray<>();
+ @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback();
+ @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback();
+
+ @NonNull private final Set<Integer> mSubIds = new ArraySet<>();
+
+ @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities;
+
+ @Nullable private UnderlyingNetworkRecord mCurrentRecord;
+ @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
public UnderlyingNetworkTracker(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
+ @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
@NonNull UnderlyingNetworkTrackerCallback cb) {
- this(vcnContext, subscriptionGroup, cb, new Dependencies());
+ this(
+ vcnContext,
+ subscriptionGroup,
+ requiredUnderlyingNetworkCapabilities,
+ cb,
+ new Dependencies());
}
private UnderlyingNetworkTracker(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
+ @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
@NonNull UnderlyingNetworkTrackerCallback cb,
@NonNull Dependencies deps) {
- super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
- mVcnContext = vcnContext;
+ mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+ mRequiredUnderlyingNetworkCapabilities =
+ Objects.requireNonNull(
+ requiredUnderlyingNetworkCapabilities,
+ "Missing requiredUnderlyingNetworkCapabilities");
mCb = Objects.requireNonNull(cb, "Missing cb");
mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+ mHandler = new Handler(mVcnContext.getLooper());
+
+ mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+ mSubscriptionManager = mVcnContext.getContext().getSystemService(SubscriptionManager.class);
+
+ registerNetworkRequests();
+ }
+
+ private void registerNetworkRequests() {
+ // register bringup requests for underlying Networks
+ mConnectivityManager.requestBackgroundNetwork(
+ getWifiNetworkRequest(), mHandler, mWifiBringupCallback);
+ updateSubIdsAndCellularRequests();
+
+ // register Network-selection request used to decide selected underlying Network
+ mConnectivityManager.requestBackgroundNetwork(
+ getNetworkRequestBase().build(), mHandler, mRouteSelectionCallback);
+ }
+
+ private NetworkRequest getWifiNetworkRequest() {
+ return getNetworkRequestBase().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
+ }
+
+ private NetworkRequest getCellNetworkRequestForSubId(int subId) {
+ return getNetworkRequestBase()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
+ .build();
+ }
+
+ private NetworkRequest.Builder getNetworkRequestBase() {
+ NetworkRequest.Builder requestBase = new NetworkRequest.Builder();
+ for (@NetCapability int capability : mRequiredUnderlyingNetworkCapabilities) {
+ requestBase.addCapability(capability);
+ }
+
+ return requestBase
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ }
+
+ /**
+ * Update the current subIds and Cellular bringup requests for this UnderlyingNetworkTracker.
+ */
+ private void updateSubIdsAndCellularRequests() {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ Set<Integer> prevSubIds = new ArraySet<>(mSubIds);
+ mSubIds.clear();
+
+ // Ensure NetworkRequests filed for all current subIds in mSubscriptionGroup
+ // STOPSHIP: b/177364490 use TelephonySubscriptionSnapshot to avoid querying Telephony
+ List<SubscriptionInfo> subInfos =
+ mSubscriptionManager.getSubscriptionsInGroup(mSubscriptionGroup);
+
+ for (SubscriptionInfo subInfo : subInfos) {
+ final int subId = subInfo.getSubscriptionId();
+ mSubIds.add(subId);
+
+ if (!mCellBringupCallbacks.contains(subId)) {
+ final NetworkBringupCallback cb = new NetworkBringupCallback();
+ mCellBringupCallbacks.put(subId, cb);
+
+ mConnectivityManager.requestBackgroundNetwork(
+ getCellNetworkRequestForSubId(subId), mHandler, cb);
+ }
+ }
+
+ // unregister all NetworkCallbacks for outdated subIds
+ for (final int subId : prevSubIds) {
+ if (!mSubIds.contains(subId)) {
+ final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId);
+ mConnectivityManager.unregisterNetworkCallback(cb);
+ }
+ }
}
/** Tears down this Tracker, and releases all underlying network requests. */
- public void teardown() {}
+ public void teardown() {
+ mVcnContext.ensureRunningOnLooperThread();
- /** An record of a single underlying network, caching relevant fields. */
+ mConnectivityManager.unregisterNetworkCallback(mWifiBringupCallback);
+ mConnectivityManager.unregisterNetworkCallback(mRouteSelectionCallback);
+
+ for (final int subId : mSubIds) {
+ final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId);
+ mConnectivityManager.unregisterNetworkCallback(cb);
+ }
+ mSubIds.clear();
+ }
+
+ /** Returns whether the currently selected Network matches the given network. */
+ private static boolean isSameNetwork(
+ @Nullable UnderlyingNetworkRecord.Builder recordInProgress, @NonNull Network network) {
+ return recordInProgress != null && recordInProgress.getNetwork().equals(network);
+ }
+
+ /** Notify the Callback if a full UnderlyingNetworkRecord exists. */
+ private void maybeNotifyCallback() {
+ // Only forward this update if a complete record has been received
+ if (!mRecordInProgress.isValid()) {
+ return;
+ }
+
+ // Only forward this update if the updated record differs form the current record
+ UnderlyingNetworkRecord updatedRecord = mRecordInProgress.build();
+ if (!updatedRecord.equals(mCurrentRecord)) {
+ mCurrentRecord = updatedRecord;
+
+ mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
+ }
+ }
+
+ private void handleNetworkAvailable(@NonNull Network network) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ mRecordInProgress = new UnderlyingNetworkRecord.Builder(network);
+ }
+
+ private void handleNetworkLost(@NonNull Network network) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Non-underlying Network lost");
+ return;
+ }
+
+ mRecordInProgress = null;
+ mCurrentRecord = null;
+ mCb.onSelectedUnderlyingNetworkChanged(null /* underlyingNetworkRecord */);
+ }
+
+ private void handleCapabilitiesChanged(
+ @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Invalid update to NetworkCapabilities");
+ return;
+ }
+
+ mRecordInProgress.setNetworkCapabilities(networkCapabilities);
+
+ maybeNotifyCallback();
+ }
+
+ private void handleNetworkSuspended(@NonNull Network network, boolean isSuspended) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Invalid update to isSuspended");
+ return;
+ }
+
+ final NetworkCapabilities newCaps =
+ new NetworkCapabilities(mRecordInProgress.getNetworkCapabilities());
+ if (isSuspended) {
+ newCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+ } else {
+ newCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+ }
+
+ handleCapabilitiesChanged(network, newCaps);
+ }
+
+ private void handlePropertiesChanged(
+ @NonNull Network network, @NonNull LinkProperties linkProperties) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Invalid update to LinkProperties");
+ return;
+ }
+
+ mRecordInProgress.setLinkProperties(linkProperties);
+
+ maybeNotifyCallback();
+ }
+
+ private void handleNetworkBlocked(@NonNull Network network, boolean isBlocked) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ if (!isSameNetwork(mRecordInProgress, network)) {
+ Slog.wtf(TAG, "Invalid update to isBlocked");
+ return;
+ }
+
+ mRecordInProgress.setIsBlocked(isBlocked);
+
+ maybeNotifyCallback();
+ }
+
+ /**
+ * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
+ *
+ * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
+ * reaped, and no action is taken on any events firing.
+ */
+ @VisibleForTesting
+ class NetworkBringupCallback extends NetworkCallback {}
+
+ /**
+ * RouteSelectionCallback is used to select the "best" underlying Network.
+ *
+ * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
+ * truth.
+ */
+ @VisibleForTesting
+ class RouteSelectionCallback extends NetworkCallback {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ handleNetworkAvailable(network);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ handleNetworkLost(network);
+ }
+
+ @Override
+ public void onCapabilitiesChanged(
+ @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+ handleCapabilitiesChanged(network, networkCapabilities);
+ }
+
+ @Override
+ public void onNetworkSuspended(@NonNull Network network) {
+ handleNetworkSuspended(network, true /* isSuspended */);
+ }
+
+ @Override
+ public void onNetworkResumed(@NonNull Network network) {
+ handleNetworkSuspended(network, false /* isSuspended */);
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(
+ @NonNull Network network, @NonNull LinkProperties linkProperties) {
+ handlePropertiesChanged(network, linkProperties);
+ }
+
+ @Override
+ public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
+ handleNetworkBlocked(network, isBlocked);
+ }
+ }
+
+ /** A record of a single underlying network, caching relevant fields. */
public static class UnderlyingNetworkRecord {
@NonNull public final Network network;
@NonNull public final NetworkCapabilities networkCapabilities;
@NonNull public final LinkProperties linkProperties;
- public final boolean blocked;
+ public final boolean isBlocked;
@VisibleForTesting(visibility = Visibility.PRIVATE)
UnderlyingNetworkRecord(
@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
- boolean blocked) {
+ boolean isBlocked) {
this.network = network;
this.networkCapabilities = networkCapabilities;
this.linkProperties = linkProperties;
- this.blocked = blocked;
+ this.isBlocked = isBlocked;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UnderlyingNetworkRecord)) return false;
+ final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o;
+
+ return network.equals(that.network)
+ && networkCapabilities.equals(that.networkCapabilities)
+ && linkProperties.equals(that.linkProperties)
+ && isBlocked == that.isBlocked;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
+ }
+
+ /** Builder to incrementally construct an UnderlyingNetworkRecord. */
+ private static class Builder {
+ @NonNull private final Network mNetwork;
+
+ @Nullable private NetworkCapabilities mNetworkCapabilities;
+ @Nullable private LinkProperties mLinkProperties;
+ boolean mIsBlocked;
+ boolean mWasIsBlockedSet;
+
+ private Builder(@NonNull Network network) {
+ mNetwork = network;
+ }
+
+ @NonNull
+ private Network getNetwork() {
+ return mNetwork;
+ }
+
+ private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+ mNetworkCapabilities = networkCapabilities;
+ }
+
+ @Nullable
+ private NetworkCapabilities getNetworkCapabilities() {
+ return mNetworkCapabilities;
+ }
+
+ private void setLinkProperties(@NonNull LinkProperties linkProperties) {
+ mLinkProperties = linkProperties;
+ }
+
+ private void setIsBlocked(boolean isBlocked) {
+ mIsBlocked = isBlocked;
+ mWasIsBlockedSet = true;
+ }
+
+ private boolean isValid() {
+ return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
+ }
+
+ private UnderlyingNetworkRecord build() {
+ return new UnderlyingNetworkRecord(
+ mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
+ }
}
}
@@ -95,9 +439,10 @@
*
* <p>This callback does NOT signal a mobility event.
*
- * @param underlying The details of the new underlying network
+ * @param underlyingNetworkRecord The details of the new underlying network
*/
- void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying);
+ void onSelectedUnderlyingNetworkChanged(
+ @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
}
private static class Dependencies {}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 9d21b92..132883e 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -168,8 +168,8 @@
@NonNull NetworkRequest request, int score, int providerId) {
if (score > getNetworkScore()) {
Slog.v(getLogTag(),
- "Request " + request.requestId + " already satisfied by higher-scoring ("
- + score + ") network from provider " + providerId);
+ "Request already satisfied by higher-scoring (" + score + ") network from "
+ + "provider " + providerId + ": " + request);
return;
}
@@ -177,8 +177,7 @@
for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
Slog.v(getLogTag(),
- "Request " + request.requestId
- + " satisfied by existing VcnGatewayConnection");
+ "Request already satisfied by existing VcnGatewayConnection: " + request);
return;
}
}
@@ -202,12 +201,12 @@
private boolean requestSatisfiedByGatewayConnectionConfig(
@NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
- final NetworkCapabilities configCaps = new NetworkCapabilities();
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
for (int cap : config.getAllExposedCapabilities()) {
- configCaps.addCapability(cap);
+ builder.addCapability(cap);
}
- return request.networkCapabilities.satisfiedByNetworkCapabilities(configCaps);
+ return request.canBeSatisfiedBy(builder.build());
}
private String getLogTag() {
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index dba59bd..7399e56 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -55,4 +55,15 @@
public VcnNetworkProvider getVcnNetworkProvider() {
return mVcnNetworkProvider;
}
+
+ /**
+ * Verifies that the caller is running on the VcnContext Thread.
+ *
+ * @throwsIllegalStateException if the caller is not running on the VcnContext Thread.
+ */
+ public void ensureRunningOnLooperThread() {
+ if (getLooper().getThread() != Thread.currentThread()) {
+ throw new IllegalStateException("Not running on VcnMgmtSvc thread");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 703bfab..39c9606 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -65,6 +65,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
@@ -476,7 +477,10 @@
mUnderlyingNetworkTracker =
mDeps.newUnderlyingNetworkTracker(
- mVcnContext, subscriptionGroup, mUnderlyingNetworkTrackerCallback);
+ mVcnContext,
+ subscriptionGroup,
+ mConnectionConfig.getAllUnderlyingCapabilities(),
+ mUnderlyingNetworkTrackerCallback);
mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
IpSecTunnelInterface iface;
@@ -963,18 +967,18 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
static NetworkCapabilities buildNetworkCapabilities(
@NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) {
- final NetworkCapabilities caps = new NetworkCapabilities();
+ final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
- caps.addTransportType(TRANSPORT_CELLULAR);
- caps.addCapability(NET_CAPABILITY_NOT_CONGESTED);
- caps.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+ builder.addTransportType(TRANSPORT_CELLULAR);
+ builder.addCapability(NET_CAPABILITY_NOT_CONGESTED);
+ builder.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
// Add exposed capabilities
for (int cap : gatewayConnectionConfig.getAllExposedCapabilities()) {
- caps.addCapability(cap);
+ builder.addCapability(cap);
}
- return caps;
+ return builder.build();
}
private static LinkProperties buildConnectedLinkProperties(
@@ -1134,8 +1138,10 @@
public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
VcnContext vcnContext,
ParcelUuid subscriptionGroup,
+ Set<Integer> requiredUnderlyingNetworkCapabilities,
UnderlyingNetworkTrackerCallback callback) {
- return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
+ return new UnderlyingNetworkTracker(
+ vcnContext, subscriptionGroup, requiredUnderlyingNetworkCapabilities, callback);
}
/** Builds a new IkeSession. */
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index 7f5b23c..b9babae 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -21,9 +21,9 @@
import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.os.Looper;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
-import android.util.SparseArray;
import java.util.Objects;
import java.util.Set;
@@ -40,7 +40,13 @@
private static final String TAG = VcnNetworkProvider.class.getSimpleName();
private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
- private final SparseArray<NetworkRequestEntry> mRequests = new SparseArray<>();
+
+ /**
+ * Cache of NetworkRequest(s), scores and network providers, keyed by NetworkRequest
+ *
+ * <p>NetworkRequests are immutable once created, and therefore can be used as stable keys.
+ */
+ private final ArrayMap<NetworkRequest, NetworkRequestEntry> mRequests = new ArrayMap<>();
public VcnNetworkProvider(Context context, Looper looper) {
super(context, looper, VcnNetworkProvider.class.getSimpleName());
@@ -51,8 +57,8 @@
mListeners.add(listener);
// Send listener all cached requests
- for (int i = 0; i < mRequests.size(); i++) {
- notifyListenerForEvent(listener, mRequests.valueAt(i));
+ for (NetworkRequestEntry entry : mRequests.values()) {
+ notifyListenerForEvent(listener, entry);
}
}
@@ -75,7 +81,9 @@
request, score, providerId));
final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId);
- mRequests.put(request.requestId, entry);
+
+ // NetworkRequests are immutable once created, and therefore can be used as stable keys.
+ mRequests.put(request, entry);
// TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on
// Default Data Sub, or similar)
@@ -86,7 +94,7 @@
@Override
public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
- mRequests.remove(request.requestId);
+ mRequests.remove(request);
}
private static class NetworkRequestEntry {
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index fe3b03ab..e0f5408 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -138,6 +138,11 @@
return mStatus != Status.RUNNING;
}
+ /** Return true is effect is a repeating vibration. */
+ public boolean isRepeating() {
+ return mEffect.getDuration() == Long.MAX_VALUE;
+ }
+
/** Return the effect that should be played by this vibration. */
@Nullable
public CombinedVibrationEffect getEffect() {
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 536375f..8910bdf 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -34,10 +34,12 @@
import android.os.Vibrator;
import android.provider.Settings;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
+import com.android.server.VibratorServiceDumpProto;
import java.util.ArrayList;
import java.util.List;
@@ -340,6 +342,24 @@
+ '}';
}
+ /** Write current settings into given {@link ProtoOutputStream}. */
+ public void dumpProto(ProtoOutputStream proto) {
+ synchronized (mLock) {
+ proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
+ mHapticFeedbackIntensity);
+ proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+ mVibrator.getDefaultHapticFeedbackIntensity());
+ proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
+ mNotificationIntensity);
+ proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+ mVibrator.getDefaultNotificationVibrationIntensity());
+ proto.write(VibratorServiceDumpProto.RING_INTENSITY,
+ mRingIntensity);
+ proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+ mVibrator.getDefaultRingVibrationIntensity());
+ }
+ }
+
private void notifyListeners() {
List<OnVibratorSettingsChanged> currentListeners;
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 5355252..4f2fc86 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -114,12 +114,11 @@
}
}
- public Vibration getVibration() {
- return mVibration;
- }
-
@Override
public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Binder died, cancelling vibration...");
+ }
cancel();
}
@@ -150,12 +149,19 @@
/** Notify current vibration that a step has completed on given vibrator. */
public void vibratorComplete(int vibratorId) {
synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
+ }
if (mCurrentVibrateStep != null) {
mCurrentVibrateStep.vibratorComplete(vibratorId);
}
}
}
+ public Vibration getVibration() {
+ return mVibration;
+ }
+
@VisibleForTesting
SparseArray<VibratorController> getVibrators() {
return mVibrators;
@@ -467,7 +473,7 @@
noteVibratorOff();
}
if (DEBUG) {
- Slog.d(TAG, "SingleVibrateStep step done.");
+ Slog.d(TAG, "SingleVibrateStep done.");
}
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
deleted file mode 100644
index 3c8cf4e..0000000
--- a/services/core/java/com/android/server/wm/BarController.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-
-/**
- * Controls state/behavior specific to a system bar window.
- */
-public class BarController {
- private final int mWindowType;
-
- private final Rect mContentFrame = new Rect();
-
- BarController(int windowType) {
- mWindowType = windowType;
- }
-
- /**
- * Sets the frame within which the bar will display its content.
- *
- * This is used to determine if letterboxes interfere with the display of such content.
- */
- void setContentFrame(Rect frame) {
- mContentFrame.set(frame);
- }
-
- private Rect getContentFrame(@NonNull WindowState win) {
- final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType);
- return rotatedContentFrame != null ? rotatedContentFrame : mContentFrame;
- }
-
- boolean isLightAppearanceAllowed(WindowState win) {
- if (win == null) {
- return true;
- }
- return !win.isLetterboxedOverlappingWith(getContentFrame(win));
- }
-
- /**
- * @return {@code true} if bar is allowed to be fully transparent when given window is show.
- *
- * <p>Prevents showing a transparent bar over a letterboxed activity which can make
- * notification icons or navigation buttons unreadable due to contrast between letterbox
- * background and an activity. For instance, this happens when letterbox background is solid
- * black while activity is white. To resolve this, only semi-transparent bars are allowed to
- * be drawn over letterboxed activity.
- */
- boolean isFullyTransparentAllowed(WindowState win) {
- if (win == null) {
- return true;
- }
- return win.isFullyTransparentBarAllowed(getContentFrame(win));
- }
-}
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index dbad8b3..a725dd3 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -24,6 +24,9 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.res.CompatibilityInfo;
@@ -32,6 +35,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
@@ -63,6 +67,58 @@
// Compatibility state: compatibility mode is enabled.
private static final int COMPAT_FLAG_ENABLED = 1<<1;
+ /**
+ * CompatModePackages#DOWNSCALED is the gatekeeper of all per-app buffer downscaling
+ * changes. Disabling this change will prevent the following scaling factors from working:
+ * CompatModePackages#DOWNSCALE_87_5
+ * CompatModePackages#DOWNSCALE_75
+ * CompatModePackages#DOWNSCALE_62_5
+ * CompatModePackages#DOWNSCALE_50
+ *
+ * If CompatModePackages#DOWNSCALED is enabled for an app package, then the app will be forcibly
+ * resized to the highest enabled scaling factor e.g. 87.5% if both 87.5% and 75% were
+ * enabled.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALED = 168419799L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_87_5 for a package will force the app to assume it's
+ * running on a display with 87.5% the vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALE_87_5 = 176926753L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_75 for a package will force the app to assume it's
+ * running on a display with 75% the vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALE_75 = 176926829L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_62_5 for a package will force the app to assume it's
+ * running on a display with 62.5% the vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALE_62_5 = 176926771L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_50 for a package will force the app to assume it's
+ * running on a display with 50% vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ private static final long DOWNSCALE_50 = 176926741L;
+
private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
private static final int MSG_WRITE = 300;
@@ -191,11 +247,39 @@
mHandler.sendMessageDelayed(msg, 10000);
}
+ float getCompatScale(String packageName, int uid) {
+ if (!CompatChanges.isChangeEnabled(
+ DOWNSCALED, packageName, UserHandle.getUserHandleForUid(uid))) {
+ return 1f;
+ }
+ if (CompatChanges.isChangeEnabled(
+ DOWNSCALE_87_5, packageName, UserHandle.getUserHandleForUid(uid))) {
+ // 8/7 == (1 / 0.875) ~= 1.14285714286
+ return 8f / 7f;
+ }
+ if (CompatChanges.isChangeEnabled(
+ DOWNSCALE_75, packageName, UserHandle.getUserHandleForUid(uid))) {
+ // 4/3 == (1 / 0.75) ~= 1.333333333
+ return 4f / 3f;
+ }
+ if (CompatChanges.isChangeEnabled(
+ DOWNSCALE_62_5, packageName, UserHandle.getUserHandleForUid(uid))) {
+ // (1 / 0.625) == 1.6
+ return 1.6f;
+ }
+ if (CompatChanges.isChangeEnabled(
+ DOWNSCALE_50, packageName, UserHandle.getUserHandleForUid(uid))) {
+ return 2f;
+ }
+ return 1f;
+ }
+
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
final Configuration globalConfig = mService.getGlobalConfiguration();
+ final float requestedScale = getCompatScale(ai.packageName, ai.uid);
CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout,
globalConfig.smallestScreenWidthDp,
- (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
+ (getPackageFlags(ai.packageName) & COMPAT_FLAG_ENABLED) != 0, requestedScale);
//Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
return ci;
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 15483cb..f075d85 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -462,6 +462,23 @@
}
@Override
+ void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+ super.resolveOverrideConfiguration(newParentConfiguration);
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ final Rect overrideBounds = resolvedConfig.windowConfiguration.getBounds();
+ final Rect overrideAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+ final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+
+ // If there is no override of appBounds, restrict appBounds to the override bounds.
+ if (!overrideBounds.isEmpty() && (overrideAppBounds == null || overrideAppBounds.isEmpty())
+ && parentAppBounds != null && !parentAppBounds.isEmpty()) {
+ final Rect appBounds = new Rect(overrideBounds);
+ appBounds.intersect(parentAppBounds);
+ resolvedConfig.windowConfiguration.setAppBounds(appBounds);
+ }
+ }
+
+ @Override
boolean isOrganized() {
return mOrganizer != null;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 11cc2c6..d5d06f9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -670,8 +670,9 @@
// Used in updating override configurations
private final Configuration mTempConfig = new Configuration();
- // Used in performing layout
- private boolean mTmpWindowsBehindIme;
+ // Used in performing layout, to record the insets provided by other windows above the current
+ // window.
+ private InsetsState mTmpAboveInsetsState = new InsetsState();
/**
* Used to prevent recursions when calling
@@ -770,17 +771,11 @@
+ " parentHidden=" + w.isParentWindowHidden());
}
- // Sets mBehindIme for each window. Windows behind IME can get IME insets.
- if (w.mBehindIme != mTmpWindowsBehindIme) {
- w.mBehindIme = mTmpWindowsBehindIme;
- if (getInsetsStateController().getRawInsetsState().getSourceOrDefaultVisibility(
- ITYPE_IME)) {
- // If IME is invisible, behind IME or not doesn't make the insets different.
- mWinInsetsChanged.add(w);
- }
- }
- if (w == mInputMethodWindow) {
- mTmpWindowsBehindIme = true;
+ // Sets mAboveInsets for each window. Windows behind the window providing the insets can
+ // receive the insets.
+ if (!w.mAboveInsetsState.equals(mTmpAboveInsetsState)) {
+ w.mAboveInsetsState.set(mTmpAboveInsetsState);
+ mWinInsetsChanged.add(w);
}
// If this view is GONE, then skip it -- keep the current frame, and let the caller know
@@ -816,8 +811,16 @@
+ " mContainingFrame=" + w.getContainingFrame()
+ " mDisplayFrame=" + w.getDisplayFrame());
}
+ provideInsetsByWindow(w);
};
+ private void provideInsetsByWindow(WindowState w) {
+ for (int i = 0; i < w.mProvidedInsetsSources.size(); i++) {
+ final InsetsSource providedSource = w.mProvidedInsetsSources.valueAt(i);
+ mTmpAboveInsetsState.addSource(providedSource);
+ }
+ }
+
private final Consumer<WindowState> mPerformLayoutAttached = w -> {
if (w.mLayoutAttached) {
if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
@@ -4283,14 +4286,20 @@
+ " dh=" + mDisplayInfo.logicalHeight);
}
+ // Used to indicate that we have processed the insets windows. This needs to be after
+ // beginLayoutLw to ensure the raw insets state display related info is initialized.
+ final InsetsState rawInsetsState = getInsetsStateController().getRawInsetsState();
+ mTmpAboveInsetsState = new InsetsState();
+ mTmpAboveInsetsState.setDisplayFrame(rawInsetsState.getDisplayFrame());
+ mTmpAboveInsetsState.setDisplayCutout(rawInsetsState.getDisplayCutout());
+ mTmpAboveInsetsState.mirrorAlwaysVisibleInsetsSources(rawInsetsState);
+
int seq = mLayoutSeq + 1;
if (seq < 0) seq = 0;
mLayoutSeq = seq;
mTmpInitial = initial;
- // Used to indicate that we have processed the IME window.
- mTmpWindowsBehindIme = false;
// First perform layout of any root windows (not attached to another window).
forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f52cb09..ed97848 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -86,6 +86,7 @@
import static android.view.WindowManagerPolicyConstants.ALT_BAR_UNKNOWN;
import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
@@ -305,8 +306,7 @@
private boolean mLastImmersiveMode;
- private final BarController mStatusBarController;
- private final BarController mNavigationBarController;
+ private final SparseArray<Rect> mBarContentFrames = new SparseArray<>();
// The windows we were told about in focusChanged.
private WindowState mFocusedWindow;
@@ -432,8 +432,8 @@
final int displayId = displayContent.getDisplayId();
- mStatusBarController = new BarController(TYPE_STATUS_BAR);
- mNavigationBarController = new BarController(TYPE_NAVIGATION_BAR);
+ mBarContentFrames.put(TYPE_STATUS_BAR, new Rect());
+ mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect());
final Resources r = mContext.getResources();
mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
@@ -1253,11 +1253,6 @@
displayFrames.mDisplayCutoutSafe.top);
}
- @VisibleForTesting
- BarController getStatusBarController() {
- return mStatusBarController;
- }
-
WindowState getStatusBar() {
return mStatusBar != null ? mStatusBar : mStatusBarAlt;
}
@@ -1473,7 +1468,7 @@
mSystemGestures.screenHeight = info.logicalHeight;
}
- private void layoutStatusBar(DisplayFrames displayFrames, Rect simulatedContentFrame) {
+ private void layoutStatusBar(DisplayFrames displayFrames, Rect contentFrame) {
// decide where the status bar goes ahead of time
if (mStatusBar == null) {
return;
@@ -1500,21 +1495,16 @@
statusBarBottom);
}
- // Tell the bar controller where the collapsed status bar content is.
sTmpRect.set(windowFrames.mFrame);
sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
sTmpRect.top = windowFrames.mFrame.top; // Ignore top display cutout inset
sTmpRect.bottom = statusBarBottom; // Use collapsed status bar size
- if (simulatedContentFrame != null) {
- simulatedContentFrame.set(sTmpRect);
- } else {
- mStatusBarController.setContentFrame(sTmpRect);
- }
+ contentFrame.set(sTmpRect);
}
- private void layoutNavigationBar(DisplayFrames displayFrames, Rect simulatedContentFrame) {
+ private int layoutNavigationBar(DisplayFrames displayFrames, Rect contentFrame) {
if (mNavigationBar == null) {
- return;
+ return NAV_BAR_INVALID;
}
final int uiMode = mDisplayContent.getConfiguration().uiMode;
@@ -1552,17 +1542,12 @@
windowFrames.setFrames(navigationFrame /* parentFrame */,
navigationFrame /* displayFrame */);
mNavigationBar.computeFrameAndUpdateSourceFrame();
- final Rect contentFrame = sTmpRect;
- contentFrame.set(windowFrames.mFrame);
- contentFrame.intersect(displayFrames.mDisplayCutoutSafe);
- if (simulatedContentFrame != null) {
- simulatedContentFrame.set(contentFrame);
- } else {
- mNavigationBarPosition = navBarPosition;
- mNavigationBarController.setContentFrame(contentFrame);
- }
+ sTmpRect.set(windowFrames.mFrame);
+ sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
+ contentFrame.set(sTmpRect);
if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + navigationFrame);
+ return navBarPosition;
}
private boolean canReceiveInput(WindowState win) {
@@ -1609,11 +1594,12 @@
*/
public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
if (win == mNavigationBar) {
- layoutNavigationBar(displayFrames, null /* simulatedContentFrame */);
+ mNavigationBarPosition = layoutNavigationBar(displayFrames,
+ mBarContentFrames.get(TYPE_NAVIGATION_BAR));
return;
}
if ((win == mStatusBar && !canReceiveInput(win))) {
- layoutStatusBar(displayFrames, null /* simulatedContentFrame */);
+ layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
return;
}
final WindowManager.LayoutParams attrs = win.getAttrs();
@@ -2622,7 +2608,7 @@
// Otherwise if it's dimming, clear the light flag.
appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
}
- if (!mStatusBarController.isLightAppearanceAllowed(statusColorWin)) {
+ if (!isLightBarAllowed(statusColorWin, TYPE_STATUS_BAR)) {
appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
}
}
@@ -2685,7 +2671,7 @@
// Clear the light flag for dimming window.
appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
}
- if (!mNavigationBarController.isLightAppearanceAllowed(navColorWin)) {
+ if (!isLightBarAllowed(navColorWin, TYPE_NAVIGATION_BAR)) {
appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
}
}
@@ -2739,7 +2725,36 @@
return appearance;
}
- private boolean drawsBarBackground(WindowState win, BarController controller) {
+ private boolean isLightBarAllowed(WindowState win, int windowType) {
+ if (win == null) {
+ return true;
+ }
+ return !win.isLetterboxedOverlappingWith(getBarContentFrameForWindow(win, windowType));
+ }
+
+ private Rect getBarContentFrameForWindow(WindowState win, int windowType) {
+ final Rect rotatedBarFrame = win.mToken.getFixedRotationBarContentFrame(windowType);
+ return rotatedBarFrame != null ? rotatedBarFrame : mBarContentFrames.get(windowType);
+ }
+
+ /**
+ * @return {@code true} if bar is allowed to be fully transparent when given window is show.
+ *
+ * <p>Prevents showing a transparent bar over a letterboxed activity which can make
+ * notification icons or navigation buttons unreadable due to contrast between letterbox
+ * background and an activity. For instance, this happens when letterbox background is solid
+ * black while activity is white. To resolve this, only semi-transparent bars are allowed to
+ * be drawn over letterboxed activity.
+ */
+ @VisibleForTesting
+ boolean isFullyTransparentAllowed(WindowState win, int windowType) {
+ if (win == null) {
+ return true;
+ }
+ return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, windowType));
+ }
+
+ private boolean drawsBarBackground(WindowState win) {
if (win == null) {
return true;
}
@@ -2752,27 +2767,19 @@
return forceDrawsSystemBars || drawsSystemBars;
}
- private boolean drawsStatusBarBackground(WindowState win) {
- return drawsBarBackground(win, mStatusBarController);
- }
-
- private boolean drawsNavigationBarBackground(WindowState win) {
- return drawsBarBackground(win, mNavigationBarController);
- }
-
/** @return the current visibility flags with the status bar opacity related flags toggled. */
private int configureStatusBarOpacity(int appearance) {
final boolean fullscreenDrawsBackground =
- drawsStatusBarBackground(mTopFullscreenOpaqueWindowState);
+ drawsBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsBackground =
- drawsStatusBarBackground(mTopDockedOpaqueWindowState);
+ drawsBarBackground(mTopDockedOpaqueWindowState);
if (fullscreenDrawsBackground && dockedDrawsBackground) {
appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
}
- if (!mStatusBarController.isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState)
- || !mStatusBarController.isFullyTransparentAllowed(mTopDockedOpaqueWindowState)) {
+ if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_STATUS_BAR)
+ || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_STATUS_BAR)) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
}
@@ -2788,9 +2795,9 @@
final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
final boolean fullscreenDrawsBackground =
- drawsNavigationBarBackground(mTopFullscreenOpaqueWindowState);
+ drawsBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsBackground =
- drawsNavigationBarBackground(mTopDockedOpaqueWindowState);
+ drawsBarBackground(mTopDockedOpaqueWindowState);
if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
if (fullscreenDrawsBackground && dockedDrawsBackground) {
@@ -2818,9 +2825,8 @@
}
}
- if (!mNavigationBarController.isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState)
- || !mNavigationBarController.isFullyTransparentAllowed(
- mTopDockedOpaqueWindowState)) {
+ if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_NAVIGATION_BAR)
+ || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_NAVIGATION_BAR)) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1692df6..c6c7fe0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -151,6 +151,7 @@
// animate-out as new one animates-in.
mWin.cancelAnimation();
mWin.mPendingPositionChanged = null;
+ mWin.mProvidedInsetsSources.remove(mSource.getType());
}
ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
mWin = win;
@@ -160,11 +161,14 @@
setServerVisible(false);
mSource.setFrame(new Rect());
mSource.setVisibleFrame(null);
- } else if (mControllable) {
- mWin.setControllableInsetProvider(this);
- if (mPendingControlTarget != null) {
- updateControlForTarget(mPendingControlTarget, true /* force */);
- mPendingControlTarget = null;
+ } else {
+ mWin.mProvidedInsetsSources.put(mSource.getType(), mSource);
+ if (mControllable) {
+ mWin.setControllableInsetProvider(this);
+ if (mPendingControlTarget != null) {
+ updateControlForTarget(mPendingControlTarget, true /* force */);
+ mPendingControlTarget = null;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 267f677..3ba7b7d 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -104,6 +104,8 @@
* visible to the target. e.g., the source which represents the target window itself, and the
* IME source when the target is above IME. We also need to exclude certain types of insets
* source for client within specific windowing modes.
+ * This is to get the insets for a window layout on the screen. If the window is not there, use
+ * the {@link #getInsetsForWindowMetrics} to get insets instead.
*
* @param target The window associate with the perspective.
* @return The state stripped of the necessary information.
@@ -117,8 +119,8 @@
final @InternalInsetsType int type = provider != null
? provider.getSource().getType() : ITYPE_INVALID;
return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(),
- isAboveIme(target),
- target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : mState);
+ target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() :
+ target.mAboveInsetsState);
}
InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
@@ -133,19 +135,7 @@
final @WindowingMode int windowingMode = token != null
? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
- return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token), mState);
- }
-
- private boolean isAboveIme(WindowContainer target) {
- final WindowState imeWindow = mDisplayContent.mInputMethodWindow;
- if (target == null || imeWindow == null) {
- return false;
- }
- if (target instanceof WindowState) {
- final WindowState win = (WindowState) target;
- return win.needsRelativeLayeringToIme() || !win.mBehindIme;
- }
- return false;
+ return getInsetsForTarget(type, windowingMode, alwaysOnTop, mState);
}
private static @InternalInsetsType
@@ -181,10 +171,12 @@
* @see #getInsetsForWindowMetrics
*/
private InsetsState getInsetsForTarget(@InternalInsetsType int type,
- @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme,
- @NonNull InsetsState state) {
+ @WindowingMode int windowingMode, boolean isAlwaysOnTop, InsetsState state) {
+ boolean stateCopied = false;
+
if (type != ITYPE_INVALID) {
state = new InsetsState(state);
+ stateCopied = true;
state.removeSource(type);
// Navigation bar doesn't get influenced by anything else
@@ -219,23 +211,15 @@
if (WindowConfiguration.isFloating(windowingMode)
|| (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) {
- state = new InsetsState(state);
+ if (!stateCopied) {
+ state = new InsetsState(state);
+ stateCopied = true;
+ }
state.removeSource(ITYPE_STATUS_BAR);
state.removeSource(ITYPE_NAVIGATION_BAR);
state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
}
- if (aboveIme) {
- InsetsSource imeSource = state.peekSource(ITYPE_IME);
- if (imeSource != null && imeSource.isVisible()) {
- imeSource = new InsetsSource(imeSource);
- imeSource.setVisible(false);
- imeSource.setFrame(0, 0, 0, 0);
- state = new InsetsState(state);
- state.addSource(imeSource);
- }
- }
-
return state;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1597604..0250376 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -212,6 +212,7 @@
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
@@ -440,6 +441,7 @@
float mGlobalScale=1;
float mLastGlobalScale=1;
float mInvGlobalScale=1;
+ float mOverrideScale = 1;
float mHScale=1, mVScale=1;
float mLastHScale=1, mLastVScale=1;
final Matrix mTmpMatrix = new Matrix();
@@ -647,9 +649,14 @@
boolean mSeamlesslyRotated = false;
/**
- * Indicates if this window is behind IME. Only windows behind IME can get insets from IME.
+ * The insets state of sources provided by windows above the current window.
*/
- boolean mBehindIme = false;
+ InsetsState mAboveInsetsState = new InsetsState();
+
+ /**
+ * The insets sources provided by this window.
+ */
+ ArrayMap<Integer, InsetsSource> mProvidedInsetsSources = new ArrayMap<>();
/**
* Surface insets from the previous call to relayout(), used to track
@@ -1008,6 +1015,8 @@
mLastRequestedWidth = 0;
mLastRequestedHeight = 0;
mLayer = 0;
+ mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(
+ mAttrs.packageName, s.mUid);
// Make sure we initial all fields before adding to parentWindow, to prevent exception
// during onDisplayChanged.
@@ -1040,8 +1049,15 @@
mSession.windowAddedLocked(mAttrs.packageName);
}
+ /**
+ * @return {@code true} if the application runs in size compatibility mode or has an app level
+ * scaling override set.
+ * @see CompatModePackages#getCompatScale
+ * @see android.content.res.CompatibilityInfo#supportsScreen
+ * @see ActivityRecord#inSizeCompatMode()
+ */
boolean inSizeCompatMode() {
- return inSizeCompatMode(mAttrs, mActivityRecord);
+ return mOverrideScale != 1f || inSizeCompatMode(mAttrs, mActivityRecord);
}
/**
@@ -1676,7 +1692,13 @@
void prelayout() {
if (inSizeCompatMode()) {
- mGlobalScale = mToken.getSizeCompatScale();
+ if (mOverrideScale != 1f) {
+ mGlobalScale = mToken.hasSizeCompatBounds()
+ ? mToken.getSizeCompatScale() * mOverrideScale
+ : mOverrideScale;
+ } else {
+ mGlobalScale = mToken.getSizeCompatScale();
+ }
mInvGlobalScale = 1 / mGlobalScale;
} else {
mGlobalScale = mInvGlobalScale = 1;
@@ -2634,8 +2656,7 @@
// scaling but the existing logic doesn't expect that. The result is that the already-
// scaled region ends up getting sent to surfaceflinger which then applies the scale
// (again). Until this is resolved, apply an inverse-scale here.
- if (mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
- && mGlobalScale != 1.f) {
+ if (mInvGlobalScale != 1.f) {
region.scale(mInvGlobalScale);
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index dc15b07..5b587e9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -26,14 +26,14 @@
// Log debug messages about InputDispatcherPolicy
#define DEBUG_INPUT_DISPATCHER_POLICY 0
-
-#include <atomic>
-#include <cinttypes>
-#include <limits.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
+#include <android/os/IInputConstants.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
+#include <limits.h>
+#include <atomic>
+#include <cinttypes>
#include <utils/Log.h>
#include <utils/Looper.h>
@@ -46,6 +46,7 @@
#include <input/SpriteController.h>
#include <ui/Region.h>
+#include <batteryservice/include/batteryservice/BatteryServiceConstants.h>
#include <inputflinger/InputManager.h>
#include <android_os_MessageQueue.h>
@@ -1908,6 +1909,20 @@
return vibIdArray;
}
+static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ std::optional<int32_t> ret = im->getInputManager()->getReader()->getBatteryCapacity(deviceId);
+ return static_cast<jint>(ret.value_or(android::os::IInputConstants::INVALID_BATTERY_CAPACITY));
+}
+
+static jint nativeGetBatteryStatus(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ std::optional<int32_t> ret = im->getInputManager()->getReader()->getBatteryStatus(deviceId);
+ return static_cast<jint>(ret.value_or(BATTERY_STATUS_UNKNOWN));
+}
+
static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -2163,6 +2178,8 @@
{"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
{"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
{"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
+ {"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity},
+ {"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus},
{"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
{"nativeReloadDeviceAliases", "(J)V", (void*)nativeReloadDeviceAliases},
{"nativeDump", "(J)Ljava/lang/String;", (void*)nativeDump},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c3bb757..14376d3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -35,10 +35,8 @@
import static android.app.admin.DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.CODE_NONSYSTEM_USER_EXISTS;
import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT;
import static android.app.admin.DevicePolicyManager.CODE_OK;
import static android.app.admin.DevicePolicyManager.CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
-import static android.app.admin.DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.CODE_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER;
import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING;
@@ -1380,11 +1378,6 @@
SystemProperties.set(key, value);
}
- // TODO (b/137101239): clean up split system user codes
- boolean userManagerIsSplitSystemUser() {
- return UserManager.isSplitSystemUser();
- }
-
boolean userManagerIsHeadlessSystemUserMode() {
return UserManager.isHeadlessSystemUserMode();
}
@@ -7404,48 +7397,18 @@
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
+ // TODO (b/137101239): remove this method in follow-up CL
+ // since it's only used for split system user.
@Override
public void setForceEphemeralUsers(ComponentName who, boolean forceEphemeralUsers) {
- if (!mHasFeature) {
- return;
- }
- Objects.requireNonNull(who, "ComponentName is null");
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
-
- // Allow setting this policy to true only if there is a split system user.
- if (forceEphemeralUsers && !mInjector.userManagerIsSplitSystemUser()) {
- throw new UnsupportedOperationException(
- "Cannot force ephemeral users on systems without split system user.");
- }
- boolean removeAllUsers = false;
- synchronized (getLockObject()) {
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- if (deviceOwner.forceEphemeralUsers != forceEphemeralUsers) {
- deviceOwner.forceEphemeralUsers = forceEphemeralUsers;
- saveSettingsLocked(caller.getUserId());
- mUserManagerInternal.setForceEphemeralUsers(forceEphemeralUsers);
- removeAllUsers = forceEphemeralUsers;
- }
- }
- if (removeAllUsers) {
- mInjector.binderWithCleanCallingIdentity(() -> mUserManagerInternal.removeAllUsers());
- }
+ throw new UnsupportedOperationException("This method was used by split system user only.");
}
+ // TODO (b/137101239): remove this method in follow-up CL
+ // since it's only used for split system user.
@Override
public boolean getForceEphemeralUsers(ComponentName who) {
- if (!mHasFeature) {
- return false;
- }
- Objects.requireNonNull(who, "ComponentName is null");
- final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
-
- synchronized (getLockObject()) {
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- return deviceOwner.forceEphemeralUsers;
- }
+ throw new UnsupportedOperationException("This method was used by split system user only.");
}
@Override
@@ -12733,13 +12696,6 @@
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(callingUserId);
- // TODO (b/137101239): clean up split system user codes
- // ACTION_PROVISION_MANAGED_USER and ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
- // only supported on split-user systems.
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
- return checkManagedUserProvisioningPreCondition(callingUserId);
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
- return checkManagedShareableDeviceProvisioningPreCondition(callingUserId);
}
}
throw new IllegalArgumentException("Unknown provisioning action " + action);
@@ -12775,14 +12731,12 @@
}
}
- // TODO (b/137101239): clean up split system user codes
if (isAdb) {
// If shell command runs after user setup completed check device status. Otherwise, OK.
if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
// In non-headless system user mode, DO can be setup only if
// there's no non-system user
if (!mInjector.userManagerIsHeadlessSystemUserMode()
- && !mInjector.userManagerIsSplitSystemUser()
&& mUserManager.getUserCount() > 1) {
return CODE_NONSYSTEM_USER_EXISTS;
}
@@ -12801,16 +12755,13 @@
}
return CODE_OK;
} else {
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // In non-split user mode, DO has to be user 0
- if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
- return CODE_NOT_SYSTEM_USER;
- }
- // Only provision DO before setup wizard completes
- // TODO (b/171423186): implement deferred DO setup for headless system user mode
- if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
- return CODE_USER_SETUP_COMPLETED;
- }
+ // DO has to be user 0
+ if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+ return CODE_NOT_SYSTEM_USER;
+ }
+ // Only provision DO before setup wizard completes
+ if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+ return CODE_USER_SETUP_COMPLETED;
}
return CODE_OK;
}
@@ -12831,17 +12782,11 @@
}
}
- // TODO (b/137101239): clean up split system user codes
private int checkManagedProfileProvisioningPreCondition(String packageName,
@UserIdInt int callingUserId) {
if (!hasFeatureManagedUsers()) {
return CODE_MANAGED_USERS_NOT_SUPPORTED;
}
- if (callingUserId == UserHandle.USER_SYSTEM
- && mInjector.userManagerIsSplitSystemUser()) {
- // Managed-profiles cannot be setup on the system user.
- return CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER;
- }
if (getProfileOwnerAsUser(callingUserId) != null) {
// Managed user cannot have a managed profile.
return CODE_USER_HAS_PROFILE_OWNER;
@@ -12917,37 +12862,6 @@
return null;
}
- // TODO (b/137101239): clean up split system user codes
- private int checkManagedUserProvisioningPreCondition(int callingUserId) {
- if (!hasFeatureManagedUsers()) {
- return CODE_MANAGED_USERS_NOT_SUPPORTED;
- }
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
- return CODE_NOT_SYSTEM_USER_SPLIT;
- }
- if (callingUserId == UserHandle.USER_SYSTEM) {
- // System user cannot be a managed user.
- return CODE_SYSTEM_USER;
- }
- if (hasUserSetupCompleted(callingUserId)) {
- return CODE_USER_SETUP_COMPLETED;
- }
- if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
- return CODE_HAS_PAIRED;
- }
- return CODE_OK;
- }
-
- // TODO (b/137101239): clean up split system user codes
- private int checkManagedShareableDeviceProvisioningPreCondition(int callingUserId) {
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
- return CODE_NOT_SYSTEM_USER_SPLIT;
- }
- return checkDeviceOwnerProvisioningPreCondition(callingUserId);
- }
-
private boolean hasFeatureManagedUsers() {
try {
return mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5ced868..636be4a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -342,6 +342,8 @@
"com.android.server.contentsuggestions.ContentSuggestionsManagerService";
private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
"com.android.server.searchui.SearchUiManagerService";
+ private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
+ "com.android.server.smartspace.SmartspaceManagerService";
private static final String DEVICE_IDLE_CONTROLLER_CLASS =
"com.android.server.DeviceIdleController";
private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -365,7 +367,7 @@
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
private static final String GAME_MANAGER_SERVICE_CLASS =
- "com.android.server.graphics.GameManagerService$Lifecycle";
+ "com.android.server.app.GameManagerService$Lifecycle";
private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
@@ -1342,6 +1344,13 @@
mSystemServiceManager.startService(DropBoxManagerService.class);
t.traceEnd();
+ // Grants default permissions and defines roles
+ t.traceBegin("StartRoleManagerService");
+ LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
+ new RoleServicePlatformHelperImpl(mSystemContext));
+ mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("StartVibratorManagerService");
mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
t.traceEnd();
@@ -1676,6 +1685,12 @@
mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
t.traceEnd();
+ // Smartspace manager service
+ // TODO: add deviceHasConfigString(context, R.string.config_defaultSmartspaceService)
+ t.traceBegin("StartSmartspaceService");
+ mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("InitConnectivityModuleConnector");
try {
ConnectivityModuleConnector.getInstance().init(context);
@@ -2052,13 +2067,6 @@
t.traceEnd();
}
- // Grants default permissions and defines roles
- t.traceBegin("StartRoleManagerService");
- LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
- new RoleServicePlatformHelperImpl(mSystemContext));
- mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
- t.traceEnd();
-
// We need to always start this service, regardless of whether the
// FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
// of initializing various settings. It will internally modify its behavior
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 9a9a171..444f9c6 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -264,7 +264,8 @@
return new ConversationChannel(shortcutInfo, uid, parentChannel,
parentChannelGroup,
conversationInfo.getLastEventTimestamp(),
- hasActiveNotifications(packageName, userId, shortcutId));
+ hasActiveNotifications(packageName, userId, shortcutId), false,
+ getStatuses(conversationInfo));
}
/** Returns the cached non-customized recent conversations. */
@@ -404,6 +405,10 @@
String conversationId) {
ConversationStore cs = getConversationStoreOrThrow(packageName, userId);
ConversationInfo conversationInfo = getConversationInfoOrThrow(cs, conversationId);
+ return getStatuses(conversationInfo);
+ }
+
+ private @NonNull List<ConversationStatus> getStatuses(ConversationInfo conversationInfo) {
Collection<ConversationStatus> statuses = conversationInfo.getStatuses();
if (statuses != null) {
final ArrayList<ConversationStatus> list = new ArrayList<>(statuses.size());
diff --git a/services/smartspace/Android.bp b/services/smartspace/Android.bp
new file mode 100644
index 0000000..fcf780d
--- /dev/null
+++ b/services/smartspace/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+ name: "services.smartspace-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.smartspace",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.smartspace-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/smartspace/OWNERS b/services/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/services/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
new file mode 100644
index 0000000..3b5a5a5
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.smartspace.ISmartspaceService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.smartspace.SmartspaceService} implementation in another
+ * process.
+ */
+public class RemoteSmartspaceService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteSmartspaceService,
+ ISmartspaceService> {
+
+ private static final String TAG = "RemoteSmartspaceService";
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ private final RemoteSmartspaceServiceCallbacks mCallback;
+
+ public RemoteSmartspaceService(Context context, String serviceInterface,
+ ComponentName componentName, int userId,
+ RemoteSmartspaceServiceCallbacks callback, boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, serviceInterface, componentName, userId, callback,
+ context.getMainThreadHandler(),
+ bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+ verbose, /* initialCapacity= */ 1);
+ mCallback = callback;
+ }
+
+ @Override
+ protected ISmartspaceService getServiceInterface(IBinder service) {
+ return ISmartspaceService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ /**
+ * Schedules a request to bind to the remote service.
+ */
+ public void reconnect() {
+ super.scheduleBind();
+ }
+
+ /**
+ * Schedule async request on remote service.
+ */
+ public void scheduleOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+ scheduleAsyncRequest(request);
+ }
+
+ /**
+ * Execute async request on remote service immediately instead of sending it to Handler queue.
+ */
+ public void executeOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+ executeAsyncRequest(request);
+ }
+
+ /**
+ * Failure callback
+ */
+ public interface RemoteSmartspaceServiceCallbacks
+ extends VultureCallback<RemoteSmartspaceService> {
+
+ /**
+ * Notifies a the failure or timeout of a remote call.
+ */
+ void onFailureOrTimeout(boolean timedOut);
+
+ /**
+ * Notifies change in connected state of the remote service.
+ */
+ void onConnectedStateChanged(boolean connected);
+ }
+
+ @Override // from AbstractRemoteService
+ protected void handleOnConnectedStateChanged(boolean connected) {
+ if (mCallback != null) {
+ mCallback.onConnectedStateChanged(connected);
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
new file mode 100644
index 0000000..169b85eb
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import static android.Manifest.permission.MANAGE_SMARTSPACE;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.SMARTSPACE_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.ISmartspaceManager;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to return smartspace targets given a query.
+ */
+public class SmartspaceManagerService extends
+ AbstractMasterSystemService<SmartspaceManagerService, SmartspacePerUserService> {
+
+ private static final String TAG = SmartspaceManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ public SmartspaceManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultSmartspaceService), null,
+ PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected SmartspacePerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+ return new SmartspacePerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(SMARTSPACE_SERVICE, new SmartspaceManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_SMARTSPACE, TAG);
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+ final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageUpdatedLocked();
+ }
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+ final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageRestartedLocked();
+ }
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class SmartspaceManagerStub extends ISmartspaceManager.Stub {
+
+ @Override
+ public void createSmartspaceSession(@NonNull SmartspaceConfig smartspaceConfig,
+ @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+ runForUserLocked("createSmartspaceSession", sessionId, (service) ->
+ service.onCreateSmartspaceSessionLocked(smartspaceConfig, sessionId, token));
+ }
+
+ @Override
+ public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+ SmartspaceTargetEvent event) {
+ runForUserLocked("notifySmartspaceEvent", sessionId,
+ (service) -> service.notifySmartspaceEventLocked(sessionId, event));
+ }
+
+ @Override
+ public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+ runForUserLocked("requestSmartspaceUpdate", sessionId,
+ (service) -> service.requestSmartspaceUpdateLocked(sessionId));
+ }
+
+ @Override
+ public void registerSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ runForUserLocked("registerSmartspaceUpdates", sessionId,
+ (service) -> service.registerSmartspaceUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ runForUserLocked("unregisterSmartspaceUpdates", sessionId,
+ (service) -> service.unregisterSmartspaceUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void destroySmartspaceSession(@NonNull SmartspaceSessionId sessionId) {
+ runForUserLocked("destroySmartspaceSession", sessionId,
+ (service) -> service.onDestroyLocked(sessionId));
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ new SmartspaceManagerServiceShellCommand(SmartspaceManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private void runForUserLocked(@NonNull final String func,
+ @NonNull final SmartspaceSessionId sessionId,
+ @NonNull final Consumer<SmartspacePerUserService> c) {
+ ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+ final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ sessionId.getUserId(), false, ALLOW_NON_FULL, null, null);
+
+ if (DEBUG) {
+ Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ }
+ if (!(mServiceNameResolver.isTemporary(userId)
+ || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+
+ String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final SmartspacePerUserService service = getServiceForUserLocked(userId);
+ c.accept(service);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
new file mode 100644
index 0000000..4143418
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the SmartspaceManagerService.
+ */
+public class SmartspaceManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ SmartspaceManagerServiceShellCommand.class.getSimpleName();
+
+ private final SmartspaceManagerService mService;
+
+ public SmartspaceManagerServiceShellCommand(@NonNull SmartspaceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ pw.println("SmartspaceService temporarily reset. ");
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("SmartspaceService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("SmartspaceManagerService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
new file mode 100644
index 0000000..db43468
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService;
+import android.service.smartspace.SmartspaceService;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link SmartspaceManagerService}.
+ */
+public class SmartspacePerUserService extends
+ AbstractPerUserSystemService<SmartspacePerUserService, SmartspaceManagerService>
+ implements RemoteSmartspaceService.RemoteSmartspaceServiceCallbacks {
+
+ private static final String TAG = SmartspacePerUserService.class.getSimpleName();
+ @GuardedBy("mLock")
+ private final ArrayMap<SmartspaceSessionId, SmartspaceSessionInfo> mSessionInfos =
+ new ArrayMap<>();
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteSmartspaceService mRemoteService;
+ /**
+ * When {@code true}, remote service died but service state is kept so it's restored after
+ * the system re-binds to it.
+ */
+ @GuardedBy("mLock")
+ private boolean mZombie;
+
+ protected SmartspacePerUserService(SmartspaceManagerService master,
+ Object lock, int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws NameNotFoundException {
+
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new NameNotFoundException("Could not get service for " + serviceComponent);
+ }
+ // TODO(b/177858728): must check that either the service is from a system component,
+ // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+ // OEMs are implementing the real service and also verify the proper permissions
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ if (enabledChanged) {
+ if (!isEnabledLocked()) {
+ // Clear the remote service for the next call
+ updateRemoteServiceLocked();
+ }
+ }
+ return enabledChanged;
+ }
+
+ /**
+ * Notifies the service of a new smartspace session.
+ */
+ @GuardedBy("mLock")
+ public void onCreateSmartspaceSessionLocked(@NonNull SmartspaceConfig smartspaceConfig,
+ @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.onCreateSmartspaceSession(smartspaceConfig, sessionId));
+
+ if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+ final SmartspaceSessionInfo sessionInfo = new SmartspaceSessionInfo(
+ sessionId, smartspaceConfig, token, () -> {
+ synchronized (mLock) {
+ onDestroyLocked(sessionId);
+ }
+ });
+ if (sessionInfo.linkToDeath()) {
+ mSessionInfos.put(sessionId, sessionInfo);
+ } else {
+ // destroy the session if calling process is already dead
+ onDestroyLocked(sessionId);
+ }
+ }
+ }
+
+ /**
+ * Records an smartspace event to the service.
+ */
+ @GuardedBy("mLock")
+ public void notifySmartspaceEventLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull SmartspaceTargetEvent event) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s -> s.notifySmartspaceEvent(sessionId, event));
+ }
+
+ /**
+ * Requests the service to return smartspace results of an input query.
+ */
+ @GuardedBy("mLock")
+ public void requestSmartspaceUpdateLocked(@NonNull SmartspaceSessionId sessionId) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId,
+ s -> s.requestSmartspaceUpdate(sessionId));
+ }
+
+ /**
+ * Registers a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void registerSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.registerSmartspaceUpdates(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.addCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void unregisterSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.unregisterSmartspaceUpdates(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.removeCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Notifies the service of the end of an existing smartspace session.
+ */
+ @GuardedBy("mLock")
+ public void onDestroyLocked(@NonNull SmartspaceSessionId sessionId) {
+ if (isDebug()) {
+ Slog.d(TAG, "onDestroyLocked(): sessionId=" + sessionId);
+ }
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s -> s.onDestroySmartspaceSession(sessionId));
+ sessionInfo.destroy();
+ }
+
+ @Override
+ public void onFailureOrTimeout(boolean timedOut) {
+ if (isDebug()) {
+ Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+ }
+ // Do nothing, we are just proxying to the smartspace ui service
+ }
+
+ @Override
+ public void onConnectedStateChanged(boolean connected) {
+ if (isDebug()) {
+ Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
+ }
+ if (connected) {
+ synchronized (mLock) {
+ if (mZombie) {
+ // Validation check - shouldn't happen
+ if (mRemoteService == null) {
+ Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
+ return;
+ }
+ mZombie = false;
+ resurrectSessionsLocked();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDied(RemoteSmartspaceService service) {
+ if (isDebug()) {
+ Slog.w(TAG, "onServiceDied(): service=" + service);
+ }
+ synchronized (mLock) {
+ mZombie = true;
+ }
+ updateRemoteServiceLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void updateRemoteServiceLocked() {
+ if (mRemoteService != null) {
+ mRemoteService.destroy();
+ mRemoteService = null;
+ }
+ }
+
+ void onPackageUpdatedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageUpdatedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ void onPackageRestartedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageRestartedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ private void destroyAndRebindRemoteService() {
+ if (mRemoteService == null) {
+ return;
+ }
+
+ if (isDebug()) {
+ Slog.d(TAG, "Destroying the old remote service.");
+ }
+ mRemoteService.destroy();
+ mRemoteService = null;
+
+ synchronized (mLock) {
+ mZombie = true;
+ }
+ mRemoteService = getRemoteServiceLocked();
+ if (mRemoteService != null) {
+ if (isDebug()) {
+ Slog.d(TAG, "Rebinding to the new remote service.");
+ }
+ mRemoteService.reconnect();
+ }
+ }
+
+ /**
+ * Called after the remote service connected, it's used to restore state from a 'zombie'
+ * service (i.e., after it died).
+ */
+ private void resurrectSessionsLocked() {
+ final int numSessions = mSessionInfos.size();
+ if (isDebug()) {
+ Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
+ + numSessions + " sessions.");
+ }
+
+ for (SmartspaceSessionInfo sessionInfo : mSessionInfos.values()) {
+ sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ protected boolean resolveService(
+ @NonNull final SmartspaceSessionId sessionId,
+ @NonNull final AbstractRemoteService.AsyncRequest<ISmartspaceService> cb) {
+
+ final RemoteSmartspaceService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.executeOnResolvedService(cb);
+ }
+ return service != null;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteSmartspaceService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteSmartspaceService(getContext(),
+ SmartspaceService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+
+ private static final class SmartspaceSessionInfo {
+ private static final boolean DEBUG = false; // Do not submit with true
+ @NonNull
+ final IBinder mToken;
+ @NonNull
+ final IBinder.DeathRecipient mDeathRecipient;
+ @NonNull
+ private final SmartspaceSessionId mSessionId;
+ @NonNull
+ private final SmartspaceConfig mSmartspaceConfig;
+ private final RemoteCallbackList<ISmartspaceCallback> mCallbacks =
+ new RemoteCallbackList<ISmartspaceCallback>() {
+ @Override
+ public void onCallbackDied(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Binder died for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ if (mCallbacks.getRegisteredCallbackCount() == 0) {
+ destroy();
+ }
+ }
+ };
+
+ SmartspaceSessionInfo(
+ @NonNull final SmartspaceSessionId id,
+ @NonNull final SmartspaceConfig context,
+ @NonNull final IBinder token,
+ @NonNull final IBinder.DeathRecipient deathRecipient) {
+ if (DEBUG) {
+ Slog.d(TAG, "Creating SmartspaceSessionInfo for session Id=" + id);
+ }
+ mSessionId = id;
+ mSmartspaceConfig = context;
+ mToken = token;
+ mDeathRecipient = deathRecipient;
+ }
+
+ void addCallbackLocked(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Storing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.register(callback);
+ }
+
+ void removeCallbackLocked(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.unregister(callback);
+ }
+
+ boolean linkToDeath() {
+ try {
+ mToken.linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
+ + mSessionId);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ void destroy() {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
+ + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
+ }
+ if (mToken != null) {
+ mToken.unlinkToDeath(mDeathRecipient, 0);
+ }
+ mCallbacks.kill();
+ }
+
+ void resurrectSessionLocked(SmartspacePerUserService service, IBinder token) {
+ int callbackCount = mCallbacks.getRegisteredCallbackCount();
+ if (DEBUG) {
+ Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
+ + ") for session Id=" + mSessionId + " and "
+ + callbackCount + " callbacks.");
+ }
+ service.onCreateSmartspaceSessionLocked(mSmartspaceConfig, mSessionId, token);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index 0a35db5..f7b2492 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -16,34 +16,61 @@
package com.android.server;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
import android.hardware.vibrator.IVibrator;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.os.CombinedVibrationEffect;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.IVibratorStateListener;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.vibrator.FakeVibrator;
import com.android.server.vibrator.FakeVibratorControllerProvider;
import com.android.server.vibrator.VibratorController;
@@ -51,12 +78,16 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Predicate;
/**
* Tests for {@link VibratorManagerService}.
@@ -67,39 +98,86 @@
@Presubmit
public class VibratorManagerServiceTest {
+ private static final int TEST_TIMEOUT_MILLIS = 1_000;
private static final int UID = Process.ROOT_UID;
private static final String PACKAGE_NAME = "package";
+ private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
+ private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
+ .setBatterySaverEnabled(true).build();
private static final VibrationAttributes ALARM_ATTRS =
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
+ private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_TOUCH).build();
+ private static final VibrationAttributes NOTIFICATION_ATTRS =
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_NOTIFICATION).build();
+ private static final VibrationAttributes RINGTONE_ATTRS =
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_RINGTONE).build();
@Rule public MockitoRule rule = MockitoJUnit.rule();
+ @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+ @Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private PowerManagerInternal mPowerManagerInternalMock;
@Mock private PowerSaveState mPowerSaveStateMock;
+ @Mock private AppOpsManager mAppOpsManagerMock;
+ @Mock private IInputManager mIInputManagerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
+ private Context mContextSpy;
private TestLooper mTestLooper;
+ private FakeVibrator mVibrator;
+ private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
+ mVibrator = new FakeVibrator();
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+ ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+ when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+ when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibrator);
+ when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
+ when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
+ when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+ when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+ .thenReturn(new ComponentName("", ""));
when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
.thenReturn(mPowerSaveStateMock);
+ doAnswer(invocation -> {
+ mRegisteredPowerModeListener = invocation.getArgument(0);
+ return null;
+ }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+
+ addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+ mTestLooper.startAutoDispatch();
}
@After
public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(PowerManagerInternal.class);
}
private VibratorManagerService createService() {
VibratorManagerService service = new VibratorManagerService(
- InstrumentationRegistry.getContext(),
+ mContextSpy,
new VibratorManagerService.Injector() {
@Override
VibratorManagerService.NativeWrapper getNativeWrapper() {
@@ -172,6 +250,74 @@
}
@Test
+ public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createService();
+ IVibratorStateListener listenerMock = mockVibratorStateListener();
+ service.registerVibratorStateListener(1, listenerMock);
+
+ vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+ // Wait until service knows vibrator is on.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ // Wait until effect ends.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ InOrder inOrderVerifier = inOrder(listenerMock);
+ // First notification done when listener is registered.
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ inOrderVerifier.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createService();
+ IVibratorStateListener listenerMock = mockVibratorStateListener();
+ service.registerVibratorStateListener(1, listenerMock);
+
+ vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+
+ // Wait until service knows vibrator is on.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ service.unregisterVibratorStateListener(1, listenerMock);
+
+ // Wait until vibrator is off.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ InOrder inOrderVerifier = inOrder(listenerMock);
+ // First notification done when listener is registered.
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+ inOrderVerifier.verify(listenerMock, atLeastOnce()).asBinder(); // unregister
+ inOrderVerifier.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception {
+ mockVibrators(0, 1, 2);
+ VibratorManagerService service = createService();
+ IVibratorStateListener[] listeners = new IVibratorStateListener[3];
+ for (int i = 0; i < 3; i++) {
+ listeners[i] = mockVibratorStateListener();
+ service.registerVibratorStateListener(i, listeners[i]);
+ }
+
+ vibrate(service, CombinedVibrationEffect.startSynced()
+ .addVibrator(0, VibrationEffect.createOneShot(40, 100))
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .combine(), ALARM_ATTRS);
+ // Wait until service knows vibrator is on.
+ assertTrue(waitUntil(s -> s.isVibrating(0), service, TEST_TIMEOUT_MILLIS));
+
+ verify(listeners[0]).onVibrating(eq(true));
+ verify(listeners[1]).onVibrating(eq(true));
+ verify(listeners[2], never()).onVibrating(eq(true));
+ }
+
+ @Test
public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
@@ -276,22 +422,264 @@
}
@Test
- public void vibrate_isUnsupported() {
+ public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
+ mockVibrators(1);
+ mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
VibratorManagerService service = createService();
- CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
- VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
- assertExpectException(UnsupportedOperationException.class,
- "Not implemented",
- () -> service.vibrate(UID, PACKAGE_NAME, effect, ALARM_ATTRS, "reason", service));
+ vibrate(service, VibrationEffect.createOneShot(40, 1), RINGTONE_ATTRS);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ service = createService();
+ vibrate(service, VibrationEffect.createOneShot(40, 10), RINGTONE_ATTRS);
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ service = createService();
+ vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS);
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ assertEquals(2, mVibratorProviders.get(1).getEffects().size());
+ assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes());
}
@Test
- public void cancelVibrate_isUnsupported() {
+ public void vibrate_withPowerMode_usesPowerModeState() throws Exception {
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createService();
- CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
- VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
- assertExpectException(UnsupportedOperationException.class,
- "Not implemented", () -> service.cancelVibrate(service));
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+ vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+ vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+ service, TEST_TIMEOUT_MILLIS));
+
+ assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes());
+ }
+
+ @Test
+ public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
+ VibratorManagerService service = createService();
+
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
+ VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
+ audioAttributes, effect).build();
+
+ vibrate(service, effect, vibrationAttributes);
+
+ verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
+ }
+
+ @Test
+ public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
+ VibratorManagerService service = createService();
+
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_UNKNOWN).build());
+
+ InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
+ anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+ }
+
+ @Test
+ public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
+ setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
+ VibratorManagerService service = createService();
+
+ // Prebaked vibration will play fallback waveform on input device.
+ ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+ verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any());
+ assertTrue(captor.getValue() instanceof VibrationEffect.Waveform);
+
+ VibrationEffect[] effects = new VibrationEffect[]{
+ VibrationEffect.createOneShot(100, 128),
+ VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose(),
+ };
+
+ for (VibrationEffect effect : effects) {
+ vibrate(service, effect, ALARM_ATTRS);
+ verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
+ }
+
+ // VibrationThread will start this vibration async, so wait before checking it never played.
+ assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
+ service, /* timeout= */ 50));
+ }
+
+ @Test
+ public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
+ IVibrator.CAP_AMPLITUDE_CONTROL);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createService();
+ // The native callback will be dispatched manually in this test.
+ mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait before triggering callbacks.
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ // Trigger callbacks from controller.
+ mTestLooper.moveTimeForward(50);
+ mTestLooper.dispatchAll();
+
+ // VibrationThread needs some time to react to native callbacks and stop the vibrator.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ }
+
+
+ @Test
+ public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
+ mVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_HIGH);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_OFF);
+
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
+ IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createService();
+
+ vibrate(service, CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .combine(), ALARM_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.createOneShot(20, 100))
+ .combine(), NOTIFICATION_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .compose(), HAPTIC_FEEDBACK_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+
+ assertEquals(3, fakeVibrator.getEffects().size());
+ assertEquals(1, fakeVibrator.getAmplitudes().size());
+
+ // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
+ VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false,
+ VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertEquals(expected, fakeVibrator.getEffects().get(0));
+
+ // Notification vibrations will be scaled with SCALE_VERY_HIGH.
+ assertTrue(150 < fakeVibrator.getAmplitudes().get(0));
+
+ // Haptic feedback vibrations will be scaled with SCALE_LOW.
+ VibrationEffect.Composed played =
+ (VibrationEffect.Composed) fakeVibrator.getEffects().get(2);
+ assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale);
+ assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale);
+
+ // Ring vibrations have intensity OFF and are not played.
+ }
+
+ @Test
+ public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
+ mockVibrators(1, 2);
+ VibratorManagerService service = createService();
+ vibrate(service,
+ CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+ .combine(),
+ HAPTIC_FEEDBACK_ATTRS);
+
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ // Haptic feedback cancelled on low power mode.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ }
+
+ @Test
+ public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createService();
+
+ vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ service.updateServiceState();
+ // Vibration is not stopped nearly after updating service.
+ assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
+ }
+
+ @Test
+ public void cancelVibrate_stopsVibrating() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createService();
+
+ service.cancelVibrate(service);
+ assertFalse(service.isVibrating(1));
+
+ vibrate(service, VibrationEffect.createOneShot(10_000, 100), ALARM_ATTRS);
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ service.cancelVibrate(service);
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
}
private void mockVibrators(int... vibratorIds) {
@@ -302,8 +690,56 @@
when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
}
+ private IVibratorStateListener mockVibratorStateListener() {
+ IVibratorStateListener listenerMock = mock(IVibratorStateListener.class);
+ IBinder binderMock = mock(IBinder.class);
+ when(listenerMock.asBinder()).thenReturn(binderMock);
+ return listenerMock;
+ }
+
+ private InputDevice createInputDeviceWithVibrator(int id) {
+ return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
+ null, /* hasVibrator= */ true, false, false, false, false);
+ }
+
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);
}
+
+ private void setRingerMode(int ringerMode) {
+ AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
+ audioManager.setRingerModeInternal(ringerMode);
+ assertEquals(ringerMode, audioManager.getRingerModeInternal());
+ }
+
+ private void setUserSetting(String settingName, int value) {
+ Settings.System.putIntForUser(
+ mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+ }
+
+ private void setGlobalSetting(String settingName, int value) {
+ Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
+ }
+
+ private void vibrate(VibratorManagerService service, VibrationEffect effect,
+ VibrationAttributes attrs) {
+ vibrate(service, CombinedVibrationEffect.createSynced(effect), attrs);
+ }
+
+ private void vibrate(VibratorManagerService service, CombinedVibrationEffect effect,
+ VibrationAttributes attrs) {
+ service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+ }
+
+ private boolean waitUntil(Predicate<VibratorManagerService> predicate,
+ VibratorManagerService service, long timeout) throws InterruptedException {
+ long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
+ boolean predicateResult = false;
+ while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
+ Thread.sleep(10);
+ predicateResult = predicate.test(service);
+ }
+ return predicateResult;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 743848c..2a7905a 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -582,16 +582,19 @@
VibratorService service = createService();
service.registerVibratorStateListener(mVibratorStateListenerMock);
- verify(mVibratorStateListenerMock).onVibrating(false);
- vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
+ vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait before triggering callbacks.
Thread.sleep(10);
+ assertTrue(service.isVibrating());
+
service.unregisterVibratorStateListener(mVibratorStateListenerMock);
// Trigger callbacks from controller.
- mTestLooper.moveTimeForward(150);
+ mTestLooper.moveTimeForward(50);
mTestLooper.dispatchAll();
+ Thread.sleep(20);
+ assertFalse(service.isVibrating());
InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
// First notification done when listener is registered.
@@ -745,7 +748,8 @@
private InputDevice createInputDeviceWithVibrator(int id) {
return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
- null, /* hasVibrator= */ true, false, false, false /* hasSensor */);
+ null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
+ false /* hasBattery */);
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
new file mode 100644
index 0000000..e44b0836
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2021 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.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.power.PowerStatsInternal;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.power.MeasuredEnergyArray;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Tests for {@link BatteryExternalStatsWorker}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:BatteryExternalStatsWorkerTest
+ */
+public class BatteryExternalStatsWorkerTest {
+ private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
+ private TestBatteryStatsImpl mBatteryStatsImpl;
+ private TestPowerStatsInternal mPowerStatsInternal;
+
+ @Before
+ public void setUp() {
+ final Context context = InstrumentationRegistry.getContext();
+
+ mBatteryStatsImpl = new TestBatteryStatsImpl();
+ mPowerStatsInternal = new TestPowerStatsInternal();
+ mBatteryExternalStatsWorker = new BatteryExternalStatsWorker(new TestInjector(context),
+ mBatteryStatsImpl);
+ }
+
+ @Test
+ public void getEnergyConsumptionData() {
+ SparseLongArray expectSubsystems = new SparseLongArray();
+ // Add some energy consumers used by BatteryExternalStatsWorker.
+ final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
+ "display");
+ mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
+ expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
+
+ // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
+ // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
+ final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
+ mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
+
+ // Inform BESW that PowerStatsInternal is ready to query
+ mBatteryExternalStatsWorker.systemServicesReady();
+
+ MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
+
+ assertEquals(expectSubsystems.size(), energies.size());
+ final int size = expectSubsystems.size();
+
+ for (int i = 0; i < size; i++) {
+ int subsystem = expectSubsystems.keyAt(i);
+ // find the subsystem in the returned MeasuredEnergyArray
+ int subsystemIndex = -1;
+ for (int j = 0; j < size; j++) {
+ if (subsystem == energies.getSubsystem(i)) {
+ subsystemIndex = i;
+ break;
+ }
+ }
+ assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
+ subsystemIndex);
+ assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
+ }
+ }
+
+ public class TestInjector extends BatteryExternalStatsWorker.Injector {
+ public TestInjector(Context context) {
+ super(context);
+ }
+
+ public <T> T getSystemService(Class<T> serviceClass) {
+ return null;
+ }
+
+ public <T> T getLocalService(Class<T> serviceClass) {
+ if (serviceClass == PowerStatsInternal.class) {
+ return (T) mPowerStatsInternal;
+ }
+ return null;
+ }
+ }
+
+ public class TestBatteryStatsImpl extends BatteryStatsImpl {
+ }
+
+ public class TestPowerStatsInternal extends PowerStatsInternal {
+ private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>();
+ private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults =
+ new SparseArray<>();
+ private final int mTimeSinceBoot = 0;
+
+ @Override
+ public EnergyConsumer[] getEnergyConsumerInfo() {
+ final int size = mEnergyConsumers.size();
+ final EnergyConsumer[] consumers = new EnergyConsumer[size];
+ for (int i = 0; i < size; i++) {
+ consumers[i] = mEnergyConsumers.valueAt(i);
+ }
+ return consumers;
+ }
+
+ @Override
+ public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
+ int[] energyConsumerIds) {
+ final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture();
+ final int size = mEnergyConsumerResults.size();
+ final EnergyConsumerResult[] results = new EnergyConsumerResult[size];
+ for (int i = 0; i < size; i++) {
+ results[i] = mEnergyConsumerResults.valueAt(i);
+ }
+ future.complete(results);
+ return future;
+ }
+
+ /**
+ * Util method to add a new EnergyConsumer for testing
+ *
+ * @return the EnergyConsumer id of the new EnergyConsumer
+ */
+ public int addEnergyConsumer(@EnergyConsumerType byte type, int ordinal, String name) {
+ final EnergyConsumer consumer = new EnergyConsumer();
+ final int id = getNextAvailableId();
+ consumer.id = id;
+ consumer.type = type;
+ consumer.ordinal = ordinal;
+ consumer.name = name;
+ mEnergyConsumers.put(id, consumer);
+
+ final EnergyConsumerResult result = new EnergyConsumerResult();
+ result.id = id;
+ result.timestampMs = mTimeSinceBoot;
+ result.energyUWs = 0;
+ mEnergyConsumerResults.put(id, result);
+ return id;
+ }
+
+ public void incrementEnergyConsumption(int id, long energyUWs) {
+ EnergyConsumerResult result = mEnergyConsumerResults.get(id, null);
+ assertNotNull(result);
+ result.energyUWs += energyUWs;
+ }
+
+ private int getNextAvailableId() {
+ final int size = mEnergyConsumers.size();
+ // Just return the first index that does not match the key (aka the EnergyConsumer id)
+ for (int i = size - 1; i >= 0; i--) {
+ if (mEnergyConsumers.keyAt(i) == i) return i + 1;
+ }
+ // Otherwise return the lowest id
+ return 0;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java
rename to services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 6fc6b9e..738f008 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.graphics;
+package com.android.server.app;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
diff --git a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java
rename to services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
index 22df645..caa46da 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/GameManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.server.graphics;
+package com.android.server.app;
import static org.junit.Assert.assertEquals;
-import android.graphics.GameManager;
+import android.app.GameManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java
new file mode 100644
index 0000000..275e7c7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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.graphics.fonts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FontCrashDetectorTest {
+
+ private File mCacheDir;
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest");
+ FileUtils.deleteContentsAndDir(mCacheDir);
+ mCacheDir.mkdirs();
+ }
+
+ @Test
+ public void detectCrash() throws Exception {
+ // Prepare a marker file.
+ File file = new File(mCacheDir, "detectCrash");
+ assertThat(file.createNewFile()).isTrue();
+
+ FontCrashDetector detector = new FontCrashDetector(file);
+ assertThat(detector.hasCrashed()).isTrue();
+
+ detector.clear();
+ assertThat(detector.hasCrashed()).isFalse();
+ assertThat(file.exists()).isFalse();
+ }
+
+ @Test
+ public void monitorCrash() {
+ File file = new File(mCacheDir, "monitorCrash");
+ FontCrashDetector detector = new FontCrashDetector(file);
+ assertThat(detector.hasCrashed()).isFalse();
+
+ FontCrashDetector.MonitoredBlock block = detector.start();
+ assertThat(file.exists()).isTrue();
+
+ block.close();
+ assertThat(file.exists()).isFalse();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index d067790..8331031 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -147,6 +147,7 @@
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dirForPreparation.loadFontFileMap();
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
.isEqualTo(expectedModifiedDate);
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
@@ -162,6 +163,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -177,6 +179,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
}
@@ -187,6 +190,7 @@
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dirForPreparation.loadFontFileMap();
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -199,6 +203,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -211,6 +216,7 @@
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dirForPreparation.loadFontFileMap();
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -224,6 +230,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -236,6 +243,7 @@
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dirForPreparation.loadFontFileMap();
installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
@@ -250,6 +258,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
// For foo.ttf, preinstalled font (revision 5) should be used.
assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
// For bar.ttf, updated font (revision 4) should be used.
@@ -268,6 +277,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
new File("/dev/null"));
+ dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
}
@@ -278,6 +288,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
installFontFile(dir, "test,1", GOOD_SIGNATURE);
assertThat(dir.getFontFileMap()).containsKey("test.ttf");
@@ -295,6 +306,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
installFontFile(dir, "test,1", GOOD_SIGNATURE);
Map<String, File> mapBeforeUpgrade = dir.getFontFileMap();
@@ -313,6 +325,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
installFontFile(dir, "test,2", GOOD_SIGNATURE);
try {
@@ -333,6 +346,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
installFontFile(dir, "bar,2", GOOD_SIGNATURE);
@@ -349,6 +363,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
try {
installFontFile(dir, "test,1", "Invalid signature");
@@ -368,6 +383,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
try {
installFontFile(dir, "test,1", GOOD_SIGNATURE);
@@ -398,6 +414,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
readonlyFile);
+ dir.loadFontFileMap();
try {
installFontFile(dir, "test,2", GOOD_SIGNATURE);
@@ -429,6 +446,7 @@
return 0;
}
}, fakeFsverityUtil, mConfigFile);
+ dir.loadFontFileMap();
try {
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
@@ -456,6 +474,7 @@
return 0;
}
}, fakeFsverityUtil, mConfigFile);
+ dir.loadFontFileMap();
try {
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
@@ -491,6 +510,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
+ dir.loadFontFileMap();
try {
installFontFile(dir, "foo,1", GOOD_SIGNATURE);
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 15a9bcf..6208801 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -21,9 +21,11 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
-import android.util.Log;
+import android.annotation.NonNull;
import android.util.Pair;
+import android.util.SparseIntArray;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
@@ -58,106 +60,112 @@
* Represents running and pending jobs.
*/
class Jobs {
- public int runningFg;
- public int runningBg;
- public int pendingFg;
- public int pendingBg;
+ public final SparseIntArray running = new SparseIntArray();
+ public final SparseIntArray pending = new SparseIntArray();
public void maybeEnqueueJobs(double startRatio, double fgJobRatio) {
while (mRandom.nextDouble() < startRatio) {
if (mRandom.nextDouble() < fgJobRatio) {
- pendingFg++;
+ pending.put(WORK_TYPE_TOP, pending.get(WORK_TYPE_TOP) + 1);
} else {
- pendingBg++;
+ pending.put(WORK_TYPE_BG, pending.get(WORK_TYPE_BG) + 1);
}
}
}
public void maybeFinishJobs(double stopRatio) {
- for (int i = runningBg; i > 0; i--) {
+ for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
if (mRandom.nextDouble() < stopRatio) {
- runningBg--;
+ running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
}
}
- for (int i = runningFg; i > 0; i--) {
+ for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) {
if (mRandom.nextDouble() < stopRatio) {
- runningFg--;
+ running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1);
}
}
}
}
- private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
- mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig("critical",
- totalMax,
- // defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, totalMax - maxBg),
- Pair.create(WORK_TYPE_BG, minBg)),
- // defaultMax
- List.of(Pair.create(WORK_TYPE_BG, maxBg))));
+ private void startPendingJobs(Jobs jobs, int totalMax,
+ @NonNull List<Pair<Integer, Integer>> minLimits,
+ @NonNull List<Pair<Integer, Integer>> maxLimits) {
+ mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig(
+ "test", totalMax, minLimits, maxLimits));
mWorkCountTracker.resetCounts();
- for (int i = 0; i < jobs.runningFg; i++) {
- mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP);
- }
- for (int i = 0; i < jobs.runningBg; i++) {
- mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG);
- }
+ for (int i = 0; i < jobs.running.size(); ++i) {
+ final int workType = jobs.running.keyAt(i);
+ final int count = jobs.running.valueAt(i);
- for (int i = 0; i < jobs.pendingFg; i++) {
- mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP);
+ for (int c = 0; c < count; ++c) {
+ mWorkCountTracker.incrementRunningJobCount(workType);
+ }
}
- for (int i = 0; i < jobs.pendingBg; i++) {
- mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_BG);
+ for (int i = 0; i < jobs.pending.size(); ++i) {
+ final int workType = jobs.pending.keyAt(i);
+ final int count = jobs.pending.valueAt(i);
+
+ for (int c = 0; c < count; ++c) {
+ mWorkCountTracker.incrementPendingJobCount(workType);
+ }
}
mWorkCountTracker.onCountDone();
- while ((jobs.pendingFg > 0
+ while ((jobs.pending.get(WORK_TYPE_TOP) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
- || (jobs.pendingBg > 0
+ || (jobs.pending.get(WORK_TYPE_BG) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
final boolean isStartingFg = mRandom.nextBoolean();
if (isStartingFg) {
- if (jobs.pendingFg > 0
+ if (jobs.pending.get(WORK_TYPE_TOP) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
- jobs.pendingFg--;
- jobs.runningFg++;
+ jobs.pending.put(WORK_TYPE_TOP, jobs.pending.get(WORK_TYPE_TOP) - 1);
+ jobs.running.put(WORK_TYPE_TOP, jobs.running.get(WORK_TYPE_TOP) + 1);
mWorkCountTracker.stageJob(WORK_TYPE_TOP);
mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
}
} else {
- if (jobs.pendingBg > 0
+ if (jobs.pending.get(WORK_TYPE_BG) > 0
&& mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
- jobs.pendingBg--;
- jobs.runningBg++;
+ jobs.pending.put(WORK_TYPE_BG, jobs.pending.get(WORK_TYPE_BG) - 1);
+ jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) + 1);
mWorkCountTracker.stageJob(WORK_TYPE_BG);
mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
}
}
}
-
- Log.i(TAG, "" + mWorkCountTracker);
}
/**
* Used by the following testRandom* tests.
*/
- private void checkRandom(Jobs jobs, int numTests, int totalMax, int maxBg, int minBg,
+ private void checkRandom(Jobs jobs, int numTests, int totalMax,
+ @NonNull List<Pair<Integer, Integer>> minLimits,
+ @NonNull List<Pair<Integer, Integer>> maxLimits,
double startRatio, double fgJobRatio, double stopRatio) {
for (int i = 0; i < numTests; i++) {
-
jobs.maybeFinishJobs(stopRatio);
jobs.maybeEnqueueJobs(startRatio, fgJobRatio);
- startPendingJobs(jobs, totalMax, maxBg, minBg);
+ startPendingJobs(jobs, totalMax, minLimits, maxLimits);
- assertThat(jobs.runningFg).isAtMost(totalMax);
- assertThat(jobs.runningBg).isAtMost(totalMax);
- assertThat(jobs.runningFg + jobs.runningBg).isAtMost(totalMax);
- assertThat(jobs.runningBg).isAtMost(maxBg);
+ int totalRunning = 0;
+ for (int r = 0; r < jobs.running.size(); ++r) {
+ final int numRunning = jobs.running.valueAt(r);
+ assertWithMessage(
+ "Work type " + jobs.running.keyAt(r) + " is running too many jobs")
+ .that(numRunning).isAtMost(totalMax);
+ totalRunning += numRunning;
+ }
+ assertThat(totalRunning).isAtMost(totalMax);
+ for (Pair<Integer, Integer> maxLimit : maxLimits) {
+ assertWithMessage("Work type " + maxLimit.first + " is running too many jobs")
+ .that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second);
+ }
}
}
@@ -170,13 +178,14 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.1;
final double fgJobRatio = 0.5;
final double startRatio = 0.1;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio , stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -185,13 +194,14 @@
final int numTests = 5000;
final int totalMax = 2;
- final int maxBg = 2;
- final int minBg = 0;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> minLimits = List.of();
final double stopRatio = 0.5;
final double fgJobRatio = 0.5;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -200,13 +210,14 @@
final int numTests = 5000;
final int totalMax = 2;
- final int maxBg = 2;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
final double fgJobRatio = 0.5;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -215,13 +226,14 @@
final int numTests = 5000;
final int totalMax = 10;
- final int maxBg = 2;
- final int minBg = 0;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+ final List<Pair<Integer, Integer>> minLimits = List.of();
final double stopRatio = 0.5;
final double fgJobRatio = 0.5;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -230,13 +242,14 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
final double fgJobRatio = 0.1;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -245,13 +258,14 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.5;
final double fgJobRatio = 0.9;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -260,13 +274,14 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.4;
final double fgJobRatio = 0.1;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
@Test
@@ -275,53 +290,135 @@
final int numTests = 5000;
final int totalMax = 6;
- final int maxBg = 4;
- final int minBg = 2;
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+ final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double stopRatio = 0.4;
final double fgJobRatio = 0.9;
final double startRatio = 0.5;
- checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+ startRatio, fgJobRatio, stopRatio);
}
/** Used by the following tests */
- private void checkSimple(int totalMax, int maxBg, int minBg,
- int runningFg, int runningBg, int pendingFg, int pendingBg,
- int resultRunningFg, int resultRunningBg, int resultPendingFg, int resultPendingBg) {
+ private void checkSimple(int totalMax,
+ @NonNull List<Pair<Integer, Integer>> minLimits,
+ @NonNull List<Pair<Integer, Integer>> maxLimits,
+ @NonNull List<Pair<Integer, Integer>> running,
+ @NonNull List<Pair<Integer, Integer>> pending,
+ @NonNull List<Pair<Integer, Integer>> resultRunning,
+ @NonNull List<Pair<Integer, Integer>> resultPending) {
final Jobs jobs = new Jobs();
- jobs.runningFg = runningFg;
- jobs.runningBg = runningBg;
- jobs.pendingFg = pendingFg;
- jobs.pendingBg = pendingBg;
+ for (Pair<Integer, Integer> run : running) {
+ jobs.running.put(run.first, run.second);
+ }
+ for (Pair<Integer, Integer> pend : pending) {
+ jobs.pending.put(pend.first, pend.second);
+ }
- startPendingJobs(jobs, totalMax, maxBg, minBg);
+ startPendingJobs(jobs, totalMax, minLimits, maxLimits);
-// fail(mWorkerCountTracker.toString());
- assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
- assertThat(jobs.runningBg).isEqualTo(resultRunningBg);
-
- assertThat(jobs.pendingFg).isEqualTo(resultPendingFg);
- assertThat(jobs.pendingBg).isEqualTo(resultPendingBg);
+ for (Pair<Integer, Integer> run : resultRunning) {
+ assertWithMessage("Incorrect running result for work type " + run.first)
+ .that(jobs.running.get(run.first)).isEqualTo(run.second);
+ }
+ for (Pair<Integer, Integer> pend : resultPending) {
+ assertWithMessage("Incorrect pending result for work type " + pend.first)
+ .that(jobs.pending.get(pend.first)).isEqualTo(pend.second);
+ }
}
-
@Test
public void testBasic() {
- // Args are:
- // First 3: Total-max, bg-max, bg-min.
- // Next 2: Running FG / BG
- // Next 2: Pending FG / BG
- // Next 4: Result running FG / BG, pending FG/BG.
- checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 1, 0, /*res run/pen=*/ 1, 0, 0, 0);
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
+ /* resPen */ List.of());
- checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 0, /*res run/pen=*/ 6, 0, 4, 0);
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 4)));
// When there are BG jobs pending, 2 (min-BG) jobs should run.
- checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
- checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 5), Pair.create(WORK_TYPE_BG, 1)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 5)));
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 1)));
- checkSimple(8, 6, 2, /*run=*/ 0, 0, /*pen=*/ 0, 49, /*res run/pen=*/ 0, 6, 0, 43);
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(),
+ /* pen */ List.of(Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 43)));
- checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 47)));
+
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 4)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 1)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 48)));
+
+ checkSimple(8,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+ /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+ /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+ /* run */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
+
+ // This could happen if we lower the effective config due to higher memory pressure after
+ // we've already started running jobs. We shouldn't stop already running jobs, but also
+ // shouldn't start new ones.
+ checkSimple(5,
+ /* min */ List.of(),
+ /* max */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+ /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+ /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index fba36cb..c28292f 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -18,6 +18,9 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static org.junit.Assert.fail;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.provider.DeviceConfig;
import android.util.Pair;
@@ -32,6 +35,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -58,42 +62,90 @@
}
private void check(@Nullable DeviceConfig.Properties config,
- int defaultTotal, int defaultMaxBg, int defaultMinBg,
- int expectedTotal, int expectedMaxBg, int expectedMinBg) throws Exception {
+ int defaultTotal,
+ @Nullable Pair<Integer, Integer> defaultTopLimits,
+ @Nullable Pair<Integer, Integer> defaultBgLimits,
+ boolean expectedValid, int expectedTotal,
+ @NonNull Pair<Integer, Integer> expectedTopLimits,
+ @NonNull Pair<Integer, Integer> expectedBgLimits) throws Exception {
resetConfig();
if (config != null) {
DeviceConfig.setProperties(config);
}
- final WorkTypeConfig counts = new WorkTypeConfig("test",
- defaultTotal,
- // defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, defaultTotal - defaultMaxBg),
- Pair.create(WORK_TYPE_BG, defaultMinBg)),
- // defaultMax
- List.of(Pair.create(WORK_TYPE_BG, defaultMaxBg)));
+ List<Pair<Integer, Integer>> defaultMin = new ArrayList<>();
+ List<Pair<Integer, Integer>> defaultMax = new ArrayList<>();
+ Integer val;
+ if (defaultTopLimits != null) {
+ if ((val = defaultTopLimits.first) != null) {
+ defaultMin.add(Pair.create(WORK_TYPE_TOP, val));
+ }
+ if ((val = defaultTopLimits.second) != null) {
+ defaultMax.add(Pair.create(WORK_TYPE_TOP, val));
+ }
+ }
+ if (defaultBgLimits != null) {
+ if ((val = defaultBgLimits.first) != null) {
+ defaultMin.add(Pair.create(WORK_TYPE_BG, val));
+ }
+ if ((val = defaultBgLimits.second) != null) {
+ defaultMax.add(Pair.create(WORK_TYPE_BG, val));
+ }
+ }
+
+ final WorkTypeConfig counts;
+ try {
+ counts = new WorkTypeConfig("test",
+ defaultTotal, defaultMin, defaultMax);
+ if (!expectedValid) {
+ fail("Invalid config successfully created");
+ return;
+ }
+ } catch (IllegalArgumentException e) {
+ if (expectedValid) {
+ throw e;
+ } else {
+ // Success
+ return;
+ }
+ }
counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
Assert.assertEquals(expectedTotal, counts.getMaxTotal());
- Assert.assertEquals(expectedMaxBg, counts.getMax(WORK_TYPE_BG));
- Assert.assertEquals(expectedMinBg, counts.getMinReserved(WORK_TYPE_BG));
+ Assert.assertEquals((int) expectedTopLimits.first, counts.getMinReserved(WORK_TYPE_TOP));
+ Assert.assertEquals((int) expectedTopLimits.second, counts.getMax(WORK_TYPE_TOP));
+ Assert.assertEquals((int) expectedBgLimits.first, counts.getMinReserved(WORK_TYPE_BG));
+ Assert.assertEquals((int) expectedBgLimits.second, counts.getMax(WORK_TYPE_BG));
}
@Test
public void test() throws Exception {
// Tests with various combinations.
- check(null, /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
- check(null, /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
- check(null, /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
- check(null, /*default*/ -1, -1, -1, /*expected*/ 1, 1, 0);
- check(null, /*default*/ 5, 5, 5, /*expected*/ 5, 5, 4);
- check(null, /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
- check(null, /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
- check(null, /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
- check(null, /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
- check(null, /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
- check(null, /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
+ check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1),
+ /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1));
+ check(null, /*default*/ 5, Pair.create(5, null), Pair.create(0, 0),
+ /*expected*/ true, 5, Pair.create(5, 5), Pair.create(0, 1));
+ check(null, /*default*/ 0, Pair.create(5, null), Pair.create(0, 0),
+ /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
+ check(null, /*default*/ -1, null, Pair.create(-1, -1),
+ /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
+ check(null, /*default*/ 5, null, Pair.create(5, 5),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
+ check(null, /*default*/ 6, Pair.create(1, null), Pair.create(6, 5),
+ /*expected*/ false, 6, Pair.create(1, 6), Pair.create(5, 5));
+ check(null, /*default*/ 4, null, Pair.create(6, 5),
+ /*expected*/ false, 4, Pair.create(1, 4), Pair.create(3, 4));
+ check(null, /*default*/ 5, Pair.create(4, null), Pair.create(1, 1),
+ /*expected*/ true, 5, Pair.create(4, 5), Pair.create(1, 1));
+ check(null, /*default*/ 15, null, Pair.create(15, 15),
+ /*expected*/ true, 15, Pair.create(1, 15), Pair.create(14, 15));
+ check(null, /*default*/ 16, null, Pair.create(16, 16),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
+ check(null, /*default*/ 20, null, Pair.create(20, 20),
+ /*expected*/ false, 16, Pair.create(1, 16), Pair.create(15, 16));
+ check(null, /*default*/ 20, null, Pair.create(16, 16),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
// Test for overriding with a setting string.
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
@@ -101,15 +153,26 @@
.setInt(KEY_MAX_BG, 4)
.setInt(KEY_MIN_BG, 3)
.build(),
- /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(3, 4));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_TOTAL, 5).build(),
- /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MAX_BG, 4).build(),
- /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 9, Pair.create(1, 9), Pair.create(4, 4));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
.setInt(KEY_MIN_BG, 3).build(),
- /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 9, Pair.create(1, 9), Pair.create(3, 9));
+ check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+ .setInt(KEY_MAX_TOTAL, 20)
+ .setInt(KEY_MAX_BG, 20)
+ .setInt(KEY_MIN_BG, 8)
+ .build(),
+ /*default*/ 9, null, Pair.create(9, 9),
+ /*expected*/ true, 16, Pair.create(1, 16), Pair.create(8, 16));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 161d316..6e57896 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -535,8 +535,11 @@
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
- assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
- TEST_SHORTCUT_ID)).isNotNull();
+ ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID);
+ assertThat(result).isNotNull();
+ assertThat(result.hasBirthdayToday()).isFalse();
+ assertThat(result.getStatuses()).isEmpty();
}
@Test
@@ -550,13 +553,15 @@
shortcut.setCached(ShortcutInfo.FLAG_PINNED);
mDataManager.addOrUpdateConversationInfo(shortcut);
assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
- TEST_SHORTCUT_ID)).isNotNull();
+ TEST_SHORTCUT_ID)).isNotNull();
assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
TEST_SHORTCUT_ID + "1")).isNull();
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
listenerService.onNotificationPosted(mStatusBarNotification);
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+ mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs);
ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
TEST_SHORTCUT_ID);
@@ -568,6 +573,8 @@
result.getParentNotificationChannel().getId());
assertEquals(mStatusBarNotification.getPostTime(), result.getLastEventTimestamp());
assertTrue(result.hasActiveNotifications());
+ assertFalse(result.hasBirthdayToday());
+ assertThat(result.getStatuses()).containsExactly(cs);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 28d313b..e71c2f5 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -294,6 +294,8 @@
private InputDevice createInputDevice(int id, boolean hasVibrator) {
return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
- null, hasVibrator, false, false, false /* hasSensor */);
+ null, hasVibrator, false, false, false /* hasSensor */, false /* hasBattery */);
+
+
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 5c67db7..91fd7a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -16,8 +16,8 @@
package com.android.server.wm;
-import static android.content.ActivityInfoProto.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
@@ -50,6 +50,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
@@ -408,6 +409,32 @@
}
@Test
+ public void testRestrictAppBoundsToOverrideBounds() {
+ final RootDisplayArea root =
+ new DisplayAreaPolicyBuilderTest.SurfacelessDisplayAreaRoot(mWm);
+ final DisplayArea<DisplayArea> da = new DisplayArea<>(mWm, ANY, "Test_DA");
+ root.addChild(da, POSITION_TOP);
+ final Rect displayBounds = new Rect(0, 0, 1800, 2800);
+ final Rect displayAppBounds = new Rect(0, 100, 1800, 2800);
+ final Rect daBounds = new Rect(0, 1400, 1800, 2800);
+ root.setBounds(displayBounds);
+
+ // DA inherit parent app bounds.
+ final Configuration displayConfig = new Configuration();
+ displayConfig.windowConfiguration.setAppBounds(displayAppBounds);
+ root.onRequestedOverrideConfigurationChanged(displayConfig);
+
+ assertEquals(displayAppBounds, da.getConfiguration().windowConfiguration.getAppBounds());
+
+ // Restrict DA appBounds to override Bounds
+ da.setBounds(daBounds);
+
+ final Rect expectedDaAppBounds = new Rect(daBounds);
+ expectedDaAppBounds.intersect(displayAppBounds);
+ assertEquals(expectedDaAppBounds, da.getConfiguration().windowConfiguration.getAppBounds());
+ }
+
+ @Test
public void testGetOrientation() {
final DisplayArea.Tokens area = new DisplayArea.Tokens(mWm, ABOVE_TASKS, "test");
final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 82ffa765..37fb0e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -123,6 +123,15 @@
updateDisplayFrames();
}
+ void addWindowWithRawInsetsState(WindowState win) {
+ addWindow(win);
+ // Without mPerformLayout in display content, the window cannot see any insets. Override the
+ // insets state with the global one.
+ final InsetsState insetsState =
+ win.getDisplayContent().getInsetsStateController().getRawInsetsState();
+ win.mAboveInsetsState = insetsState;
+ }
+
public void setRotation(int rotation, boolean includingWindows) {
mRotation = rotation;
updateDisplayFrames();
@@ -272,7 +281,7 @@
@Test
public void layoutWindowLw_fitStatusBars() {
mWindow.mAttrs.setFitInsetsTypes(Type.statusBars());
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -283,7 +292,7 @@
@Test
public void layoutWindowLw_fitNavigationBars() {
mWindow.mAttrs.setFitInsetsTypes(Type.navigationBars());
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -294,7 +303,7 @@
@Test
public void layoutWindowLw_fitAllSides() {
mWindow.mAttrs.setFitInsetsSides(Side.all());
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -305,7 +314,7 @@
@Test
public void layoutWindowLw_fitTopOnly() {
mWindow.mAttrs.setFitInsetsSides(Side.TOP);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -315,11 +324,12 @@
@Test
public void layoutWindowLw_fitInsetsIgnoringVisibility() {
- final InsetsState state = mWindow.getInsetsState();
+ final InsetsState state =
+ mDisplayContent.getInsetsStateController().getRawInsetsState();
state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
mWindow.mAttrs.setFitInsetsIgnoringVisibility(true);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -329,11 +339,12 @@
@Test
public void layoutWindowLw_fitInsetsNotIgnoringVisibility() {
- final InsetsState state = mWindow.getInsetsState();
+ final InsetsState state =
+ mDisplayContent.getInsetsStateController().getRawInsetsState();
state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
mWindow.mAttrs.setFitInsetsIgnoringVisibility(false);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -349,8 +360,7 @@
state.getSource(InsetsState.ITYPE_IME).setFrame(
0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
- mWindow.mBehindIme = true;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -364,7 +374,7 @@
mWindow.mAttrs.setFitInsetsTypes(Type.displayCutout());
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -379,7 +389,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -395,7 +405,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -411,7 +421,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -427,7 +437,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -442,7 +452,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -457,11 +467,12 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- mWindow.getInsetsState().getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
+ mDisplayContent.getInsetsStateController().getRawInsetsState()
+ .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
final InsetsState requestedState = new InsetsState();
requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
mWindow.updateRequestedVisibility(requestedState);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -476,12 +487,13 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- mWindow.getInsetsState().getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
+ mDisplayContent.getInsetsStateController().getRawInsetsState()
+ .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
final InsetsState requestedState = new InsetsState();
requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
mWindow.updateRequestedVisibility(requestedState);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -497,7 +509,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -513,7 +525,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -529,7 +541,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -545,7 +557,7 @@
mWindow.mAttrs.type = TYPE_APPLICATION_OVERLAY;
mWindow.mAttrs.width = DISPLAY_WIDTH;
mWindow.mAttrs.height = DISPLAY_HEIGHT;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -562,7 +574,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -576,7 +588,7 @@
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -592,7 +604,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -608,7 +620,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -624,7 +636,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -638,7 +650,7 @@
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
- addWindow(mWindow);
+ addWindowWithRawInsetsState(mWindow);
final int forwardedInsetBottom = 50;
mDisplayPolicy.setForwardedInsets(Insets.of(0, 0, 0, forwardedInsetBottom));
@@ -776,9 +788,13 @@
public void testFixedRotationInsetsSourceFrame() {
doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent)
.rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
- final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+ mWindow.mAboveInsetsState.addSource(mDisplayContent.getInsetsStateController()
+ .getRawInsetsState().peekSource(ITYPE_STATUS_BAR));
+ final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
+ .getSource(ITYPE_STATUS_BAR).getFrame();
mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
- final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+ final Rect rotatedFrame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
+ .getSource(ITYPE_STATUS_BAR).getFrame();
assertEquals(DISPLAY_WIDTH, frame.width());
assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 77537a9..499507e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -300,6 +300,7 @@
displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+ mImeWindow.mAboveInsetsState = state;
mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
state, displayInfo, null /* displayCutout */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index e6f24da..f91c9d0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.content.ActivityInfoProto.SCREEN_ORIENTATION_LANDSCAPE;
+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_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index e0fd379..bf3ed69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -45,6 +45,7 @@
import android.app.StatusBarManager;
import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -272,7 +273,6 @@
final WindowState navBar = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar");
navBar.setHasSurface(true);
navBar.getControllableInsetProvider().setServerVisible(true);
-
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
@@ -337,11 +337,14 @@
@UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
- addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
- .getControllableInsetProvider().getSource().setVisible(false);
- addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
- .getControllableInsetProvider().getSource().setVisible(false);
-
+ final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
+ .getControllableInsetProvider().getSource();
+ final InsetsSource navBarSource = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
+ .getControllableInsetProvider().getSource();
+ statusBarSource.setVisible(false);
+ navBarSource.setVisible(false);
+ mAppWindow.mAboveInsetsState.addSource(navBarSource);
+ mAppWindow.mAboveInsetsState.addSource(statusBarSource);
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(mAppWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 2766438..2107ab1e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -59,25 +59,6 @@
public class InsetsStateControllerTest extends WindowTestsBase {
@Test
- public void testStripForDispatch_notOwn() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR));
- assertNotNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR));
- }
-
- @Test
- public void testStripForDispatch_own() {
- final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
- .setWindow(statusBar, null, null);
- statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR));
- final InsetsState state = getController().getInsetsForWindow(statusBar);
- assertNull(state.peekSource(ITYPE_STATUS_BAR));
- }
-
- @Test
public void testStripForDispatch_navBar() {
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
@@ -142,14 +123,15 @@
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
- app1.mBehindIme = true;
-
final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
- app2.mBehindIme = false;
+
+ app1.mAboveInsetsState.addSource(getController().getRawInsetsState().getSource(ITYPE_IME));
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME).isVisible());
- assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME)
+ .isVisible());
+ assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME)
+ .isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -158,7 +140,8 @@
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- app.mBehindIme = true;
+ app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
+ app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
@@ -170,10 +153,10 @@
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- app.mBehindIme = false;
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME)
+ .isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -210,7 +193,8 @@
// app won't get visible IME insets while above IME even when IME is visible.
assertTrue(getController().getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
- assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME)
+ .isVisible());
// Reset invocation counter.
clearInvocations(app);
@@ -219,6 +203,8 @@
app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
mDisplayContent.computeImeTarget(true);
mDisplayContent.applySurfaceChangesTransaction();
+ app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
+ app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
// Make sure app got notified.
verify(app, atLeast(1)).notifyInsetsChanged();
@@ -234,6 +220,8 @@
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
+ app.mAboveInsetsState.set(getController().getRawInsetsState());
+ child.mAboveInsetsState.set(getController().getRawInsetsState());
child.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
mDisplayContent.computeImeTarget(true);
@@ -242,7 +230,8 @@
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
- assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME)
+ .isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@@ -252,6 +241,7 @@
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
+ app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME));
child.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
child.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -261,7 +251,8 @@
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
- assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME).isVisible());
+ assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME)
+ .isVisible());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6dfbbc7..bbd89b8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -26,6 +26,7 @@
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -645,12 +646,11 @@
assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
mActivity.getLetterboxInsets());
- final BarController statusBarController =
- mActivity.mDisplayContent.getDisplayPolicy().getStatusBarController();
+ final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy();
// The activity doesn't fill the display, so the letterbox of the rotated activity is
// overlapped with the rotated content frame of status bar. Hence the status bar shouldn't
// be transparent.
- assertFalse(statusBarController.isFullyTransparentAllowed(w));
+ assertFalse(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR));
// Make the activity fill the display.
prepareUnresizable(mActivity, 10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
@@ -660,7 +660,7 @@
// The letterbox should only cover the notch area, so status bar can be transparent.
assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets());
- assertTrue(statusBarController.isFullyTransparentAllowed(w));
+ assertTrue(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR));
}
@Test
@@ -1020,9 +1020,9 @@
displayPolicy.onConfigurationChanged();
final TestWindowToken token = createTestWindowToken(
- WindowManager.LayoutParams.TYPE_STATUS_BAR, displayContent);
+ TYPE_STATUS_BAR, displayContent);
final WindowManager.LayoutParams attrs =
- new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_STATUS_BAR);
+ new WindowManager.LayoutParams(TYPE_STATUS_BAR);
attrs.gravity = android.view.Gravity.TOP;
attrs.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index 1607f01..a1f89ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -275,7 +275,7 @@
imeSource.setFrame(imeFrame);
imeSource.setVisible(true);
w.updateRequestedVisibility(state);
- w.mBehindIme = true;
+ w.mAboveInsetsState.addSource(imeSource);
// With no insets or system decor all the frames incoming from PhoneWindowManager
// are identical.
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 3815326..0cb1255 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -79,7 +79,6 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -111,6 +110,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
/**
* A service that collects, aggregates, and persists application usage data.
@@ -162,7 +162,7 @@
ShortcutServiceInternal mShortcutServiceInternal;
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
- private final SparseBooleanArray mUserUnlockedStates = new SparseBooleanArray();
+ private final CopyOnWriteArraySet<Integer> mUserUnlockedStates = new CopyOnWriteArraySet<>();
private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
int mUsageSource;
@@ -333,7 +333,7 @@
synchronized (mLock) {
// User was started but never unlocked so no need to report a user stopped event
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
persistPendingEventsLocked(userId);
return;
}
@@ -346,7 +346,7 @@
if (userService != null) {
userService.userStopped();
}
- mUserUnlockedStates.put(userId, false);
+ mUserUnlockedStates.remove(userId);
mUserState.put(userId, null); // release the service (mainly for GC)
}
}
@@ -360,6 +360,11 @@
UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
}
synchronized (mLock) {
+ // This should be safe to add this early. Other than reportEventOrAddToQueue, every
+ // other user grabs the lock before accessing
+ // mUserUnlockedStates. reportEventOrAddToQueue does not depend on anything other than
+ // mUserUnlockedStates, and the lock will protect the handler.
+ mUserUnlockedStates.add(userId);
// Create a user unlocked event to report
final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
unlockEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
@@ -377,7 +382,6 @@
initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(),
installedPackages);
- mUserUnlockedStates.put(userId, true);
final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId);
if (userService == null) {
Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId);
@@ -780,12 +784,11 @@
}
private void reportEventOrAddToQueue(int userId, Event event) {
+ if (mUserUnlockedStates.contains(userId)) {
+ mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+ return;
+ }
synchronized (mLock) {
- if (mUserUnlockedStates.get(userId)) {
- mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
- return;
- }
-
LinkedList<Event> events = mReportedEvents.get(userId);
if (events == null) {
events = new LinkedList<>();
@@ -823,7 +826,7 @@
synchronized (mLock) {
// This should never be called directly when the user is locked
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
Slog.wtf(TAG, "Failed to report event for locked user " + userId
+ " (" + event.mPackage + "/" + event.mClass
+ " eventType:" + event.mEventType
@@ -1006,7 +1009,7 @@
final int tokenRemoved;
synchronized (mLock) {
final long timeRemoved = System.currentTimeMillis();
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
// If user is not unlocked and a package is removed for them, we will handle it
// when the user service is initialized and package manager is queried.
return;
@@ -1030,7 +1033,7 @@
*/
private boolean pruneUninstalledPackagesData(int userId) {
synchronized (mLock) {
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
return false; // user is no longer unlocked
}
@@ -1050,7 +1053,7 @@
// fetch the installed packages outside the lock so it doesn't block package manager.
final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
synchronized (mLock) {
- if (!mUserUnlockedStates.get(UserHandle.USER_SYSTEM)) {
+ if (!mUserUnlockedStates.contains(UserHandle.USER_SYSTEM)) {
return false; // user is no longer unlocked
}
@@ -1069,7 +1072,7 @@
List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
boolean obfuscateInstantApps) {
synchronized (mLock) {
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
Slog.w(TAG, "Failed to query usage stats for locked user " + userId);
return null;
}
@@ -1103,7 +1106,7 @@
List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime,
long endTime) {
synchronized (mLock) {
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
Slog.w(TAG, "Failed to query configuration stats for locked user " + userId);
return null;
}
@@ -1122,7 +1125,7 @@
List<EventStats> queryEventStats(int userId, int bucketType, long beginTime,
long endTime) {
synchronized (mLock) {
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
Slog.w(TAG, "Failed to query event stats for locked user " + userId);
return null;
}
@@ -1140,7 +1143,7 @@
*/
UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) {
synchronized (mLock) {
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
Slog.w(TAG, "Failed to query events for locked user " + userId);
return null;
}
@@ -1159,7 +1162,7 @@
UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
String packageName, boolean includeTaskRoot) {
synchronized (mLock) {
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
Slog.w(TAG, "Failed to query package events for locked user " + userId);
return null;
}
@@ -1203,7 +1206,7 @@
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
final int userId = mUserState.keyAt(i);
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
persistPendingEventsLocked(userId);
continue;
}
@@ -1261,7 +1264,7 @@
final int numUsers = mUserState.size();
for (int user = 0; user < numUsers; user++) {
final int userId = mUserState.keyAt(user);
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
continue;
}
ipw.println("user=" + userId);
@@ -1288,7 +1291,7 @@
final int numUsers = mUserState.size();
for (int user = 0; user < numUsers; user++) {
final int userId = mUserState.keyAt(user);
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
continue;
}
ipw.println("user=" + userId);
@@ -1344,7 +1347,7 @@
idpw.printPair("user", userId);
idpw.println();
idpw.increaseIndent();
- if (mUserUnlockedStates.get(userId)) {
+ if (mUserUnlockedStates.contains(userId)) {
if (checkin) {
mUserState.valueAt(i).checkin(idpw);
} else {
@@ -1382,7 +1385,7 @@
ipw.println("the specified user does not exist.");
return UserHandle.USER_NULL;
}
- if (!mUserUnlockedStates.get(userId)) {
+ if (!mUserUnlockedStates.contains(userId)) {
ipw.println("the specified user is currently in a locked state.");
return UserHandle.USER_NULL;
}
@@ -2250,12 +2253,11 @@
@Override
public byte[] getBackupPayload(int user, String key) {
+ if (!mUserUnlockedStates.contains(user)) {
+ Slog.w(TAG, "Failed to get backup payload for locked user " + user);
+ return null;
+ }
synchronized (mLock) {
- if (!mUserUnlockedStates.get(user)) {
- Slog.w(TAG, "Failed to get backup payload for locked user " + user);
- return null;
- }
-
// Check to ensure that only user 0's data is b/r for now
// Note: if backup and restore is enabled for users other than the system user, the
// #onUserUnlocked logic, specifically when the update mappings job is scheduled via
@@ -2275,7 +2277,7 @@
@Override
public void applyRestoredPayload(int user, String key, byte[] payload) {
synchronized (mLock) {
- if (!mUserUnlockedStates.get(user)) {
+ if (!mUserUnlockedStates.contains(user)) {
Slog.w(TAG, "Failed to apply restored payload for locked user " + user);
return;
}
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 1faae42..a9dae89 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -138,6 +138,8 @@
* Indicates if the session is for a conference call or not. If not defined, should be
* considered {@code false}.
* Boolean extra properties - {@code true} / {@code false}.
+ *
+ * This extra is set on an instance of {@link ImsCallProfile} via {@link #setCallExtraBoolean}.
* @hide
*/
@SystemApi
@@ -174,6 +176,8 @@
* Indicates if the session can be extended to a conference call. If not defined, should be
* considered {@code false}.
* Boolean extra properties - {@code true} / {@code false}.
+ *
+ * This extra is set on an instance of {@link ImsCallProfile} via {@link #setCallExtraBoolean}.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
index eddbb10..8762b6a 100644
--- a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
+++ b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
@@ -280,6 +280,12 @@
"sip_config_path_header_string";
/**
+ * The SIP User-Agent header value used by the IMS stack during IMS registration.
+ */
+ public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING =
+ "sip_config_sip_user_agent_header_string";
+
+ /**
* SIP User part string in contact header
*/
public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING =
@@ -292,12 +298,20 @@
"sip_config_p_access_network_info_header_string";
/**
- * SIP P-last-access-network-info header string
+ * The SIP P-last-access-network-info header value, populated for networks that require this
+ * information to be provided in outgoing SIP messages.
*/
public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING =
"sip_config_p_last_access_network_info_header_string";
/**
+ * The Cellular-Network-Info header value (See 3GPP 24.229, section 7.2.15), populated for
+ * networks that require this information to be provided as part of outgoing SIP messages.
+ */
+ public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING =
+ "sip_config_cellular_network_info_header_string";
+
+ /**
* SIP P-associated-uri header string
*/
public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING =
@@ -320,9 +334,11 @@
KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING,
KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING,
KEY_SIP_CONFIG_PATH_HEADER_STRING,
+ KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING,
KEY_SIP_CONFIG_URI_USER_PART_STRING,
KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING,
KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING,
+ KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING,
KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 22aa18f..d17415a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2360,6 +2360,16 @@
*/
String getContactFromEab(String contact);
+ /*
+ * Check whether the device supports RCS User Capability Exchange or not.
+ */
+ boolean getDeviceUceEnabled();
+
+ /*
+ * Set the device supports RCS User Capability Exchange.
+ */
+ void setDeviceUceEnabled(boolean isEnabled);
+
/**
* Set a SignalStrengthUpdateRequest to receive notification when Signal Strength breach the
* specified thresholds.
diff --git a/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING
deleted file mode 100644
index db251b9..0000000
--- a/tests/FlickerTests/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "postsubmit": [
- // Run tests on real device
- {
- "name": "FlickerTests",
- "keywords": ["primary-device"]
- },
- // Also run the tests in the cloud
- {
- "name": "FlickerTests"
- }
- ]
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index ba12fbe..6b6d21b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -21,9 +21,12 @@
import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
import com.android.server.wm.flicker.dsl.WmAssertionBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_LAYER_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_WINDOW_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_LAYER_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME
-const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
-const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
+const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
const val WALLPAPER_TITLE = "Wallpaper"
@@ -33,7 +36,7 @@
enabled: Boolean = bugId == 0
) {
all("statusBarWindowIsAlwaysVisible", bugId, enabled) {
- this.showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE)
+ this.showsAboveAppWindow(STATUS_BAR_WINDOW_NAME)
}
}
@@ -43,7 +46,7 @@
enabled: Boolean = bugId == 0
) {
all("navBarWindowIsAlwaysVisible", bugId, enabled) {
- this.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE)
+ this.showsAboveAppWindow(NAV_BAR_WINDOW_NAME)
}
}
@@ -113,6 +116,18 @@
}
}
+fun WmAssertionBuilder.appWindowBecomesInVisible(
+ appName: String,
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("appWindowBecomesInVisible", bugId, enabled) {
+ this.showsAppWindow(appName)
+ .then()
+ .hidesAppWindow(appName)
+ }
+}
+
@JvmOverloads
fun LayersAssertionBuilder.noUncoveredRegions(
beginRotation: Int,
@@ -151,15 +166,15 @@
) {
if (rotatesScreen) {
all("navBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+ this.showsLayer(NAV_BAR_LAYER_NAME)
.then()
- .hidesLayer(NAVIGATION_BAR_WINDOW_TITLE)
+ .hidesLayer(NAV_BAR_LAYER_NAME)
.then()
- .showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+ .showsLayer(NAV_BAR_LAYER_NAME)
}
} else {
all("navBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+ this.showsLayer(NAV_BAR_LAYER_NAME)
}
}
}
@@ -172,15 +187,15 @@
) {
if (rotatesScreen) {
all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(STATUS_BAR_WINDOW_TITLE)
+ this.showsLayer(STATUS_BAR_LAYER_NAME)
.then()
- hidesLayer(STATUS_BAR_WINDOW_TITLE)
+ hidesLayer(STATUS_BAR_LAYER_NAME)
.then()
- .showsLayer(STATUS_BAR_WINDOW_TITLE)
+ .showsLayer(STATUS_BAR_LAYER_NAME)
}
} else {
all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
- this.showsLayer(STATUS_BAR_WINDOW_TITLE)
+ this.showsLayer(STATUS_BAR_LAYER_NAME)
}
}
}
@@ -196,15 +211,15 @@
val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
start("navBarLayerRotatesAndScales_StartingPos", bugId, enabled) {
- this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos)
}
end("navBarLayerRotatesAndScales_EndingPost", bugId, enabled) {
- this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos)
+ this.hasVisibleRegion(NAV_BAR_LAYER_NAME, endingPos)
}
if (startingPos == endingPos) {
all("navBarLayerRotatesAndScales", enabled = false, bugId = 167747321) {
- this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos)
}
}
}
@@ -220,10 +235,10 @@
val endingPos = WindowUtils.getStatusBarPosition(endRotation)
start("statusBarLayerRotatesScales_StartingPos", bugId, enabled) {
- this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(STATUS_BAR_LAYER_NAME, startingPos)
}
end("statusBarLayerRotatesScales_EndingPos", bugId, enabled) {
- this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos)
+ this.hasVisibleRegion(STATUS_BAR_LAYER_NAME, endingPos)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index b569eda..1a47449 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -41,6 +41,9 @@
wmHelper.waitForRotation(rotation)
wmHelper.waitForNavBarStatusBarVisible()
wmHelper.waitForAppTransitionIdle()
+
+ // Ensure WindowManagerService wait until all animations have completed
+ instrumentation.getUiAutomation().syncInputTransactions()
} catch (e: RemoteException) {
throw RuntimeException(e)
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 104758d..5381009 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -98,6 +98,8 @@
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
private Context mSpyContext;
+ // Keep track of all created watchdogs to apply device config changes
+ private List<PackageWatchdog> mAllocatedWatchdogs;
@Mock
private ConnectivityModuleConnector mConnectivityModuleConnector;
@Mock
@@ -112,7 +114,8 @@
MockitoAnnotations.initMocks(this);
new File(InstrumentationRegistry.getContext().getFilesDir(),
"package-watchdog.xml").delete();
- adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG);
+ adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.WRITE_DEVICE_CONFIG);
mTestLooper = new TestLooper();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
@@ -157,12 +160,23 @@
return storedValue == null ? defaultValue : Long.parseLong(storedValue);
}
).when(() -> SystemProperties.getLong(anyString(), anyLong()));
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
+ Boolean.toString(true), false);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT), false);
+
+ mAllocatedWatchdogs = new ArrayList<>();
}
@After
public void tearDown() throws Exception {
dropShellPermissions();
mSession.finishMocking();
+ mAllocatedWatchdogs.clear();
}
@Test
@@ -611,10 +625,6 @@
*/
@Test
public void testExplicitHealthCheckStateChanges() throws Exception {
- adoptShellPermissions(
- Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
-
TestController controller = new TestController();
PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
@@ -807,9 +817,6 @@
/** Test default values are used when device property is invalid. */
@Test
public void testInvalidConfig_watchdogTriggerFailureCount() {
- adoptShellPermissions(
- Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
Integer.toString(-1), /*makeDefault*/false);
@@ -835,9 +842,6 @@
/** Test default values are used when device property is invalid. */
@Test
public void testInvalidConfig_watchdogTriggerDurationMillis() {
- adoptShellPermissions(
- Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
Integer.toString(2), /*makeDefault*/false);
@@ -850,7 +854,6 @@
watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- mTestLooper.dispatchAll();
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -862,7 +865,6 @@
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- mTestLooper.dispatchAll();
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -917,9 +919,6 @@
/** Test we are notified when enough failures are triggered within any window. */
@Test
public void testFailureTriggerWindow() {
- adoptShellPermissions(
- Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
Integer.toString(3), /*makeDefault*/false);
@@ -933,11 +932,9 @@
// Raise 2 failures at t=0 and t=900 respectively
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- mTestLooper.dispatchAll();
moveTimeForwardAndDispatch(900);
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- mTestLooper.dispatchAll();
// Raise 2 failures at t=1100
moveTimeForwardAndDispatch(200);
@@ -1303,15 +1300,15 @@
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
Boolean.toString(enabled), /*makeDefault*/false);
- //give time for DeviceConfig to broadcast the property value change
- try {
- Thread.sleep(SHORT_DURATION);
- } catch (InterruptedException e) {
- fail("Thread.sleep unexpectedly failed!");
+ // Call updateConfigs() so device config changes take effect immediately
+ for (PackageWatchdog watchdog : mAllocatedWatchdogs) {
+ watchdog.updateConfigs();
}
}
private void moveTimeForwardAndDispatch(long milliSeconds) {
+ // Exhaust all due runnables now which shouldn't be executed after time-leap
+ mTestLooper.dispatchAll();
mTestClock.moveTimeForward(milliSeconds);
mTestLooper.moveTimeForward(milliSeconds);
mTestLooper.dispatchAll();
@@ -1354,6 +1351,7 @@
verify(mConnectivityModuleConnector).registerHealthListener(
mConnectivityModuleCallbackCaptor.capture());
}
+ mAllocatedWatchdogs.add(watchdog);
return watchdog;
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 0db2b2a..7b2a07f 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1225,4 +1225,44 @@
InstallUtils.dropShellPermissionIdentity();
}
}
+
+ /**
+ * Tests an app can be rolled back to the previous signing key.
+ *
+ * <p>The rollback capability in the signing lineage allows an app to be updated to an APK
+ * signed with a previous signing key in the lineage; however this often defeats the purpose
+ * of key rotation as a compromised key could then be used to roll an app back to the previous
+ * key. To avoid requiring the rollback capability to support app rollbacks the PackageManager
+ * allows an app to be rolled back to the previous signing key if the rollback install reason
+ * is set.
+ */
+ @Test
+ public void testRollbackAfterKeyRotation() throws Exception {
+ try {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.TEST_MANAGE_ROLLBACKS,
+ Manifest.permission.MANAGE_ROLLBACKS);
+
+ // Uninstall TestApp.A
+ Uninstall.packages(TestApp.A);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+
+ // Install v1 of the app with the original signing key (without rollbacks enabled).
+ Install.single(TestApp.AOriginal1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+ // Upgrade from v1 to v2 with the rotated signing key, with rollbacks enabled.
+ Install.single(TestApp.ARotated2).setEnableRollback().commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ // Roll back the app.
+ RollbackInfo available = waitForAvailableRollback(TestApp.A);
+ RollbackUtils.rollback(available.getRollbackId());
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ } finally {
+ InstallUtils.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index ef973ac..861d221 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -182,6 +182,24 @@
@SmallTest
@Test
+ public void setFunctionsNcmAndRndis() {
+ final long rndisPlusNcm = UsbManager.FUNCTION_RNDIS | UsbManager.FUNCTION_NCM;
+
+ mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+ UsbManager.FUNCTION_NCM));
+ assertEquals(UsbManager.FUNCTION_NCM, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+
+ mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+ rndisPlusNcm));
+ assertEquals(rndisPlusNcm, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+
+ mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+ UsbManager.FUNCTION_NCM));
+ assertEquals(UsbManager.FUNCTION_NCM, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+ }
+
+ @SmallTest
+ @Test
public void enableAdb() {
sendBootCompleteMessages(mUsbHandler);
Message msg = mUsbHandler.obtainMessage(MSG_ENABLE_ADB);
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index f6a2846..e630da0 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -53,6 +53,7 @@
jarjar_rules: "jarjar-rules.txt",
static_libs: [
"androidx.test.rules",
+ "bouncycastle-repackaged-unbundled",
"FrameworksNetCommonTests",
"frameworks-base-testutils",
"frameworks-net-integration-testutils",
diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java
index 076e41d..1abd39a 100644
--- a/tests/net/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java
@@ -30,7 +30,7 @@
import com.android.internal.net.VpnProfile;
import com.android.net.module.util.ProxyUtils;
-import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 68e5ce0..bf73134 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1283,32 +1283,10 @@
}
private void updateUidNetworkingBlocked() {
- // Changes the return value of the mock NetworkPolicyManager's isUidNetworkingBlocked method
- // based on the current UID rules and restrict background setting. Note that the test never
- // pretends to be a foreground app, so always declare no connectivity if background
- // networking is not allowed.
- switch (mUidRules) {
- case RULE_REJECT_ALL:
- when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), anyBoolean()))
- .thenReturn(true);
- break;
-
- case RULE_REJECT_METERED:
- when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), eq(true)))
- .thenReturn(true);
- when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), eq(false)))
- .thenReturn(mRestrictBackground);
- break;
-
- case RULE_ALLOW_METERED:
- case RULE_NONE:
- when(mNetworkPolicyManager.isUidNetworkingBlocked(anyInt(), anyBoolean()))
- .thenReturn(mRestrictBackground);
- break;
-
- default:
- fail("Unknown policy rule " + mUidRules);
- }
+ doAnswer(i -> NetworkPolicyManagerInternal.isUidNetworkingBlocked(
+ i.getArgument(0) /* uid */, mUidRules, i.getArgument(1) /* metered */,
+ mRestrictBackground)
+ ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
}
private void setUidRulesChanged(int uidRules) throws RemoteException {
@@ -6917,7 +6895,7 @@
cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
mCellNetworkAgent);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
- assertEquals(null, mCm.getActiveNetwork());
+ assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -6930,17 +6908,21 @@
setUidRulesChanged(RULE_NONE);
cellNetworkCallback.assertNoCallback();
- // Restrict the network based on BackgroundRestricted.
+ // Restrict background data. Networking is not blocked because the network is unmetered.
setRestrictBackgroundChanged(true);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
- assertEquals(null, mCm.getActiveNetwork());
+ assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
-
setRestrictBackgroundChanged(true);
cellNetworkCallback.assertNoCallback();
- setRestrictBackgroundChanged(false);
+
+ setUidRulesChanged(RULE_ALLOW_METERED);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+ assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+
+ setRestrictBackgroundChanged(false);
cellNetworkCallback.assertNoCallback();
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index e26bf19..e7d334e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -30,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -39,16 +39,20 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilities.Transport;
+import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnConfigTest;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.wifi.WifiInfo;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
@@ -253,7 +257,14 @@
verify(mConfigReadWriteHelper).readFromDisk();
}
- private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) {
+ private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+ Set<ParcelUuid> activeSubscriptionGroups) {
+ return triggerSubscriptionTrackerCbAndGetSnapshot(
+ activeSubscriptionGroups, Collections.emptyMap());
+ }
+
+ private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+ Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) {
final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
@@ -267,8 +278,14 @@
argThat(val -> activeSubscriptionGroups.contains(val)),
eq(TEST_PACKAGE_NAME));
+ doAnswer(invocation -> {
+ return subIdToGroupMap.get(invocation.getArgument(0));
+ }).when(snapshot).getGroupForSubId(anyInt());
+
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
cb.onNewSnapshot(snapshot);
+
+ return snapshot;
}
private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() {
@@ -287,7 +304,7 @@
@Test
public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
- triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1));
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG));
}
@@ -296,7 +313,7 @@
final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
- triggerSubscriptionTrackerCallback(Collections.emptySet());
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
// Verify teardown after delay
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
@@ -311,13 +328,13 @@
final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
// Simulate SIM unloaded
- triggerSubscriptionTrackerCallback(Collections.emptySet());
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
// Simulate new SIM loaded right during teardown delay.
mTestLooper.moveTimeForward(
VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
mTestLooper.dispatchAll();
- triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2));
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
// Verify that even after the full timeout duration, the VCN instance is not torn down
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
@@ -331,7 +348,7 @@
final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
// Simulate SIM unloaded
- triggerSubscriptionTrackerCallback(Collections.emptySet());
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
// Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
// vcnInstance.
@@ -496,14 +513,73 @@
mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
}
+ private void setUpVcnSubscription(int subId, ParcelUuid subGroup) {
+ mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+
+ triggerSubscriptionTrackerCbAndGetSnapshot(
+ Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
+ }
+
+ private void verifyMergedNetworkCapabilities(
+ NetworkCapabilities mergedCapabilities, @Transport int transportType) {
+ assertTrue(mergedCapabilities.hasTransport(transportType));
+ assertFalse(
+ mergedCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+ }
+
@Test
- public void testGetUnderlyingNetworkPolicy() throws Exception {
+ public void testGetUnderlyingNetworkPolicyTransportCell() throws Exception {
+ setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
+
+ NetworkCapabilities nc =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+ .build();
+
VcnUnderlyingNetworkPolicy policy =
- mVcnMgmtSvc.getUnderlyingNetworkPolicy(
- new NetworkCapabilities(), new LinkProperties());
+ mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
assertFalse(policy.isTeardownRequested());
- assertNotNull(policy.getMergedNetworkCapabilities());
+ verifyMergedNetworkCapabilities(
+ policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR);
+ }
+
+ @Test
+ public void testGetUnderlyingNetworkPolicyTransportWifi() throws Exception {
+ setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
+
+ WifiInfo wifiInfo = mock(WifiInfo.class);
+ when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
+ when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
+ NetworkCapabilities nc =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setTransportInfo(wifiInfo)
+ .build();
+
+ VcnUnderlyingNetworkPolicy policy =
+ mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+
+ assertFalse(policy.isTeardownRequested());
+ verifyMergedNetworkCapabilities(
+ policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI);
+ }
+
+ @Test
+ public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception {
+ NetworkCapabilities nc =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+ .build();
+
+ VcnUnderlyingNetworkPolicy policy =
+ mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+
+ assertFalse(policy.isTeardownRequested());
+ assertEquals(nc, policy.getMergedNetworkCapabilities());
}
@Test(expected = SecurityException.class)
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
new file mode 100644
index 0000000..48e068d
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2021 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.vcn;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.ParcelUuid;
+import android.os.test.TestLooper;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+
+import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback;
+import com.android.server.vcn.UnderlyingNetworkTracker.RouteSelectionCallback;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+public class UnderlyingNetworkTrackerTest {
+ private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+ private static final int INITIAL_SUB_ID_1 = 1;
+ private static final int INITIAL_SUB_ID_2 = 2;
+
+ private static final NetworkCapabilities INITIAL_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .build();
+ private static final NetworkCapabilities SUSPENDED_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder(INITIAL_NETWORK_CAPABILITIES)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ .build();
+ private static final NetworkCapabilities UPDATED_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+
+ private static final LinkProperties INITIAL_LINK_PROPERTIES =
+ getLinkPropertiesWithName("initial_iface");
+ private static final LinkProperties UPDATED_LINK_PROPERTIES =
+ getLinkPropertiesWithName("updated_iface");
+
+ @Mock private Context mContext;
+ @Mock private VcnNetworkProvider mVcnNetworkProvider;
+ @Mock private ConnectivityManager mConnectivityManager;
+ @Mock private SubscriptionManager mSubscriptionManager;
+ @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb;
+ @Mock private Network mNetwork;
+
+ @Captor private ArgumentCaptor<RouteSelectionCallback> mRouteSelectionCallbackCaptor;
+
+ private TestLooper mTestLooper;
+ private VcnContext mVcnContext;
+ private UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestLooper = new TestLooper();
+ mVcnContext = spy(new VcnContext(mContext, mTestLooper.getLooper(), mVcnNetworkProvider));
+ doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+ setupSystemService(
+ mContext,
+ mConnectivityManager,
+ Context.CONNECTIVITY_SERVICE,
+ ConnectivityManager.class);
+ setupSystemService(
+ mContext,
+ mSubscriptionManager,
+ Context.TELEPHONY_SUBSCRIPTION_SERVICE,
+ SubscriptionManager.class);
+
+ List<SubscriptionInfo> initialSubInfos =
+ Arrays.asList(
+ getSubscriptionInfoForSubId(INITIAL_SUB_ID_1),
+ getSubscriptionInfoForSubId(INITIAL_SUB_ID_2));
+ when(mSubscriptionManager.getSubscriptionsInGroup(eq(SUB_GROUP)))
+ .thenReturn(initialSubInfos);
+
+ mUnderlyingNetworkTracker =
+ new UnderlyingNetworkTracker(
+ mVcnContext,
+ SUB_GROUP,
+ Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET),
+ mNetworkTrackerCb);
+ }
+
+ private static LinkProperties getLinkPropertiesWithName(String iface) {
+ LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(iface);
+ return linkProperties;
+ }
+
+ private SubscriptionInfo getSubscriptionInfoForSubId(int subId) {
+ SubscriptionInfo subInfo = mock(SubscriptionInfo.class);
+ when(subInfo.getSubscriptionId()).thenReturn(subId);
+ return subInfo;
+ }
+
+ @Test
+ public void testNetworkCallbacksRegisteredOnStartup() {
+ // verify NetworkCallbacks registered when instantiated
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getWifiRequest()),
+ any(),
+ any(NetworkBringupCallback.class));
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getCellRequestForSubId(INITIAL_SUB_ID_1)),
+ any(),
+ any(NetworkBringupCallback.class));
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getCellRequestForSubId(INITIAL_SUB_ID_2)),
+ any(),
+ any(NetworkBringupCallback.class));
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getRouteSelectionRequest()),
+ any(),
+ any(RouteSelectionCallback.class));
+
+ verify(mSubscriptionManager).getSubscriptionsInGroup(eq(SUB_GROUP));
+ }
+
+ private NetworkRequest getWifiRequest() {
+ return getExpectedRequestBase()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ }
+
+ private NetworkRequest getCellRequestForSubId(int subId) {
+ return getExpectedRequestBase()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
+ .build();
+ }
+
+ private NetworkRequest getRouteSelectionRequest() {
+ return getExpectedRequestBase().build();
+ }
+
+ private NetworkRequest.Builder getExpectedRequestBase() {
+ return new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ }
+
+ @Test
+ public void testTeardown() {
+ mUnderlyingNetworkTracker.teardown();
+
+ // Expect 3 NetworkBringupCallbacks to be unregistered: 1 for WiFi and 2 for Cellular (1x
+ // for each subId)
+ verify(mConnectivityManager, times(3))
+ .unregisterNetworkCallback(any(NetworkBringupCallback.class));
+ verify(mConnectivityManager).unregisterNetworkCallback(any(RouteSelectionCallback.class));
+ }
+
+ @Test
+ public void testUnderlyingNetworkRecordEquals() {
+ UnderlyingNetworkRecord recordA =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ UnderlyingNetworkRecord recordB =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ UnderlyingNetworkRecord recordC =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ UPDATED_NETWORK_CAPABILITIES,
+ UPDATED_LINK_PROPERTIES,
+ false /* isBlocked */);
+
+ assertEquals(recordA, recordB);
+ assertNotEquals(recordA, recordC);
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkChange() {
+ verifyRegistrationOnAvailableAndGetCallback();
+ }
+
+ private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback() {
+ return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
+ }
+
+ private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback(
+ NetworkCapabilities networkCapabilities) {
+ verify(mConnectivityManager)
+ .requestBackgroundNetwork(
+ eq(getRouteSelectionRequest()),
+ any(),
+ mRouteSelectionCallbackCaptor.capture());
+
+ RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue();
+ cb.onAvailable(mNetwork);
+ cb.onCapabilitiesChanged(mNetwork, networkCapabilities);
+ cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES);
+ cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ networkCapabilities,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ return cb;
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ UPDATED_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForLinkPropertiesChange() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ UPDATED_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onNetworkSuspended(mNetwork);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ SUSPENDED_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkResumed() {
+ RouteSelectionCallback cb =
+ verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
+
+ cb.onNetworkResumed(mNetwork);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ false /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForBlocked() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
+
+ UnderlyingNetworkRecord expectedRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ INITIAL_NETWORK_CAPABILITIES,
+ INITIAL_LINK_PROPERTIES,
+ true /* isBlocked */);
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ }
+
+ @Test
+ public void testRecordTrackerCallbackNotifiedForNetworkLoss() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onLost(mNetwork);
+
+ verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null);
+ }
+
+ @Test
+ public void testRecordTrackerCallbackIgnoresDuplicateRecord() {
+ RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+ cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+
+ // Verify no more calls to the UnderlyingNetworkTrackerCallback when the
+ // UnderlyingNetworkRecord does not actually change
+ verifyNoMoreInteractions(mNetworkTrackerCb);
+ }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index b4d39bf..4d92fb9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -94,7 +94,7 @@
doReturn(mUnderlyingNetworkTracker)
.when(mDeps)
- .newUnderlyingNetworkTracker(any(), any(), any());
+ .newUnderlyingNetworkTracker(any(), any(), any(), any());
}
@Before
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index ef26532..c64f4bc 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.AlarmManager;
@@ -948,8 +949,9 @@
* has been set up).
*/
public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
- @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs,
- @Nullable Bundle extraScanningParams) {
+ @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
+ @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
+ @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);